web-dev-qa-db-ja.com

どちらが速い/好ましいですか:memsetまたはforループでdoubleの配列をゼロにしますか?

double d[10];
int length = 10;

memset(d, length * sizeof(double), 0);

//or

for (int i = length; i--;)
  d[i] = 0.0;
25
vehomzzz

Memsetの場合、これは古いC関数であるため、要素の数ではなく、バイト数を渡す必要があることに注意してください。

_memset(d, 0, sizeof(double)*length);
_

memset canアセンブラで記述されているため高速ですが、_std::fill_は単に内部でループを実行するテンプレート関数です。

しかし、型の安全性とより読みやすいコードのために私はお勧めしますstd::fill()-それは物事を行うc ++の方法であり、パフォーマンスの最適化が必要な場合はmemsetを検討してください。コード内のこの場所。

27
codymanix

あなたが本当に気にかけているなら、あなたは試して測定するべきです。ただし、最も移植性の高い方法は、std :: fill()を使用することです。

std::fill( array, array + numberOfElements, 0.0 );
42
sharptooth

コード内のいくつかのバグと脱落に加えて、memsetの使用は移植性がありません。すべてゼロビットのdoubleが0.0に等しいと想定することはできません。最初にコードを正しくしてから、最適化について心配してください。

6
paul

ループの長さが整数の定数式であると仮定すると、最も可能性の高い結果は、優れたオプティマイザーがforループとmemset(0)の両方を認識することです。その結果、生成されたアセンブリは基本的に等しくなります。おそらく、レジスタの選択や設定が異なる可能性があります。しかし、ダブルあたりの限界費用は実際には同じであるはずです。

5
MSalters
memset(d,0,10*sizeof(*d));

より速くなる可能性があります。彼らが言うようにあなたもできる

std::fill_n(d,10,0.);

しかし、それはおそらくループを行うためのよりきれいな方法です。

calloc(length, sizeof(double))

IEEE-754によると、正のゼロのビット表現はすべてゼロビットであり、IEEE-754準拠を要求することに何の問題もありません。 (アレイを再利用するためにゼロにする必要がある場合は、上記の解決策のいずれかを選択してください)。

3
user57368

IEEE 754-1975 64ビット浮動小数点 に関するこのウィキペディアの記事によると、すべて0のビットパターンは実際にdoubleを0.0に適切に初期化します。残念ながら、memsetコードはそれを行いません。

使用する必要のあるコードは次のとおりです。

_memset(d, 0, length * sizeof(double));
_

より完全なパッケージの一部として...

_{
    double *d;
    int length = 10;
    d = malloc(sizeof(d[0]) * length);
    memset(d, 0, length * sizeof(d[0]));
}
_

もちろん、mallocの戻り値に対して実行する必要があるエラーチェックは削除されます。 sizeof(d[0])sizeof(double)よりもわずかに優れています。これは、dのタイプの変更に対して堅牢であるためです。

また、calloc(length, sizeof(d[0]))を使用すると、メモリがクリアされ、後続のmemsetは不要になります。例では使用しませんでした。質問に答えられないようです。

3
Omnifarious

配列にメモリを割り当てる必要があるため、この例は機能しません。これは、スタックまたはヒープで実行できます。

これはスタックでそれを行う例です:

double d[50] = {0.0};

その後はmemsetは必要ありません。

3
frast

デバッグモードまたは低レベルの最適化が使用されている場合、Memsetは常に高速になります。より高いレベルの最適化でも、std :: fillまたはstd :: fill_nと同等になります。たとえば、Googleベンチマークの下の次のコードの場合:(テストセットアップ:xubuntu 18、GCC 7.3、Clang 6.0)

#include <cstring>
#include <algorithm>
#include <benchmark/benchmark.h>

double total = 0;


static void memory_memset(benchmark::State& state)
{
    int ints[50000];

    for (auto _ : state)
    {
        std::memset(ints, 0, sizeof(int) * 50000);
    }

    for (int counter = 0; counter != 50000; ++counter)
    {
        total += ints[counter];
    }
}


