- 特定のオブジェクトの特定の datamemberに値をバインドする必要があるのは、bStateがtrueの場合だけです。 bState がfalseの場合は、必要ではありませんが、どちらでも問題ありません。
次のコードのほうが効率的で、その理由は?状態チェックは常に効率的なものですか?
私はbState
を伝えることができるものについては
for (; i < x; ++i) {
pObject[i].somedatamember = iToBind;
}
次のコードのほうが効率的で、その理由は?状態チェックは常に効率的なものですか?
私はbState
を伝えることができるものについては
for (; i < x; ++i) {
pObject[i].somedatamember = iToBind;
}
私は後者が間違いなく速いと言います。第1のバージョンは双方向メモリアクセスを有し、後者は単方向メモリアクセスを有する。
このバージョンでは:CPUは、メモリから読み出されるデータを待つ必要がありますよう
for (; i < x; ++i) {
if (pObject[x].bState) {
pObject[x].somedatamember = iToBind;
}
}
if
の文の間にストールがあります。メモリが読み込まれる速度は、データがどこにあるかによって異なります。 CPUから離れるほど、L1(最速)、L2、L3、RAM、ディスク(最も遅い)のいずれか長い時間がかかります。このバージョンで
for (; i < x; ++i) {
pObject[x].somedatamember = iToBind;
}
専用メモリへの書き込みがあります。メモリへの書き込みによってCPUが停止することはありません。
メモリアクセス時間と同様に、後者のループはループ内に条件ジャンプを持ちません。条件付きループは、とりわけ、取られた/されなかった決定が効果的にランダムである場合に、重大なオーバーヘッドである。
メモリアクセスに関しては、どちらの場合も、オブジェクトがCPUキャッシュにロードされる必要があるため、似ていると主張します。私たちが細部まで細かく説明するならば、最初の形式は、CPUコアが読み込みのために排他的アクセスを必要としないので、キャッシュライン上のロック競合を防ぐかもしれないことを指摘していますか? @MatthieuM。 –
。それは気の利いたよ!しかし、もう少し説明できますか? :p – xcrypt
@MattieuM:最初のケースでは、オブジェクト全体がロードされず、もっと重要なことに、ロードされるデータはメモリコアサブシステムでのみ使用され、CPUコアでは使用されません。 2番目の形式では、キャッシュを完全にバイパスできます。 – Skizz
が最初にループ内で変更されません:
const int x;
int i;
int iToBind;
Classname pObject[x];
for (; i < x; ++i) {
if (pObject[i].bState) {
pObject[i].somedatamember = iToBind;
}
}
対(EDIT更新は、状態が現在のオブジェクトのメンバーです)フラグメントなので、if
を外に置くことができますが、これは明らかに効率的です。
Loop Invariant Code Motionについて聞いたことがありますか?
可能であれば、ループ本体からコードを移動するコンパイラからの最適化パスです。例えば
、次のコード所与:バックCに変換することができる
define i32 @main(i32 %argc, i8** nocapture %argv) nounwind {
%1 = icmp sgt i32 %argc, 0
br i1 %1, label %.lr.ph, label %._crit_edge
.lr.ph: ; preds = %0
%2 = icmp slt i32 %argc, 100
%3 = getelementptr inbounds i8** %argv, i64 1
br i1 %2, label %4, label %._crit_edge
; <label>:4 ; preds = %4, %.lr.ph
%i.01.us = phi i32 [ %9, %4 ], [ 0, %.lr.ph ]
%5 = load i8** %3, align 8, !tbaa !0
%6 = tail call i64 @strtol(i8* nocapture %5, i8** null, i32 10) nounwind
%7 = trunc i64 %6 to i32
%8 = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @.str, i64 0, i64 0), i32 %7) nounwind
%9 = add nsw i32 %i.01.us, 1
%exitcond = icmp eq i32 %9, %argc
br i1 %exitcond, label %._crit_edge, label %4
._crit_edge: ; preds = %4, %.lr.ph, %0
ret i32 0
}
:
int main(int argc, char** argv) {
if (argc == 0) { return 0; }
if (argc >= 100) { return 0; }
for (int i = 0; i < argc; ++i) {
printf("%d\n", atoi(argv[1]));
}
return 0;
}
結論
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv) {
for (int i = 0; i < argc; ++i) {
if (argc < 100) {
printf("%d\n", atoi(argv[1]));
}
}
}
クランは、以下のIRを生成します。は、マイクロ最適化を気にせずにプロファイラは、あなたが思ったほどミクロではないことを明らかにします。
EDIT:
編集は根本質問(:P私はそれを憎む神)に変更しました。LICMはもはや適用されず、2つの機能には幅広く異なる機能があります。
結論は同じです。 for
ループ内の単一のif
チェックでは、コードの基本的な複雑さは変わりません(ループの状態は各繰り返しでもテストされます)。
申し訳ありませんが悪い例です。素晴らしい答えがあります:) 編集:と申し訳ありません編集:p – xcrypt
すべてがあなたの投稿を簡略化したかどうかによって異なります。変数の設定をスキップするだけのブランチを追加している場合は、おそらく何も得られず、ブランチ予測が失敗した場合には失われている可能性があります。私はテストを削除します。 [今すぐ
、更新するオブジェクトはいつものように、単純なint
...でない場合は、測定、プロファイルと実際の事実ではなく、勘に基づいて決定を下します。これがタイトなループの一部でない場合は、いずれにしても違いに気付かないことがあります。
私は本当にコンテキストに依存していると思います。バインド中に bState
が真であることが重要である場合は、ステートをチェックするループの繰り返しごとに余分な1または2のアセンブリ命令を支払う必要があります。そうでない場合は、x
が特に大きい場合は、 if
を除外してください。
私は後者と一緒に行くだろうが、なぜ両方のバージョンを実行してどちらが速いのかを知りませんか? –
私はあなたのコードを単純化していると思いますが、書かれているように、ループ外で 'if(bState)'テストを動かすことができます。 – Chowlett
いつものように、答えは:あなたが実際に気にしているのなら、*プロファイルit *。おそらく顕著な違いはありませんが、それは私の個人的な予測です。再び、プロファイリングだけが伝えます。 – delnan