Javaの ジャストインタイムコンパイラ (JIT)最適化手法について説明しているドキュメントを調べていました。それらの1つは「ループ反転」でした。そして文書は言う:
通常の
while
ループをdo-while
ループに置き換えます。また、do-while
ループはif
句内で設定されます。この置換により、ジャンプが2つ少なくなります。
ループ反転はどのように機能し、コードパスを最適化しますか?
NB:Javaコードの例で誰かが説明できたら素晴らしいですそして、JITがネイティブコードに最適化する方法と、最新のプロセッサーで最適化される理由
while (condition) {
...
}
ワークフロー:
if (condition) do {
...
} while (condition);
ワークフロー:
これら2つを比較すると、ループ全体で1つのステップしかなく、ジャンプの数が反復数より1つ少ない場合、後者はジャンプをまったく行わない可能性があることが簡単にわかります。前者は条件をチェックするためにジャンプして戻る必要があり、条件が偽の場合にループからジャンプするだけです。
最新のパイプライン化されたCPUアーキテクチャでのジャンプは非常に高価になる可能性があります。CPUはジャンプ前にチェックの実行を終了しているため、そのジャンプを超える命令はすでにパイプラインの途中にあります。分岐予測が失敗した場合、この処理はすべて破棄する必要があります。パイプラインが再準備されている間、それ以上の実行は遅延されます。
上記の分岐予測の説明:条件付きジャンプの種類ごとに、CPUには2命令があり、それぞれに結果にbetが含まれています。たとえば、最後の反復を除くすべての反復でジャンプを行う必要があるため、ループの最後に「ゼロでない場合はジャンプ、非ゼロに賭ける」という命令を配置します。このようにして、CPUは、ジャンプ命令自体に続く命令ではなく、ジャンプターゲットに続く命令でパイプラインのポンプを開始します。
ソースコードレベルで最適化する方法の例として、notを実行してください。最初の形式から2番目の形式への変換は、JITコンパイラーがルーチンの問題として完全に独自に行うものであるため、それは完全に見当違いです。
これにより、常に少なくとも1回は実行されるループを最適化できます。
通常のwhile
ループは、常に少なくとも1回は最初に戻り、最後に1回は最後にジャンプします。 1回実行する単純なループの例:
int i = 0;
while (i++ < 1) {
//do something
}
一方、do-while
ループは、最初と最後のジャンプをスキップします。以下は、ジャンプなしで実行される上記のループと同等のループです。
int i = 0;
if (i++ < 1) {
do {
//do something
} while (i++ < 1);
}
ループ反転は、プロセッサーがより少ない命令で同じ結果を達成できるため、パフォーマンスを改善するパフォーマンス最適化手法です。これにより、境界条件でのパフォーマンスが向上するはずです。
このリンク は、ループ反転の別の例を提供します。デクリメントと比較が単一の命令セットとして実装されているいくつかのアーキテクチャでは、デクリメントと比較の操作でforループをwhileに変換することは理にかなっています。
ウィキペディアには非常に良い例があり、ここでもう一度説明します。
int i, a[100];
i = 0;
while (i < 100) {
a[i] = 0;
i++;
}
コンパイラによって次のように変換されます
int i, a[100];
i = 0;
if (i < 100) {
do {
a[i] = 0;
i++;
} while (i < 100);
}
これはどのようにパフォーマンスに変換されますか? iの値が99の場合、プロセッサはGOTO(最初のケースで必要)を実行する必要がありません。これにより、パフォーマンスが向上します。
それらを見ていきましょう:
while
バージョン:_void foo(int n) {
while (n < 10) {
use(n);
++n;
}
done();
}
_
n
をテストし、条件が真でない場合はdone();
にジャンプします。n
を使用して増分します。done()
にジャンプします。do-while
_バージョン:(実際には、これはソースコードでは行われません[メンテナンスの問題が発生する可能性があります]、コンパイラ/ JITが行います)
_void foo(int n) {
if (n < 10) {
do {
use(n);
++n;
}
while (n < 10);
}
done();
}
_
n
をテストし、条件が真でない場合はdone();
にジャンプします。n
を使用して増分します。done()
に(ジャンプせずに)フローします。したがって、たとえば、n
が_9
_で始まる場合、_do-while
_バージョンではまったくジャンプしませんが、while
バージョンでは、最初に、テストを行い、それが真実ではないとわかったら最後に戻ります。