web-dev-qa-db-ja.com

memset()はCのforループよりも効率的ですか?

forループよりもmemsetの方が効率的です。だから私が持っているなら

char x[500];
memset(x,0,sizeof(x));

または

char x[500];
for(int i = 0 ; i < 500 ; i ++) x[i] = 0;

どちらがより効率的で、なぜですか?ハードウェアにブロックレベルの初期化を行うための特別な命令がありますか。

36
David

確かに、memsetはそのループよりもはるかに高速です。一度に1つの文字を処理する方法に注意してください。ただし、これらの関数は非常に最適化されているため、MMXとSSE手順。

これらの最適化の典型的な例は、通常は気付かれませんが、GNU Cライブラリstrlen関数です。少なくともO(n)パフォーマンスですが、実際にはO(n/4)またはO(n/8)アーキテクチャによって異なります(yes 、私は知っています、大きなO()は同じになりますが、実際にはeighthの時間を取得します)。どのように?トリッキーですが、うまく: strlen

29
Diego Sevilla

さて、VS 2010で生成されたアセンブリコード、完全な最適化を見てみましょう。

char x[500];
char y[500];
int i;      

memset(x, 0, sizeof(x) );   
  003A1014  Push        1F4h  
  003A1019  lea         eax,[ebp-1F8h]  
  003A101F  Push        0  
  003A1021  Push        eax  
  003A1022  call        memset (3A1844h)  

そしてあなたのループ...

char x[500];
char y[500];
int i;    

for( i = 0; i < 500; ++i )
{
    x[i] = 0;

      00E81014  Push        1F4h  
      00E81019  lea         eax,[ebp-1F8h]  
      00E8101F  Push        0  
      00E81021  Push        eax  
      00E81022  call        memset (0E81844h)  

      /* note that this is *replacing* the loop, 
         not being called once for each iteration. */
}

したがって、このコンパイラーでは、生成されるコードはまったく同じです。 memsetは高速で、コンパイラーはmemsetを1回呼び出すのと同じことを実行していることを認識するのに十分なほどスマートなので、自動的に実行されます。

コンパイラーが実際にループをそのままにした場合、一度に複数のバイトサイズのブロックを設定できるため、遅くなる可能性があります(つまり、ループを少なくとも少し展開することができます。memset少なくともループなどの単純な実装と同じくらい高速になります。デバッグビルドで試してみると、ループが置き換えられていないことがわかります。

とはいえ、コンパイラーが何をするかによって異なります。分解を確認することは、何が起こっているのかを正確に知るための良い方法です。

33
Ed S.

それは本当にコンパイラとライブラリに依存します。古いコンパイラまたは単純なコンパイラの場合、memsetはライブラリに実装される可能性があり、カスタムループよりもパフォーマンスが良くありません。

使用に値するほとんどすべてのコンパイラーにとって、memsetは組み込み関数であり、コンパイラーは最適化されたインラインコードを生成します。

他のものはプロファイリングと比較を提案しましたが、私は気にしないでしょう。 memsetを使用してください。コードはシンプルで理解しやすいです。ベンチマークでコードのこの部分がパフォーマンスのホットスポットであるとわかるまで、心配する必要はありません。

12
Michael

答えは「状況次第」です。 memsetより効率的な場合があります。そうでない場合は、内部でforループを使用できます。 memsetの効率が低下するケースは考えられません。この場合、より効率的なforループになる可能性があります。ループは500回繰り返され、毎回配列のバイトに相当する値を0に設定します。 64ビットマシンでは、一度に8バイト(長い)を設定してループすることができます。これは、ほぼ8倍速く、最後に残りの4バイト(500%8)を処理します。

編集:

実際、これはglibcでmemsetが行うことです。

http://repo.or.cz/w/glibc.git/blob/HEAD:/string/memset.c

Michaelが指摘したように、特定の場合(コンパイル時に配列の長さがわかっている場合)、Cコンパイラはmemsetをインライン化して、関数呼び出しのオーバーヘッドを取り除くことができます。 Glibcには、AMD64などのほとんどの主要なプラットフォーム向けに、アセンブリ最適化バージョンのmemsetもあります。

http://repo.or.cz/w/glibc.git/blob/HEAD:/sysdeps/x86_64/memset.S

8
Bobby Powers

優れたコンパイラはforループを認識し、最適なインラインシーケンスまたはmemsetの呼び出しに置き換えます。また、バッファーサイズが小さい場合、memsetを最適なインラインシーケンスに置き換えます。

実際には、最適化コンパイラを使用すると、生成されたコード(したがってパフォーマンス)は同じになります。

3
Stephen Canon

上記に同意します。場合によります。しかし、確かにmemsetはforループよりも高速か、同等です。環境がわからない場合やテストが面倒な場合は、安全な方法でmemsetを使用してください。

2
beetree