web-dev-qa-db-ja.com

Cでの効率のための「静的定数」と「#define」

私は最近、#definestatic constの違いは[〜#〜] c [〜#〜]であり、なぜ2つのメソッドが存在するのか疑問に思っていました同じこと。私はここで同様の質問をした人を見つけました:

多くの人がベストプラクティスと慣習について話しているだけでなく、ポインタを定数に渡す必要があるなど、実際にstatic constを使用しても#defineを使用できない理由など、実際的な理由を説明しています。しかし、私は2つの効率の比較について誰かが話すのをまだ見つけていません。

[〜#〜] c [〜#〜]プリプロセッサについて私が理解していることから、次のようなステートメントがある場合:

#define CONSTANT 6

このように使用できる定数値を作成します

char[CONSTANT]は、実際にコンパイルされる前に、このステートメントchar[6]に実際に置き換えられます。

これは、static const constant = 6;を使用するよりも効率的であるように思われます。これは、#defineよりも手荷物が多いと想定してスタックに存在する定数と呼ばれる変数を作成するためです。プリプロセッサ#defineまたはstatic constステートメントを使用することを選択できる状況で定数を必要とすると仮定します。明確な理由がないため、どちらを選択するのがより効率的ですか?そして、私はこれを自分でどの程度正確にテストしますか?

24
user3282276

次の2つのテストファイルを検討してください。

Test1.c:静的const fooを使用します。

// Test1.c uses static const..

#include <stdio.h>

static const foo = 6;

int main() {
    printf("%d", foo);
    return 0;
}

Test2.c:マクロを使用します。

// Test2.c uses macro..

#include <stdio.h>

#define foo 6

int main() {
    printf("%d", foo);
    return 0;
}

gcc -O0(デフォルト)を使用する場合の対応するアセンブリの同等性は次のとおりです。

Test1.cのアセンブリ:

  0000000000000000 <main>:
   0:   55                      Push   rbp
   1:   48 89 e5                mov    rbp,rsp
   4:   48 83 ec 20             sub    rsp,0x20
   8:   e8 00 00 00 00          call   d <main+0xd>
   d:   b8 06 00 00 00          mov    eax,0x6
  12:   89 c2                   mov    edx,eax
  14:   48 8d 0d 04 00 00 00    lea    rcx,[rip+0x4]        # 1f <main+0x1f>
  1b:   e8 00 00 00 00          call   20 <main+0x20>
  20:   b8 00 00 00 00          mov    eax,0x0
  25:   48 83 c4 20             add    rsp,0x20
  29:   5d                      pop    rbp
  2a:   c3                      ret
  2b:   90                      nop

Test2.cのアセンブリ:

  0000000000000000 <main>:
   0:   55                      Push   rbp
   1:   48 89 e5                mov    rbp,rsp
   4:   48 83 ec 20             sub    rsp,0x20
   8:   e8 00 00 00 00          call   d <main+0xd>
   d:   ba 06 00 00 00          mov    edx,0x6
  12:   48 8d 0d 00 00 00 00    lea    rcx,[rip+0x0]        # 19 <main+0x19>
  19:   e8 00 00 00 00          call   1e <main+0x1e>
  1e:   b8 00 00 00 00          mov    eax,0x0
  23:   48 83 c4 20             add    rsp,0x20
  27:   5d                      pop    rbp
  28:   c3                      ret
  29:   90                      nop

どちらの場合も、外部メモリを使用していません。ただし、#definefooを値で置き換え、static constは命令であるため、次の命令への命令ポインタをインクリメントし、値を格納するために1つの追加レジスタを使用するという違いがあります。 。

これにより、静的定数よりマクロの方が優れていると言えますが、その差は最小です。

編集:-O3コンパイルオプションを使用する場合(最適化時)、test1.cとtest2.cの両方が同じように評価されます。

0000000000000000 <main>:
   0:   48 83 ec 28             sub    rsp,0x28
   4:   e8 00 00 00 00          call   9 <main+0x9>
   9:   48 8d 0d 00 00 00 00    lea    rcx,[rip+0x0]        # 10 <main+0x10>
  10:   ba 06 00 00 00          mov    edx,0x6
  15:   e8 00 00 00 00          call   1a <main+0x1a>
  1a:   31 c0                   xor    eax,eax
  1c:   48 83 c4 28             add    rsp,0x28
  20:   c3                      ret
  21:   90                      nop

したがって、gccは、最適化時にstatic const#defineの両方を同じものとして扱います。

36
Venkatesh

簡単な最適化の質問をテストする簡単な方法は、 godbolt を使用することです。

特定の問題については、最新の最適化コンパイラは両方のケースで同じコードを生成でき、実際にはそれらを定数に最適化するだけです。これは次のプログラムで確認できます(実際に見る):

#include <stdio.h>

#define CONSTANT 6
static const int  constant = 6;

void func()
{
  printf( "%d\n", constant ) ;
  printf( "%d\n", CONSTANT ) ;
}

どちらの場合も、どちらのアクセスも次のように減少します。

movl    $6, %esi    #,
4
Shafik Yaghmour

定数の定義が翻訳から見える場合、コンパイラーはそれを最適化として利用することができます。

これにより、スタックと呼ばれる定数と呼ばれる変数が作成され、#defineよりも手荷物が増えると思います。

複数の場所に「住む」ことができます。コンパイラーは、静的またはスタックのストレージを必要とせずに、参照されている場所で定数を確実に置き換えることができます。

プリプロセッサ#defineまたは静的constステートメントのどちらを使用するかを選択できる状況で定数を必要とすると仮定した場合、明確な理由がありません。

コンパイラとアーキテクチャに依存します。 _#define_には大きなアドバンテージがあると信じている人もいるようです。そうではありません。明らかなケースは、複雑な評価または関数呼び出し(sin(4.8)など)です。ループ内で使用される定数を検討してください。適切にスコープされた定数は一度評価されます。defineは反復ごとに評価できます。

そして、私はこれを自分でどの程度正確にテストしますか?

使用する各コンパイラによって生成されたアセンブリを読み取り、測定します。

経験則が必要な場合は、「_#define_がシナリオで測定可能な改善を提供しない限り、定数を使用する」と言います。

これについては、GCCのドキュメントに良い記事がありました。多分誰かがそれが正確にどこにあったか覚えています。

3
justin

_static const_変数はスタックに作成されません(少なくとも作成しないでください)。それらのスペースは、プログラムのロード時に確保されるため、作成に関連する実行時のペナルティはありません。

mayには、初期化に関連する実行時のペナルティがあります。私が使用しているgccのバージョンは、コンパイル時に定数を初期化しますが、その動作がどれほど一般的であるかはわかりません。このような実行時のペナルティがある場合、それはプログラムの起動時に一度だけ発生します。

それを超えて、静的なconst修飾オブジェクトとリテラルの実行時パフォーマンスの違い1 (これはマクロが最終的に展開されるものです)、リテラルのタイプと関連する操作に応じて、存在しないことは無視できるはずです。

愚かな例(gcc version 4.1.2 20070115 (SUSE Linux)):

_#include <stdio.h>

#define FOO_MACRO 5

static const int foo_const = 5;

int main( void )
{
  printf( "sizeof FOO_MACRO = %zu\n", sizeof FOO_MACRO );
  printf( "sizeof foo_const = %zu\n", sizeof foo_const );
  printf( "      &foo_const = %p\n",  ( void * ) &foo_const );

  printf( "FOO_MACRO = %d\n", FOO_MACRO );
  printf( "foo_const = %d\n", foo_const );

  return 0;
}
_

出力:

_sizeof FOO_MACRO = 4
sizeof foo_const = 4
      &foo_const = 0x400660
FOO_MACRO = 5
foo_const = 5
_

_foo_const_のアドレスは、バイナリの_.rodata_セクションにあります。

_[fbgo448@n9dvap997]~/prototypes/static: objdump -s -j .rodata static

static:     file format elf64-x86-64

Contents of section .rodata:
 40065c 01000200 05000000 73697a65 6f662046  ........sizeof F
                 ^^^^^^^^
 40066c 4f4f5f4d 4143524f 203d2025 7a750a00  OO_MACRO = %zu..
 40067c 73697a65 6f662066 6f6f5f63 6f6e7374  sizeof foo_const
 40068c 203d2025 7a750a00 20202020 20202666   = %zu..      &f
 40069c 6f6f5f63 6f6e7374 203d2025 700a0046  oo_const = %p..F
 4006ac 4f4f5f4d 4143524f 203d2025 640a0066  OO_MACRO = %d..f
 4006bc 6f6f5f63 6f6e7374 203d2025 640a00    oo_const = %d..
_

オブジェクトはすでに5に初期化されているため、実行時の初期化ペナルティはありません。

printfステートメントでは、_foo_const_の値を_%esi_にロードするための命令には、リテラル値_0x5_をロードするためのバイトよりも1バイト多く必要で、命令には_%rip_レジスタを効果的に逆参照するには:

_400538:       be 05 00 00 00          mov    $0x5,%esi
              ^^^^^^^^^^^^^^
40053d:       bf ab 06 40 00          mov    $0x4006ab,%edi
400542:       b8 00 00 00 00          mov    $0x0,%eax
400547:       e8 e4 fe ff ff          callq  400430 <printf@plt>
40054c:       8b 35 0e 01 00 00       mov    270(%rip),%esi        # 400660 <foo_const>
              ^^^^^^^^^^^^^^^^^
400552:       bf bb 06 40 00          mov    $0x4006bb,%edi
400557:       b8 00 00 00 00          mov    $0x0,%eax
40055c:       e8 cf fe ff ff          callq  400430 <printf@plt>
_

これは測定可能なランタイムパフォーマンスの違いにつながりますか?たぶん、正しい状況で。タイトなループでCPUにバインドされた何かを数十万回実行している場合、はい、_static const_変数に対してマクロ(リテラルに解決される)を使用するとmayかなり高速になります。これがプログラムの存続期間中に一度発生するものである場合、その差は小さすぎて測定できず、_static const_変数に対してマクロを使用する説得力のある理由はありません。

いつものように、正確性と保守性はパフォーマンスよりも重要です2。マクロの代わりに_static const_を使用すると、間違いを犯す可能性が低くなります。次のシナリオを検討してください。

_#define FOO 1+2
...
x = FOO * 3;
_

expect、そしてgetはどのような答えになりますか?それと比較して

_static const int foo = 1+2;
...
x = foo * 3;
_

はい、かっこ-_(1 + 2)_を使用してマクロのケースを修正できます。ポイントは、_static const_オブジェクトを使用する場合、このシナリオは問題ではありません。足で自分を撃つ方法が1つ少なくなります。


1.今のところ、私は単純なスカラーリテラル(整数または浮動小数点数)についてのみ話しているのであり、複合リテラルについては話していません。彼らの行動を調査していません。

2。それがあなたに間違った答えを与えたり、間違ったことをしたりするなら、それはあなたのコードがどれほど速くても問題ではありません。コードの動作を理解できないため、コードを修正またはアップグレードできない場合でも、コードの速度は関係ありません。不正な入力の最初のヒントでコードが停止する場合、コードの速度は問題ではありません。マルウェアへの扉が開かれたとしても、コードの速度は問題ではありません。
1
John Bode