C ++では、わざわざ変数をキャッシュするか、コンパイラーに最適化を行わせる必要がありますか?(エイリアシング)


114

次のコードを考えてみてください(これpは型unsigned char*bitmap->widthあり、整数型であり、正確には不明であり、使用している外部ライブラリのバージョンに依存します)。

for (unsigned x = 0;  x < static_cast<unsigned>(bitmap->width);  ++x)
{
    *p++ = 0xAA;
    *p++ = 0xBB;
    *p++ = 0xCC;
}

それを最適化する価値があります[..]

これにより、次のように書くことでより効率的な結果が得られる場合があります。

unsigned width(static_cast<unsigned>(bitmap->width));
for (unsigned x = 0;  x < width;  ++x)
{
    *p++ = 0xAA;
    *p++ = 0xBB;
    *p++ = 0xCC;
}

...または、コンパイラが最適化するのは簡単ですか?

「より良い」コードは何だと思いますか?

編集者からのメモ(Ike):取り消しテキストについて疑問に思っている方のために、元の質問は、フレーズどおり、トピックから外れた領域に危険なほど近く、肯定的なフィードバックにもかかわらず非常に閉じていました。これらは打たれました。ただし、質問のこれらの被害を受けたセクションに対応した回答者を罰しないでください。


19
*pと同じタイプの場合、ループ内でポイントして変更できるwidthため、最適化するのは簡単ではありません。pwidth
emlai 2015年

31
コンパイラが特定の操作を最適化するかどうかを尋ねることは、通常、間違った質問です。(通常)最終的に関心があるのは、どのバージョンがより高速に実行されるかであり、単純に測定する必要があります。
SirGuy 2015年

4
@GuyGreer私は同意しますが、質問は良い、または少なくとも興味深いと思いますが、残念ながら答えは「ユースケースごとに測定する必要がある」と考えました。その理由は、機能は移植可能ですが、パフォーマンスはそうではないからです。そのため、実際には、コンパイラから始まり、ターゲットサイト(OS /ハードウェアの組み合わせ)で終了する、ビルドプロセスのすべての部分に依存します。そしてもちろん、最良の推測は、コンパイラは人間よりも賢いということです。
luk32 2015年

19
私がコンパイラだったら、あなたの2つの例は同じではないことがわかります。がとp同じメモリを指す可能性がありbitmap->widthます。したがって、最初の例を2番目の例に合法的に最適化することはできません。
Mysticial 2015年

4
「p」はどこに保存されますか?「char * restrict p2 = p;」のようなものを実行することで、パフォーマンスを大幅に向上させることができると思います。ループ内で「p」ではなく「p2」を使用します。次に、「p2」への変更をpに適用したい場合は、「p + =(p2-p);」を使用します。p2からコピーされていないポインターによってp2の存続期間内に書き込まれたポインターは、p2からコピーされたポインターを使用して読み取ることはできません。その逆も同様です。p2の存続期間後、p2のコピーを目的に使用することはできませんが、コンパイラーはそれらを使用できます。他の方法では達成できない最適化を可能にする事実。
スーパーキャット2015年

回答:


81

一見すると、コンパイラーは、最適化フラグがアクティブになっている両方のバージョンの同等のアセンブリーを生成できると思いました。確認したところ、その結果に驚きました。

ソース unoptimized.cpp

注:このコードは実行するためのものではありません。

struct bitmap_t
{
    long long width;
} bitmap;

int main(int argc, char** argv)
{
    for (unsigned x = 0 ; x < static_cast<unsigned>(bitmap.width) ; ++x)
    {
        argv[x][0] = '\0';
    }
    return 0;
}

ソース optimized.cpp

注:このコードは実行するためのものではありません。

struct bitmap_t
{
    long long width;
} bitmap;

int main(int argc, char** argv)
{
    const unsigned width = static_cast<unsigned>(bitmap.width);
    for (unsigned x = 0 ; x < width ; ++x)
    {
        argv[x][0] = '\0';
    }
    return 0;
}

コンパイル

  • $ g++ -s -O3 unoptimized.cpp
  • $ g++ -s -O3 optimized.cpp

アセンブリ(最適化されていない。s)

    .file   "unoptimized.cpp"
    .text
    .p2align 4,,15
