最適化に関する私の (限られた) 知識に関する限り、私は常に、コンパイラーの最適化はそれほど重要ではないと考えていました。つまり、メモリーの代わりにレジスターを使用してループをアンローリングすることで、かなりの数パーセント (非常に大まかな概算としては 0...100% かもしれません) の時間を確かに得ることができますが、コードのパフォーマンスを制約する基本的な要因は実際にはアルゴリズムの選択。
私は最近、小さなインタープリター型スクリプト言語であるペット プロジェクトを開始しました。バイトコードにコンパイルし、仮想マシンを使用して実行します。デバッグ、プロファイリング、およびテストを容易にするために、最初に(および) の-O0 -g
フラグを使用してコードを自然にコンパイルし、次に. 次に、VM 上で実行する小さなプログラムを作成しました。これは基本的にこれを行います (疑似コード、プロジェクトが公開されるまで実際の構文は示しません)。gcc
clang
-O2
time
i = 1
sum = 0
while (i < 10000000) {
sum = (sum + i * i) mod 1000000
i++
}
print sum
これは、大まかに次の疑似アセンブリに変換されます。
load r0, 0 # sum = 0
load r1, 1 # i = 1
load r2, 1000000
load r3, 10000000
loop:
mul r4, r1, r1 # i * i
add r0, r0, r4 # sum += i * i
mod r0, r0, r2 # sum %= 1000000
inc r1
gt r5, r1, r3 # if i > 10000000
jz r5, loop # then don't goto loop
ret r0
基本的に、これは 10000000 回の反復を伴うタイトなループです。でコンパイルした場合は 0.47...0.52 秒、 でコンパイルした場合は 1.51...1.74 秒、プロファイリング ( ) が有効な場合は 3.16...3.47 秒time
で実行されることが報告されています。-O2
-O0 -g
-pg
ご覧のとおり、最速の実行時間と最も遅い実行時間の間には 7 倍の差があります。
これ自体はそれほど驚くべきことではありません。なぜなら、追加のデバッグ情報と小さな最適化の欠如が実際にコードの実行を遅くすることを知っていたからです。実際に何が起こるかをより現実的に理解するために、Lua 5.2 で同じゲームをプレイしました。をいじってみるMakefile
と、まったく同じ Lua プログラムであることがわかりました。
local sum = 0
for i = 1, 10000000 do
sum = (sum + i * i) % 1000000
end
print(sum)
Lua を でコンパイルすると約 0.8...0.87 秒、 onlyを有効-O0 -g -pg
にすると 0.39...0.43 秒で実行されます。-O2
そのため、オプティマイザーがトリッキーな修正を実行すると、コードの速度が 7 倍に向上したように見えますが、Lua リファレンス実装では、これらの改善によるメリットははるかに少ないようです。
今私の質問は次のとおりです。
なぜこれが起こるのか分かりますか?その主な理由は、Lua の作成者が私より頭が良く、コンパイラとその VM が何をしているかをよく知っているからだと思います。
また、「これは時期尚早の最適化に違いない。オプティマイザーに渡してみましょう。とにかく仕事をしてくれる」と何度か考えていることに気づきました。これらには、ほぼすべての VM 命令の実装で呼び出される 1 行の静的関数 (必要に応じてインライン化されると思っていました)、さまざまな複雑な式の使用 (予測が容易ではない副作用を伴う場合もあります) が含まれます。ただし、それは単純化できます。私のこの態度も重要ですか?(ちなみに、それは正しい態度ですか?)
とにかく、この現象についてまったく心配する必要がありますか?結局、私のコードは Lua よりも 1.5 倍遅く実行されます。これはかなり良いことです (少なくとも、私にとっては十分です)。デバッグ ビルドのパフォーマンスを改善しようとする必要があるのは、自分のコードに関する詳細な知識が不足していることを示しているからです。それとも、リリース (最適化) ビルドが十分に高速である限り、完全に忘れてもいいですか?
(この質問が Prog.SE または CodeReview に適している場合は、お知らせください。移行します。)