web-dev-qa-db-ja.com

Postfixインクリメント演算子を避ける

パフォーマンス上の理由から(場合によっては) 後置インクリメント演算子を回避する をすべきだと私は読んだ。

しかし、これはコードの読みやすさに影響しませんか?私の考えでは:

for(int i = 0; i < 42; i++);
    /* i will never equal 42! */

よりよく見えます:

for(int i = 0; i < 42; ++i);
    /* i will never equal 42! */

しかし、これはおそらく習慣から外れています。確かに、私は++iの使用をあまり見ていません。

この場合、読みやすさを犠牲にするのにパフォーマンスは悪いですか?それとも私は盲目で、++ii++より読みやすいですか?

25
Mateen Ulhaq

事実:

  1. i ++と++ iは同じように読みやすいです。慣れていないので好きではありませんが、本質的にそれを誤って解釈することはできないため、読み取りや書き込みを行う必要はありません。

  2. 少なくともいくつかのケースでは、postfix演算子は効率が悪くなります。

  3. ただし、99.99%の場合は、(a)いずれにせよ単純型またはプリミティブ型で動作し、大きなオブジェクトをコピーする場合にのみ問題となるため、問題にはなりません(b)パフォーマンスに含まれませんコードの重要な部分(c)コンパイラが最適化するかどうか分からない場合、最適化する場合があります。

  4. したがって、(a)他のものと正確に一致するのは良い習慣であり、(b)青い月に一度、postfixを使用するつもりであるため、postfixが特に必要な場合を除き、接頭辞を使用することをお勧めしますそして、それを間違った方法で得る:あなたが常にあなたが意味することを書くならば、それはあまりありそうもない。パフォーマンスと最適化の間には常にトレードオフがあります。

常識を使い、必要になるまでマイクロ最適化を行わないでください。しかし、それのために極端に非効率的であることもありません。通常、これは次のことを意味します。最初に、タイムクリティカルではないコード(通常、500MBのオブジェクトを理由なく値で渡すなどの基本的な概念エラーを表すもの)でも許容できないほど効率的でないコード構造を除外します。次に、コードを書く他のすべての方法のうち、最も明確なものを選択します。

しかし、ここでは、答えは単純だと思います。特に接尾辞が必要でない限り、接頭辞を書くことは、(a)非常にわずかに明確であり、(b)非常にわずかに効率的である可能性が高いため、常にデフォルトで記述する必要がありますが、忘れても大丈夫。

6か月前、私はあなたと同じようにi ++の方が自然だと思っていましたが、これは純粋にあなたが慣れていることです。

編集1:私が一般的にこのことを信頼している「より効果的なC++」のスコット・マイヤーズは、ユーザー定義型で後置演算子を使用することは一般的に避けるべきだと述べています(後置インクリメント関数の唯一の健全な実装は、オブジェクトのコピー、接頭辞インクリメント関数を呼び出してインクリメントを実行し、コピーを返します。ただし、コピー操作には負荷がかかる場合があります)。

したがって、(a)今日に当てはまるかどうか、(b)組み込み型にも当てはまるかどうか(c)に「++」を使用する必要があるかどうかに関する一般的なルールがあるかどうかはわかりませんこれまでにない軽量のイテレータクラス以上のもの。しかし、私が上で説明したすべての理由のために、それは問題ではありません、私が前に言ったようにしてください。

編集2:これは一般的な慣行を指します。特定のインスタンスで問題になると思われる場合は、プロファイルを作成して確認する必要があります。プロファイリングは簡単で安価で機能します。何を最適化する必要があるかを第一原理から推定することは困難で費用がかかり、機能しません。

58
Jack V.

常にプログラマー向けに最初にコーディングし、次にコンピューター向けにコーディングします。