.globl main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    .cfi_personality 0x3,__gxx_personality_v0
    movl    bitmap(%rip), %eax
    testl   %eax, %eax
    je  .L2
    xorl    %eax, %eax
    .p2align 4,,10
    .p2align 3
.L3:
    mov %eax, %edx
    addl    $1, %eax
    movq    (%rsi,%rdx,8), %rdx
    movb    $0, (%rdx)
    cmpl    bitmap(%rip), %eax
    jb  .L3
.L2:
    xorl    %eax, %eax
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
.globl bitmap
    .bss
    .align 8
    .type   bitmap, @object
    .size   bitmap, 8
bitmap:
    .zero   8
    .ident  "GCC: (GNU) 4.4.7 20120313 (Red Hat 4.4.7-16)"
    .section    .note.GNU-stack,"",@progbits

アセンブリ(optimized.s)

    .file   "optimized.cpp"
    .text
    .p2align 4,,15
.globl main
    .type   main, @function
main:
.LFB0:
    .cfi_startproc
    .cfi_personality 0x3,__gxx_personality_v0
    movl    bitmap(%rip), %eax
    testl   %eax, %eax
    je  .L2
    subl    $1, %eax
    leaq    8(,%rax,8), %rcx
    xorl    %eax, %eax
    .p2align 4,,10
    .p2align 3
.L3:
    movq    (%rsi,%rax), %rdx
    addq    $8, %rax
    cmpq    %rcx, %rax
    movb    $0, (%rdx)
    jne .L3
.L2:
    xorl    %eax, %eax
    ret
    .cfi_endproc
.LFE0:
    .size   main, .-main
.globl bitmap
    .bss
    .align 8
    .type   bitmap, @object
    .size   bitmap, 8
bitmap:
    .zero   8
    .ident  "GCC: (GNU) 4.4.7 20120313 (Red Hat 4.4.7-16)"
    .section    .note.GNU-stack,"",@progbits

差分

$ diff -uN unoptimized.s optimized.s
--- unoptimized.s   2015-11-24 16:11:55.837922223 +0000
+++ optimized.s 2015-11-24 16:12:02.628922941 +0000
@@ -1,4 +1,4 @@
-   .file   "unoptimized.cpp"
+   .file   "optimized.cpp"
    .text
    .p2align 4,,15
 .globl main
@@ -10,16 +10,17 @@
    movl    bitmap(%rip), %eax
    testl   %eax, %eax
    je  .L2
+   subl    $1, %eax
+   leaq    8(,%rax,8), %rcx
    xorl    %eax, %eax
    .p2align 4,,10
    .p2align 3
 .L3:
-   mov %eax, %edx
-   addl    $1, %eax
-   movq    (%rsi,%rdx,8), %rdx
+   movq    (%rsi,%rax), %rdx
+   addq    $8, %rax
+   cmpq    %rcx, %rax
    movb    $0, (%rdx)
-   cmpl    bitmap(%rip), %eax
-   jb  .L3
+   jne .L3
 .L2:
    xorl    %eax, %eax
    ret