static void memory_filln(benchmark::State& state)
{
    int ints[50000];

    for (auto _ : state)
    {
        std::fill_n(ints, 50000, 0);
    }

    for (int counter = 0; counter != 50000; ++counter)
    {
        total += ints[counter];
    }
}


static void memory_fill(benchmark::State& state)
{
    int ints[50000];

    for (auto _ : state)
    {
        std::fill(std::begin(ints), std::end(ints), 0);
    }

    for (int counter = 0; counter != 50000; ++counter)
    {
        total += ints[counter];
    }
}


// Register the function as a benchmark
BENCHMARK(memory_filln);
BENCHMARK(memory_fill);
BENCHMARK(memory_memset);



int main (int argc, char ** argv)
{
    benchmark::Initialize (&argc, argv);
    benchmark::RunSpecifiedBenchmarks ();
    printf("Total = %f\n", total);
    getchar();
    return 0;
}

GCCのリリースモード(-O2; -march = native)で次の結果が得られます。

-----------------------------------------------------
Benchmark              Time           CPU Iterations
-----------------------------------------------------
memory_filln       16488 ns      16477 ns      42460
memory_fill        16493 ns      16493 ns      42440
memory_memset       8414 ns       8408 ns      83022

また、デバッグモード(-O0)では次のようになります。

-----------------------------------------------------
Benchmark              Time           CPU Iterations
-----------------------------------------------------
memory_filln       87209 ns      87139 ns       8029
memory_fill        94593 ns      94533 ns       7411
memory_memset       8441 ns       8434 ns      82833

-O3の場合、または-O2の場合、次のようになります。

-----------------------------------------------------
Benchmark              Time           CPU Iterations
-----------------------------------------------------
memory_filln        8437 ns       8437 ns      82799
memory_fill         8437 ns       8437 ns      82756
memory_memset       8436 ns       8436 ns      82754

TLDR:少なくともIEEE-754以外の浮動小数点ではないPODタイプの場合は、std :: fillまたはforループを絶対に使用する必要があると言われない限り、memsetを使用してください。そうしない強い理由はありません。

(注:配列の内容をカウントするforループは、clangがGoogleベンチマークループを完全に最適化しないようにするために必要です(他の方法では使用されていないことを検出します))

2
metamorphosis

memset(d、10、0)は、10バイトしかヌルしないため、間違っています。意図が最も明確であるため、std :: fillを優先します。

1
stefaanv

パフォーマンスが本当に気になる場合は、適切に最適化されたforループを比較することを忘れないでください。

配列が十分に長く、接頭辞--iが接尾辞iではない場合のDuffのデバイスのいくつかの変形(ただし、ほとんどのコンパイラはおそらくそれを自動的に修正します)。

これが最適化するのに最も価値のあることかどうか疑問に思いますが。これは本当にシステムのボトルネックですか?

1
Massif

提案されたすべてのものの代わりとして、起動時に配列をすべてゼロに設定しないことをお勧めします。代わりに、特定のセルの値に最初にアクセスするときにのみ、値をゼロに設定します。これはあなたの質問を食い止め、より速くなるかもしれません。

0
P Shved

一般に、memsetははるかに高速になります。長さが正しいことを確認してください。明らかに、例ではdoubleの配列が(m)割り当てられていないか定義されていません。本当にほんの一握りのdoubleで終わる場合は、ループが速くなる可能性があります。しかし、塗りつぶしループが影になるようになると、memsetは通常、速度を最大化するために、より大きく、場合によっては整列されたチャンクを使用します。

いつものように、テストして測定します。 (ただし、この場合はキャッシュに入れられ、測定値が偽物であることが判明する可能性があります)。

0
old_timer

STLを使用しない必要がある場合...

double aValues [10];
ZeroMemory (aValues, sizeof(aValues));

ZeroMemoryは、少なくとも意図を明確にします。

0
Bob Moore