パフォーマンスの違いがある場合は、コンパイラがコードにエキスパートの目を向けた後、[〜#〜] and [〜#〜]測定できます[〜#〜] and [〜#〜]重要です-その後、それを変更できます。

62
Martin Beckett

GCCは両方のループに対して同じマシンコードを生成します。

Cコード

int main(int argc, char** argv)
{
    for (int i = 0; i < 42; i++)
            printf("i = %d\n",i);

    for (int i = 0; i < 42; ++i)
        printf("i = %d\n",i);

    return 0;
}

アセンブリコード(私のコメント付き)

    cstring
LC0:
    .ascii "i = %d\12\0"
    .text
.globl _main
_main:
    pushl   %ebp
    movl    %esp, %ebp
    pushl   %ebx
    subl    $36, %esp
    call    L9
"L00000000001$pb":
L9:
    popl    %ebx
    movl    $0, -16(%ebp)  // -16(%ebp) is "i" for the first loop 
    jmp L2
L3:
    movl    -16(%ebp), %eax   // move i for the first loop to the eax register 
    movl    %eax, 4(%esp)     // Push i onto the stack
    leal    LC0-"L00000000001$pb"(%ebx), %eax // load the effective address of the format string into the eax register
    movl    %eax, (%esp)      // Push the address of the format string onto the stack
    call    L_printf$stub    // call printf
    leal    -16(%ebp), %eax  // make the eax register point to i
    incl    (%eax)           // increment i
L2:
    cmpl    $41, -16(%ebp)  // compare i to the number 41
    jle L3              // jump to L3 if less than or equal to 41
    movl    $0, -12(%ebp)   // -12(%ebp) is "i" for the second loop  
    jmp L5
L6:
    movl    -12(%ebp), %eax   // move i for the second loop to the eax register 
    movl    %eax, 4(%esp)     // Push i onto the stack
    leal    LC0-"L00000000001$pb"(%ebx), %eax // load the effective address of the format string into the eax register
    movl    %eax, (%esp)      // Push the address of the format string onto the stack
    call    L_printf$stub     // call printf
    leal    -12(%ebp), %eax  // make eax point to i
    incl    (%eax)           // increment i
L5:
    cmpl    $41, -12(%ebp)   // compare i to 41 
    jle L6               // jump to L6 if less than or equal to 41
    movl    $0, %eax
    addl    $36, %esp
    popl    %ebx
    leave
    ret
    .section __IMPORT,__jump_table,symbol_stubs,self_modifying_code+pure_instructions,5
L_printf$stub:
    .indirect_symbol _printf
    hlt ; hlt ; hlt ; hlt ; hlt
    .subsections_via_symbols
13
bit-twiddler

パフォーマンスについて心配する必要はありません。たとえば、97%の時間です。時期尚早の最適化はすべての悪の根です。

-ドナルドクヌース

これが私たちの邪魔にならないところで、私たちの選択をしましょうsanely

  • ++iprefix increment、現在の値をインクリメントして結果を生成します
  • i++postfix increment、値をコピーし、現在の値をインクリメントして、コピーを生成します

古い値のコピーが必要な場合を除いて、postfix incrementを使用することは、物事をやり遂げるための遠回りの方法です。

不正確は怠惰に由来します。常に最も直接的な方法で意図を表現する構成を使用してください。将来のメンテナが元の意図を誤解する可能性はほとんどありません。

ここでは(本当に)マイナーですが、コードを読んで本当に困惑していることがあります。意図と実際のエクスプレスが一致しているかどうかを本当に疑問に思っていました。もちろん、数か月後には(または私は)どちらも覚えていません...

だから、それがあなたにとって正しいかどうかは関係ありません。抱きしめる[〜#〜] kiss [〜#〜]。数か月であなたはあなたの古い慣習を避けなければならないでしょう。

12
Matthieu M.

C++では、couldは、オペレーターのオーバーロードが関係している場合、特にテンプレートコードを記述していて、どのイテレーターが渡されるかわからない場合に、パフォーマンスに大きな違いをもたらします。イテレーターXの背後にあるロジック実質的かつ重要である可能性があります。つまり、遅く、コンパイラーによって最適化できません。

しかし、これはCの場合ではありません。Cの場合は、それが些細な型になるだけであり、パフォーマンスの違いはごくわずかで、コンパイラーは簡単に最適化できます。

したがって、ヒント:CまたはC++でプログラムを作成し、質問はどちらか一方に関係し、両方には関係しません。

4
DeadMG

どちらの操作のパフォーマンスも、基盤となるアーキテクチャに大きく依存します。メモリに保存される値をインクリメントする必要があります。これは、フォンノイマンのボトルネックがどちらの場合でも制限要因であることを意味します。

++ iの場合は、

Fetch i from memory 
Increment i
Store i back to memory
Use i

I ++の場合は、

Fetch i from memory
Use i
Increment i
Store i back to memory

++および-演算子は、その原点をPDP-11命令セットまで追跡します。 PDP-11は、レジスタに対して自動ポストインクリメントを実行できます。また、レジスターに含まれる実効アドレスに対して自動事前減分を実行することもできます。どちらの場合でも、問題の変数が「レジスタ」変数である場合にのみ、コンパイラはこれらのマシンレベルの操作を利用できます。

2
bit-twiddler

何かが遅いかどうかを知りたい場合は、テストしてください。 BigIntegerまたは同等のものを取り、両方のイディオムを使用して同様のforループに固定し、ループの内側が最適化されないようにして、両方の時間を計ります。

この記事を読んでも、3つの理由から、あまり説得力がありません。 1つ目は、コンパイラーは、決して使用されないオブジェクトの作成に関して最適化できる必要があります。二、i++コンセプトはループの数値の慣用的なものであるため、実際に影響を受けることがわかるケースはに限定されます。 3つ目は、理論的な議論であり、それを裏付ける数値はありません。

特に理由1に基づいて、実際にタイミングを実行すると、それらは互いに隣り合うことになると思います。

2
jprete