最適化されたバージョン用に生成されたアセンブリは、各反復でオフセットを計算する最適化されていないバージョン(leawidthとは異なり、実際に定数をロードしwidthます(movq)。

時間があるとき、私は最終的にいくつかのベンチマークを投稿します。良い質問。


3
最適化されていない場合にキャストするのではconst unsignedなく、コードが異なる方法で生成されているかどうかを確認すると興味深いでしょうunsigned
Mark Ransom

2
@MarkRansom違いはないと思います:constであることの「約束」は、ループ全体ではなく、単一の比較中のみです
Hagen von Eitzen

13
してください決して機能を使用しないmain最適化のためのテストに。Gccは意図的にそれをコールドとしてマークし、そのためいくつかの最適化を無効にします。それがここに当てはまるかどうかはわかりませんが、それは理解するための重要な習慣です。
Marc Glisse、2015年

3
@MarcGlisseあなたは100%正しいです。私は急いでそれを書きました、私はそれを改善します。
YSC 2015年

3
ここへのリンクですgodboltに1個のコンパイル単位での両方の機能を想定し、bitmapグローバルです。非CSEdバージョンは、メモリオペランドtoを使用しますがcmp、この場合はperfの問題ではありません。ローカルの場合、コンパイラーは他のポインターがそれを「認識」できず、それを指すことができないと想定する可能性があります。グローバルを含む式を一時変数に格納することは、読みやすさを向上させる(または害を与えない)場合、またはパフォーマンスが重要である場合に、問題ではありません。多くのことが起こっていない限り、そのような地元の人々は通常レジスターに住んでいるだけで、こぼれることはありません。
Peter Cordes

38

実際には、コードスニペットからは情報を伝えるには不十分な情報があり、私が考えることができる1つのことはエイリアシングです。私たちの観点からは、メモリ内の同じ場所を望まpbitmapに指し示すことは明らかですが、コンパイラはそれを認識せず、(pタイプがであるためchar*)コンパイラはこのコードを機能させる必要がありますpそしてbitmap重なる。

つまり、この場合bitmap->width、ポインターを介してループが変更された場合、後でp再読み取りするときbitmap->widthにそれを確認する必要があります。つまり、ローカル変数にループを格納することは不正です。

そうは言っても、一部のコンパイラーは実際には同じコードの2つのバージョンを生成することがあります(これについては状況証拠を確認しましたが、この場合にコンパイラーが実行していることに関する情報を直接求めたことはありません)。エイリアスを設定し、問題がなければ高速なコードを実行します。

とは言っても、2つのバージョンのパフォーマンスを単に測定することについての私のコメントはそのままにしています。私のお金は、2つのバージョンのコード間で一貫したパフォーマンスの違いが見られないことです。

私の意見では、このような質問は、コンパイラの最適化の理論と手法について学ぶことを目的とする場合は問題ありませんが、ここでの最終目標がプログラムの実行を高速化することである場合は、時間の無駄です(無駄なマイクロ最適化)。


1
@GuyGreer:これは主要な最適化ブロッカーです。言語のルールが、さまざまなアイテムの書き込みと読み取りがシーケンスされていない、またはシーケンスされていない状況を特定するのではなく、効果的なタイプに関するルールに焦点を当てているのは残念です。そのような用語で書かれたルールは、現在のものよりもコンパイラーおよびプログラマーのニーズを満たすというはるかに優れた仕事をすることができます。
スーパーキャット2015年

3
@GuyGreer- restrictこの場合、修飾子はエイリアシングの問題の答えになりませんか?
LThode

4
私の経験でrestrictは、大部分は見当違いです。MSVCは、私がこれまでに正しく実行したように見える唯一のコンパイラです。ICCは、インライン化されている場合でも、関数呼び出しを通じてエイリアス情報を失います。そして、GCCは通常、すべての入力パラメーターをrestrictthisメンバー関数を含む)として宣言しない限り、何のメリットも得られません。
Mysticial 2015年

1
@Mystical:覚えておくべきことの1つは、charすべての型のエイリアスであることです。したがって、char *がある場合は、restrictすべてに対して使用する必要があります。または、GCCの厳密なエイリアスルールを強制的にオフにした-fno-strict-aliasing場合、すべてが可能なエイリアスと見なされます。
Zan Lynx 2015年

1
@Ray restrictC ++ でのようなセマンティクスの最新の提案はN4150です。
TC、

24

わかりました、私は測定しましたGCC -O3(Linux x64でGCC 4.9を使用)。

結局、2番目のバージョンは54%高速に実行されます。

ですから、エイリアシングが問題だと思います、私はそれについて考えていませんでした。

[編集]

すべてのポインターがで定義された最初のバージョンを再試行しましたが__restrict__、結果は同じです。奇妙です。エイリアシングは問題ではないか、または何らかの理由で、コンパイラーはを使用してもそれを最適化しません__restrict__

[編集2]

OK、エイリアシングが問題であることを証明できたと思います。私は元のテストを繰り返しましたが、今回はポインターではなく配列を使用しました。

const std::size_t n = 0x80000000ull;
bitmap->width = n;
static unsigned char d[n*3];
std::size_t i=0;
for (unsigned x = 0;  x < static_cast<unsigned>(bitmap->width);  ++x)
{
    d[i++] = 0xAA;
    d[i++] = 0xBB;
    d[i++] = 0xCC;
}

そして、測定しました(「-mcmodel = large」を使用してリンクする必要がありました)。それから私は試しました:

