web-dev-qa-db-ja.com

constパラメータを使用して関数に引数を渡す:高速ですか?

たとえば、次のことを考慮してください。

int sum(int a, int b)
{
    return a + b;
}

vs.

int sum(const int a, const int b)
{
    return a + b;
}

一般的に、2番目のアプローチの方が高速ですか?

Cの関数パラメーターがコピーされて関数に送信されるため、関数内での変更が元の値に影響を与えません。上記の2番目のsumでは、コンパイラはabが関数内で変更されないことを確実に知っているため、最初に値をコピーせずに元の値を渡すことができます。そのため、2番目のsumは最初のものよりも速いと思います。しかし、私は本当に知りません。上記のsumの特定の単純な例では、違いがあっても最小限に抑える必要があります。

編集:sumの例は、私のポイントを説明するためのものです。この特定の例では、大きな違いがあるとは思いません。しかし、より複雑な状況では、関数パラメーター内のconst修飾子をコンパイラーが利用して、関数を高速化できるかどうか疑問に思います。コンパイラーがパラメーターが関数内で変更されたかどうかを常に判別できるとは思えません(したがって、以下の2番目の質問)。したがって、const修飾子が見つかった場合、const修飾子がない場合とは異なる動作をすることを期待します。

質問:一般に、引数がconstである場合、そうでない場合よりも関数の方が高速になりますか?

質問2:一般に、Cコンパイラは(理論的には)関数のパラメーターが関数内で変更されたかどうかを常に判断できますか?

21
becko

短い答え:いいえ

長い答え、いいえ、証明あり

私はこのテストを数回実行しましたが、clangでコンパイルしたMacBookプロでリアルタイムの違いは見られませんでした。

int add(int a, int b)
{
    return a + b;
}

const int cadd(const int a, const int b)
{
    return a + b;
}

int main (int argc, char * argv[])
{
#define ITERS 1000000000

    clock_t start = clock();
    int j = 0;
    for (int i = 0; i < ITERS; i++)
    {
        j += add(i, i + 1);
    }

    printf("add took %li ticks\n", clock() - start);

    start = clock();
    j = 0;
    for (int i = 0; i < ITERS; i++)
    {
        j += cadd(i, i + 1);
    }

    printf("cadd took %li ticks\n", clock() - start);

    return 0;
}

出力

 addは4875711ティックを取得しました
 caddは4885519ティックを取得しました

ただし、clockはタイミング関数の中で最も正確ではなく、実行中の他のプログラムの影響を受ける可能性があるため、これらの時間は実際には細かく調整する必要があります。

だから、これが生成された比較されたアセンブリです:

_add:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register %rbp
    movl    %edi, -4(%rbp)
    movl    %esi, -8(%rbp)
    movl    -4(%rbp), %esi
    addl    -8(%rbp), %esi
    movl    %esi, %eax
    popq    %rbp
    ret

_cadd:                                 
    .cfi_startproc    
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register %rbp
    movl    %edi, -4(%rbp)
    movl    %esi, -8(%rbp)
    movl    -4(%rbp), %esi
    addl    -8(%rbp), %esi
    movl    %esi, %eax
    popq    %rb

ご覧のとおり、2つの間にNo差異があります。引数をconstとして渡すことは呼び出し側へのヒントにすぎず、引数は変更されません。また、上記のような単純なシナリオでは、異なるアセンブリがコンパイルされることはありません。

16

答えは、コンパイラ、最適化レベル、およびコンパイラが関数をインライン展開するかどうかによって異なります。これらのことに興味がある場合は、コンパイラによって生成された実際のアセンブリを見て、それを見つけるのは簡単です。

9
David Grayson

パーティーに遅れましたが、コンパイラはconstとして定義された変数を読み取り専用のメモリセグメント/ブロックに配置できるため、アドレスへの書き込みが試みられた場合、ポインタへのポインタを介して、メモリへの書き込みがトリガーされます。実行時の例外。

-ジェイミー

0
Jamey Kirby

いいえ、どちらも同じ速度でなければなりません。あなたの理由で、元の値をsum関数に渡すと仮定します。sum関数からの一部のコードは、たとえば別のスレッドなど、元の値を変更します。

一般に、constは引数のパフォーマンスに影響を与えません。 constがローカル変数またはグローバル変数の場合、一部の計算をconstであるかのようにコンパイル時間に移動できるため、パフォーマンスに影響します。

0
RolandXu