現在私が制作中のマップエディタで、部分消去という処理があります。この部分消去は、同じパーツが隣接していた場合に、それを間引くという機能も実装しようとしてました。そのとき、最初のコードからは 40% 程度までは高速化できたのですが、それをTwitterでもっと速くならないかと、フォロワーの皆さんに提示したところ、様々な提案がありまして、そちらを試していったところ、50%まで高速化する事が出来ました。今回はその手法について、具体的に説明していきたいと思います。

処理時間はむ 186ms です。ここから自力である程度は高速化していたのですが、私のTLにいるフォロワーの皆様は流石は百戦錬磨です。こういうのはどうかと次々と提案してもらい、試しているうちに完成したのが、次のコードです。

コードサイズは一気に大きくなりました。ですが、処理速度は一気に 97ms と 100の大台を切ってきました。それでは一つずつ、高速化の理由について解説していきます。
速度向上は数msでしたが、確実に速くなりました。
0 からスタートすると -1 したら配列範囲外アクセスになってしまうので、範囲内判定を入れていましたが、この範囲外が発生するのは、最上部の1行と最左端の1列だけと、例外発生タイミングは確定しています。そのため、ループの開始を 0 ではなく 1 からとしてしまえば、ループは必ず範囲内で走るようになるので、範囲内判定は不要になります。
ただ、そのままだと最上部と最左端が隣接判定されませんので、最上部と最左端だけ別途専用のループを走らせる必要が出ます。結果、一気にプログラムコードの長さが3倍近く大きくなりますが、元々そんなに大きい処理ではないので、この程度のサイズアップは許容範囲と考えました。
ループ内から判定を外すのは効くんですよねw
二次元配列の次元別要素数の受け取りに GetLength() というメソッドがありますが、その第一要素、第二要素とループを構築するようにすると、最適化が掛かりやすいようです。覚えておくと良いですね。
これで、データの隣接が多いほど効果が高くなります。これは疎の時も zero が隣接しますので処理速度の向上に繋がったりします。この効果は絶大でした!
また、ループ内で x - 1 とあるのを、第2ループが始まる直前に var zx = x - 1; 等と格納しておき、x - 1 を zx に置き換えるアイディアは、試してみたところ、殆ど変わらないか、場合によっては遅くなってしまいました。想像ですが、CPU にとっては -1 はデクリメント一発なので大した負荷にはならないのに対して、zx と作ってしまうと毎回変数の内容を拾ってくるため遅いのかなあと思いました。
以上、意外に盛り上がったのでしたw
- どう変わったのか

処理時間はむ 186ms です。ここから自力である程度は高速化していたのですが、私のTLにいるフォロワーの皆様は流石は百戦錬磨です。こういうのはどうかと次々と提案してもらい、試しているうちに完成したのが、次のコードです。

コードサイズは一気に大きくなりました。ですが、処理速度は一気に 97ms と 100の大台を切ってきました。それでは一つずつ、高速化の理由について解説していきます。
- プロパティ参照をローカル変数参照とした
速度向上は数msでしたが、確実に速くなりました。
PLUS(プラス)
2020-11-24
※ 妻が使ってます。切れ味も良く分離するとペーパーナイフのような使い方が出来るためとても重宝しています。
※ 妻が使ってます。切れ味も良く分離するとペーパーナイフのような使い方が出来るためとても重宝しています。
- zero 定数参照をローカル変数参照とした
- ループ内の範囲内判定を無くした
0 からスタートすると -1 したら配列範囲外アクセスになってしまうので、範囲内判定を入れていましたが、この範囲外が発生するのは、最上部の1行と最左端の1列だけと、例外発生タイミングは確定しています。そのため、ループの開始を 0 ではなく 1 からとしてしまえば、ループは必ず範囲内で走るようになるので、範囲内判定は不要になります。
ただ、そのままだと最上部と最左端が隣接判定されませんので、最上部と最左端だけ別途専用のループを走らせる必要が出ます。結果、一気にプログラムコードの長さが3倍近く大きくなりますが、元々そんなに大きい処理ではないので、この程度のサイズアップは許容範囲と考えました。
ループ内から判定を外すのは効くんですよねw
- 二次元配列の参照ループを連続的に参照できるようにした
二次元配列の次元別要素数の受け取りに GetLength() というメソッドがありますが、その第一要素、第二要素とループを構築するようにすると、最適化が掛かりやすいようです。覚えておくと良いですね。
- 対象を消去したら次は隣接ではない
これで、データの隣接が多いほど効果が高くなります。これは疎の時も zero が隣接しますので処理速度の向上に繋がったりします。この効果は絶大でした!
- 意外と効果が薄かった対応は
また、ループ内で x - 1 とあるのを、第2ループが始まる直前に var zx = x - 1; 等と格納しておき、x - 1 を zx に置き換えるアイディアは、試してみたところ、殆ど変わらないか、場合によっては遅くなってしまいました。想像ですが、CPU にとっては -1 はデクリメント一発なので大した負荷にはならないのに対して、zx と作ってしまうと毎回変数の内容を拾ってくるため遅いのかなあと思いました。
以上、意外に盛り上がったのでしたw
※ 名古屋人御用達のめちゃくちゃ美味しいとんかつソース。少なくとも我が家ではコレ一択です!
コメント