const std::size_t n = 0x80000000ull;
bitmap->width = n;
static unsigned char d[n*3];
std::size_t i=0;
unsigned width(static_cast<unsigned>(bitmap->width));
for (unsigned x = 0;  x < width;  ++x)
{
    d[i++] = 0xAA;
    d[i++] = 0xBB;
    d[i++] = 0xCC;
}

測定結果は同じでした-コンパイラがそれ自体で最適化できたようです。

次に、元のコード(ポインタ付きp)を試しましたが、今回pは型std::uint16_t*です。繰り返しになりますが、結果は同じです-厳密なエイリアシングのため。その後、「-fno-strict-aliasing」を使用してビルドを試みたところ、時間の違いがわかりました。


4
これはコメントであるように見えますが、技術的には質問に答えます。また、残念ながら、エイリアスが問題であることを実証していません。それは確かにもっともらしいようですが、それがそれであったと結論付けることとは異なります。
SirGuy 2015年

@GuyGreer:私の[編集2]を参照してください-今ではかなり証明されていると思います。
Yaron Cohen-Tal 2015年

2
ループに「x」があるときに変数「i」を使い始めたのはなぜですか。
Jesper Madsen

1
フレーズを54%速く理解するのが難しいと思うのは私だけですか?それは、最適化されていない速度の1.54倍か、それ以外の速度ですか?
Roddy、

3
@ YaronCohen-Talは2倍以上速いですか?印象的ですが、「54%速い」という意味では理解できませんでした。
Roddy、

24

他の答えは、ポインタ操作をループの外に引き上げると、charにエイリアスを許可するエイリアスルールが原因で定義済みの動作が変わる可能性があるため、ほとんどの場合それが人間にとって明らかに正しいとしても、コンパイラにとって許容できる最適化ではないことが指摘されていますプログラマー。

彼らはまた、ループの外で操作を引き上げることは、パフォーマンスの観点からは常にではないが通常は改善であり、読みやすさの観点からはしばしば否定的であることを指摘しました。

「第三の方法」がしばしばあることを指摘したいと思います。必要な反復回数までカウントアップするのではなく、ゼロまでカウントダウンできます。これは、ループの開始時に反復回数が1度だけ必要であることを意味し、その後に格納する必要はありません。アセンブラーレベルのほうが優れていますが、デクリメント操作では通常、デクリメントの前(キャリーフラグ)とアフターフラグ(ゼロフラグ)の両方でカウンターがゼロであったかどうかを示すフラグが設定されるため、明示的な比較が不要になることがよくあります。

for (unsigned x = static_cast<unsigned>(bitmap->width);x > 0;  x--)
{
    *p++ = 0xAA;
    *p++ = 0xBB;
    *p++ = 0xCC;
}

このバージョンのループは、0 ..(width-1)の範囲ではなく、1..widthの範囲のx値を提供することに注意してください。実際にはxを何も使用していないため、これは問題ではありませんが、注意する必要があります。xの値が0 ..(width-1)の範囲のカウントダウンループが必要な場合は、実行できます。

for (unsigned x = static_cast<unsigned>(bitmap->width); x-- > 0;)
{
    *p++ = 0xAA;
    *p++ = 0xBB;
    *p++ = 0xCC;
}

また、ビットマップ->幅で実行しているのは変数に直接割り当てるため、比較ルールへの影響を心配せずに、上記の例のキャストを取り除くこともできます。


2
2番目のケースがとしてフォーマットされx --> 0ているため、「downto」演算子が発生します。かなり面白いです。PS私は、終了条件の変数を読みやすくするために負にすることは考慮していません。実際には逆になる可能性があります。
Mark Ransom 2015年

それは本当に依存し、時にはステートメントが非常に恐ろしくなり、それを複数のステートメントに分割すると読みやすさが向上しますが、ここではそうではないと私は信じていません。
プラグウォッシュ2015年

1
+1良い観察ですが、ループでのの引き上げstatic_cast<unsigned>(bitmap->width)width代わりの使用は、読み手が1行ごとに解析することが少なくなるため、実際には読みやすさの改善になると主張します。他人の見解は異なる場合があります。
SirGuy 2015年

1
カウントダウンの方が優れている状況は他にもたくさんあります(リストからアイテムを削除する場合など)。なぜこれが頻繁に行われないのか分かりません。
Ian Goldby

3
最適なasmのように見えるループを作成する場合は、を使用してください。ASMではdo { } while()、最後に条件付き分岐でループを作成するためです。通常のループfor(){}while(){}ループでは、コンパイラが少なくとも1回は常に実行されることを証明できない場合、ループの前に1回ループ条件をテストするための追加の命令が必要です。ループを1回でも実行する必要があるかどうか、または読みやすいかどうかを確認するのに役立つ場合、for()または使用while()する場合は、必ずです。
Peter Cordes

11

最適化を妨げる可能性があるのは、厳密なエイリアシングルールだけです。つまり

「厳密なエイリアシングは、C(またはC ++)コンパイラによって作成された、異なるタイプのオブジェクトへのポインタの逆参照は決して同じメモリ位置を参照しない(つまり、相互にエイリアシングする)ことを前提としています。」

[…]

ルールの例外は、char*任意のタイプを指すことができるです。

例外はunsignedsigned charポインタにも適用されます。

これはあなたのコードの場合です:あなたは*pを通してpを変更しているunsigned char*ので、コンパイラそれがを指すことができると仮定しなければなりませんbitmap->width。したがって、のキャッシュbitmap->widthは無効な最適化です。この最適化を防ぐ動作は、YSCの回答に示されています

非型および非型をp指し示している場合にのみ、キャッシングは可能な最適化となるでしょう。chardecltype(bitmap->width)


10

最初に尋ねられた質問:

最適化する価値はありますか?

そして、それに対する私の答え(賛成票と反対票の両方を上手く組み合わせて得ています。)

コンパイラにそれについて心配させてください。

コンパイラはほぼ間違いなくあなたより良い仕事をします。そして、あなたの「最適化」が「明白な」コードよりも優れているという保証はありません-それを測定しましたか?

さらに重要なことに、最適化しているコードがプログラムのパフォーマンスに影響を与えているという証拠はありますか?

反対票が投じられたにもかかわらず(そして今ではエイリアシングの問題が発生しています)、それでも有効な答えとして満足しています。何かを最適化する価値があるかどうかわからない場合は、おそらくそうではありません。

もちろん、かなり異なる質問は次のとおりです。

コードのフラグメントを最適化する価値があるかどうかはどうすればわかりますか?

まず、アプリケーションまたはライブラリは、現在よりも速く実行する必要がありますか?ユーザーは長時間待ち続けていますか?ソフトウェアは、明日の天気予報ではなく、昨日の天気予報を予測していますか?

ソフトウェアの用途とユーザーの期待に基づいて、実際にこれを知ることができるのはあなただけです。

ソフトウェアに何らかの最適化が必要であると仮定すると、次に行うことは測定を開始することです。プロファイラーは、コードが時間を費やす場所を教えてくれます。フラグメントがボトルネックとして表示されない場合は、そのままにしておくことをお勧めします。プロファイラーやその他の測定ツールも、変更によって違いが生じたかどうかを教えてくれます。コードを最適化するために何時間も費やすことは可能ですが、認識できるほどの違いはないことがわかります。

とにかく、「最適化」とはどういう意味ですか?

「最適化された」コードを作成していない場合は、コードをできるだけ明確、簡潔、簡潔にする必要があります。「時期尚早な最適化は悪だ」という議論は、ずさんなコードや非効率なコードの言い訳にはなりません。

最適化されたコードは通常、パフォーマンスのために上記の属性の一部を犠牲にします。追加のローカル変数の導入、予想よりも広いスコープを持つオブジェクトの作成、通常のループの順序の逆転などが含まれる場合があります。これらはすべて、あまり明確ではないか簡潔になる可能性があるため、これを行う理由についてコードを(簡潔に!)文書化してください。

しかし、しばしば「遅い」コードでは、これらのマイクロ最適化は最後の手段です。最初に調べるのは、アルゴリズムとデータ構造です。作業をまったく回避する方法はありますか?線形検索をバイナリ検索に置き換えることはできますか?リンクされたリストはベクトルよりも高速ですか?それともハッシュテーブル?結果をキャッシュできますか?ここで適切な「効率的な」決定を行うと、多くの場合、桁違いにパフォーマンスに影響を与える可能性があります。


12
ビットマップ画像の幅を反復処理する場合、ループロジックは、ループで費やされる時間のかなりの部分を占める可能性があります。この場合は、時期尚早の最適化を心配するのではなく、最初から効率的なベストプラクティスを開発することをお勧めします。
Mark Ransom

4
@MarkRansomは一部同意しましたが、「ベストプラクティス」は、a:画像を埋めるために既存のライブラリまたはAPI呼び出しを使用するか、b:GPUに実行させます。これは、OPが提案する測定不能なマイクロ最適化のようなものであってはなりません。そして、どのようにしてこのコードが2回以上実行されたのか、またはビットマップが16ピクセルよりも大きいビットマップで実行されたことをどのようにして知るのですか...?
Roddy

@Veedrac。-1の正当性を認めてください。質問の推力は、私が回答して以来、微妙かつ大幅に変化しています。(拡張された)答えがまだ役に立たないと思われる場合、私がそれを削除する時間は... "それは価値がある..."はとにかく、常に主に意見に基づいています。
Roddy

@Roddy私は編集に感謝します、彼らは助けてくれます(そして私のコメントはおそらくとにかく厳しすぎるように聞こえました)。ただし、これはスタックオーバーフローに適さない質問への回答なので、私はまだ現場にいます。ここで非常に投票された回答がそうであるように、適切な回答はスニペットに固有であるように感じます。
Veedrac

6

このような状況では、次のパターンを使用します。それはあなたの最初のケースとほとんど同じくらい短く、ループに対してローカルな一時変数を保持するため、2番目のケースよりも優れています。

for (unsigned int x = 0, n = static_cast<unsigned>(bitmap->width); x < n; ++x)
{
  *p++ = 0xAA;
  *p++ = 0xBB;
  *p++ = 0xCC;
}

これは、スマートコンパイラ、デバッグビルド、または特定のコンパイルフラグよりも高速です。

Edit1:ループ外に定数演算を配置することは、優れたプログラミングパターンです。特にC / C ++でのマシン操作の基本の理解を示しています。自分自身を証明するための努力は、この慣行に従わない人々にあるべきだと私は主張します。コンパイラが良いパターンを罰する場合、それはコンパイラのバグです。

Edit2::vs2013で元のコードに対して提案を測定しましたが、%1改善されました。もっと上手にできる?単純な手動最適化により、エキゾチックな命令に頼ることなく、x64マシンの元のループの3倍の改善が得られます。以下のコードは、リトルエンディアンシステムと適切に配置されたビットマップを想定しています。テスト0はオリジナル(9秒)、テスト1は高速(3秒)です。私は誰かがこれをさらに速くできると思います、そしてテストの結果はビットマップのサイズに依存するでしょう。将来的には間違いなく、コンパイラーは常に最速のコードを生成できるようになるでしょう。これはコンパイラがプログラマAIになる将来になるのではないかと心配しています。ただし、今のところは、ループ内での追加の操作は不要であることを示すコードを記述してください。

#include <memory>
#include <time.h>

struct Bitmap_line
{
  int blah;
  unsigned int width;
  Bitmap_line(unsigned int w)
  {
    blah = 0;
    width = w;
  }
};

#define TEST 0 //define 1 for faster test

int main(int argc, char* argv[])
{
  unsigned int size = (4 * 1024 * 1024) / 3 * 3; //makes it divisible by 3
  unsigned char* pointer = (unsigned char*)malloc(size);
  memset(pointer, 0, size);
  std::unique_ptr<Bitmap_line> bitmap(new Bitmap_line(size / 3));
  clock_t told = clock();
#if TEST == 0
  for (int iter = 0; iter < 10000; iter++)
  {
    unsigned char* p = pointer;
    for (unsigned x = 0; x < static_cast<unsigned>(bitmap->width); ++x)
    //for (unsigned x = 0, n = static_cast<unsigned>(bitmap->width); x < n; ++x)
    {
      *p++ = 0xAA;
      *p++ = 0xBB;
      *p++ = 0xCC;
    }
  }
#else
  for (int iter = 0; iter < 10000; iter++)
  {
    unsigned char* p = pointer;
    unsigned x = 0;
    for (const unsigned n = static_cast<unsigned>(bitmap->width) - 4; x < n; x += 4)
    {
      *(int64_t*)p = 0xBBAACCBBAACCBBAALL;
      p += 8;
      *(int32_t*)p = 0xCCBBAACC;
      p += 4;
    }

    for (const unsigned n = static_cast<unsigned>(bitmap->width); x < n; ++x)
    {
      *p++ = 0xAA;
      *p++ = 0xBB;
      *p++ = 0xCC;
    }
  }
#endif
  double ms = 1000.0 * double(clock() - told) / CLOCKS_PER_SEC;
  printf("time %0.3f\n", ms);

  {
    //verify
    unsigned char* p = pointer;
    for (unsigned x = 0, n = static_cast<unsigned>(bitmap->width); x < n; ++x)
    {
      if ((*p++ != 0xAA) || (*p++ != 0xBB) || (*p++ != 0xCC))
      {
        printf("EEEEEEEEEEEEERRRRORRRR!!!\n");
        abort();
      }
    }
  }

  return 0;
}

int64_tとint32_tの代わりに3つのint64_tを使用すると、64ビットでさらに25%節約できます。
アントニン・Lejsek

5

考慮すべき点が2つあります。

A)最適化はどのくらいの頻度で実行されますか?

ユーザーがボタンをクリックしたときだけのように、答えがそれほど頻繁ではない場合は、コードが判読できなくても気にしないでください。答えが1秒間に1000回である場合は、おそらく最適化を行う必要があります。少しでも複雑な場合は、コメントを書き込んで、次にやって来る人を助けるために何が起こっているのかを説明してください。

B)これにより、コードの維持/トラブルシューティングが難しくなりますか?

パフォーマンスが大幅に向上していない場合は、コードを暗号化して数クロックの目盛りを節約することはお勧めできません。多くの人は、どんな優れたプログラマーでもコードを見て何が起こっているのかを理解できるはずだと言うでしょう。これは本当です。問題は、ビジネスの世界では、その計算に余分な時間がかかるとお金がかかるということです。だから、あなたがそれを読んでよりきれいにすることができるなら、それをしてください。あなたの友人はそれをあなたに感謝します。

それは私が個人的にBの例を使用すると言った。


4

コンパイラーは多くのことを最適化できます。あなたの例では、読みやすさ、保守性、そしてコード標準に準拠するために行く必要があります。(GCCを使用して)何を最適化できるかについて詳しくは、このブログ投稿を参照してください。


4

原則として、引き継ぐ必要があると判断するまで、コンパイラーに最適化を行わせます。このロジックはパフォーマンスとは関係ありませんが、人間の可読性とは関係ありません。で、広大な大多数の場合、あなたのプログラムの可読性は、その性能よりも重要です。人間が読みやすいコードを書くことを目指し、コードの保守性よりもパフォーマンスの方が重要であると確信できる場合にのみ、最適化について心配してください。

パフォーマンスが重要であることがわかったら、コードでプロファイラーを実行して、どのループが非効率的かを判断し、それらを個別に最適化する必要があります。実際にその最適化を行いたい場合もありますが(特に、STLコンテナーが関与するC ++に移行する場合)、読みやすさの点でコストは高くなります。

さらに、実際にコードが遅くなる可能性のある病理学的状況を考えることができます。たとえば、コンパイラーがbitmap->widthプロセス全体で一定であることを証明できなかった場合を考えてみます。width変数を追加することにより、コンパイラーにそのスコープ内のローカル変数を維持するように強制します。あるプラットフォーム固有の理由により、その余分な変数がスタック空間の最適化を妨げた場合、バイトコードの出力方法を再編成し、効率の悪いものを生成する必要があるかもしれません。

例として、Windows x64では__chkstk、関数が2ページ以上のローカル変数を使用する場合、関数のプリアンブルで特別なAPI呼び出しを呼び出す必要があります。この機能により、ウィンドウは、必要に応じてスタックを拡張するために使用するガードページを管理する機会を与えられます。追加の変数によってスタックの使用量が1ページ未満から1ページ以上にプッシュされる場合、関数__chkstkは入力されるたびに呼び出す必要があります。低速パスでこのループを最適化すると、低速パスで節約した速度よりも高速パスの速度が遅くなる可能性があります。

もちろん、それは少し病的ですが、その例の要点は、コンパイラを実際に遅くすることができるということです。それは、最適化がどこに行くかを決定するために作業をプロファイルする必要があることを示しているだけです。それまでの間、重要であるかどうかに関係なく、最適化のために読みやすさを決して犠牲にしないでください。


4
CとC ++が、プログラマが気にしないものを明示的に識別する方法をもっと提供してくれることを願っています。コンパイラーが物事を最適化する機会が増えるだけでなく、コードを読み取る他のプログラマーが、ビットマップ->幅を毎回再チェックしてループに影響を与えることを確認する必要があるかどうかを推測する必要がなくなります。変更がループに影響を及ぼさないようにするために、ビットマップ->幅をキャッシュするかどうか。「これをキャッシュするかどうか-私は気にしない」と言う手段があれば、プログラマの選択の理由が明確になります。
スーパーキャット2015年

@supercat私はこれを解決するために私が書こうと努めた入れ墨された失敗した言語の山を見ているかどうかがわかるように、心から同意します。私は、誰かが気にしない「何」を定義することは非常に難しいことを発見しました。私は無駄に検索を続けます。
Cort Ammon、2015年

すべてのケースでそれを定義することは不可能ですが、型システムが役立つケースはたくさんあると思います。あまりにもCはに適用できる文字の種類ではなく、「揮発性」よりもビット緩いた型修飾子を持つよりも、「ユニバーサルアクセサ」することに決めていますどのようなタイプのアクセスがで順次処理されるだろうとの意味で、タイプを修飾されていない同等のタイプのアクセス、および同じ修飾子を持つすべてのタイプの変数のアクセス。これは、文字タイプが必要だったために文字タイプを使用していたかどうかを明確にするのに役立ちます...
スーパーキャット2015年

...エイリアシングの動作、またはニーズに合う適切なサイズだったためにそれらを使用していたかどうか。文字型アクセスに関連する暗黙のバリアとは異なり、多くの場合ループの外側に配置できる明示的なエイリアシングバリアがあると便利です。
スーパーキャット2015年

1
これは賢明な話ですが、一般的に、タスクにすでにCを選択している場合は、おそらくパフォーマンスが非常に重要であり、さまざまなルールを適用する必要があります。それ以外の場合は、Ruby、Java、Pythonなどを使用することをお勧めします。
Audrius Meskauskas 2015年

4

比較が間違っている 2つのコードスニペットので、

for (unsigned x = 0;  x < static_cast<unsigned>(bitmap->width);  ++x)

そして

unsigned width(static_cast<unsigned>(bitmap->width));
for (unsigned x = 0;  x<width ;  ++x)

同等ではない

最初の場合widthは依存していてconstではなく、後続の反復間で変更されないことを想定することはできません。したがって、最適化することはできませんが、ループごとチェックする必要があります

最適化されたケースでは、ローカル変数には、bitmap->widthプログラム実行中のある時点での値が割り当てられます。コンパイラーは、これが実際に変更されていないことを確認できます。

マルチスレッドについて考えましたか、または値が変動するように値が外部に依存している可能性があります。あなたが言わなければ、コンパイラがこれらすべてを理解することをどのように期待しますか?

コンパイラができることは、コードで可能なことだけです。


2

コンパイラーがコードを最適化する方法を正確に知らない限り、コードを読みやすくして設計することにより、独自の最適化を行うことをお勧めします。実際には、新しいコンパイラバージョン用に作成するすべての関数のアセンブリコードをチェックすることは困難です。


1

bitmap->width値はwidth反復間で変更できるため、コンパイラーは最適化できません。最も一般的な理由はいくつかあります。

  1. マルチスレッド。コンパイラは、他のスレッドが値を変更しようとしているかどうかを予測できません。
  2. ループ内での変更。変数がループ内で変更されるかどうかを判断するのは簡単ではない場合があります。
  3. これは関数呼び出しです。たとえばiterator::end()container::size()常に同じ結果を返すかどうかを予測することは困難です。

高度な最適化が必要な場所について(私の個人的な意見)をまとめると、自分でそれを行う必要があります。他の場所ではそのままにしておくと、コンパイラが最適化するかどうかにかかわらず、コードの可読性が主な目標ではありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.