哪个更快/更首选:memset或for循环将双精度数组归零?

时间:2009-09-03 13:19:07

标签: c++ c performance optimization

double d[10];
int length = 10;

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

//or

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

18 个答案:

答案 0 :(得分:40)

如果你真的在乎你应该尝试和衡量。然而,最便携的方式是使用std :: fill():

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

答案 1 :(得分:20)

请注意,对于memset,您必须传递字节数,而不是元素数,因为这是一个旧的C函数:

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

memset 可以更快,因为它是用汇编语言编写的,而std::fill是一个模板函数,它只是在内部循环。

但是对于类型安全和更易读的代码我建议 std::fill() - 这是c ++的做事方式,如果需要进行性能优化,请考虑memset这个地方在代码中。

答案 2 :(得分:13)

试试这个,如果只是为了酷xD

{
    double *to = d;
    int n=(length+7)/8;
    switch(length%8){
        case 0: do{ *to++ = 0.0;
        case 7:     *to++ = 0.0;
        case 6:     *to++ = 0.0;
        case 5:     *to++ = 0.0;
        case 4:     *to++ = 0.0;
        case 3:     *to++ = 0.0;
        case 2:     *to++ = 0.0;
        case 1:     *to++ = 0.0;
        }while(--n>0);
    }
}

答案 3 :(得分:5)

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

可能会更快。就像他们说你也可以

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

但它很可能是一种更漂亮的循环方式。

答案 4 :(得分:5)

除了代码中的一些错误和遗漏之外,使用memset是不可移植的。您不能假设所有零位的double都等于0.0。首先让你的代码正确,然后担心优化。

答案 5 :(得分:5)

假设循环长度是一个整数常量表达式,最好的结果是良好的优化器将识别for循环和memset(0)。结果是生成的程序集基本相同。也许寄存器的选择可能不同,或者设置不同。但每双的边际成本应该是一样的。

答案 6 :(得分:3)

calloc(length, sizeof(double))

根据IEEE-754,正零的位表示都是零位,并且要求符合IEEE-754标准没有任何问题。 (如果您需要将数组清零以重复使用它,请选择上述解决方案之一)。

答案 7 :(得分:3)

根据维基百科关于IEEE 754-1975 64-bit floating point的文章,所有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。我没有在示例中使用它,因为看起来您的问题似乎无法解答。

答案 8 :(得分:3)

该示例无效,因为您必须为阵列分配内存。您可以在堆栈上或堆上执行此操作。

这是在堆栈上执行此操作的示例:

double d[50] = {0.0};

之后不需要memset。

答案 9 :(得分:2)

memset(d,10,0)错误,因为它只有10个字节。 喜欢std :: fill因为意图最清楚。

答案 10 :(得分:1)

如果您真的关心性能,请不要忘记比较经过适当优化的for循环。

Duff设备的一些变体,如果数组足够长,前缀--i不是后缀i--(尽管大多数编译器可能会自动纠正它。)。

虽然我怀疑这是否是最有价值的优化方法。这真的是系统的瓶颈吗?

答案 11 :(得分:1)

一般来说,memset会更快,确保你的长度合适,显然你的例子没有(m)分配或定义双打数组。现在,如果它真的最终只有少数双打,那么循环可能会变得更快。但是,当填充循环影响少数设置指令时,memset通常会使用较大且有时对齐的块来最大化速度。

像往常一样,测试和测量。 (虽然在这种情况下,你最终会进入缓存,但测量结果可能是假的)。

答案 12 :(得分:1)

如果使用调试模式或低级别的优化,Memset将始终更快。在更高级别的优化中,它仍然等同于std :: fill或std :: fill_n。 例如,对于Google Benchmark下的以下代码: (测试设置: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:使用memset,除非告诉您绝对必须使用std :: fill或for循环,至少对于不是非IEEE-754浮点的POD类型而言。没有充分的理由不这样做。

(注意:计数数组内容的for循环对于clang不能完全优化Google基准测试循环是必不可少的(它将检测到未使用它们))

答案 13 :(得分:0)

如果您不需要使用STL ......

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

ZeroMemory至少使意图明确。

答案 14 :(得分:0)

作为所有提议内容的替代方案,我建议你不要在启动时将数组设置为全零。而是仅在首次访问特定单元格中的值时将值设置为零。这样可以避开你的问题,可能会更快。

答案 15 :(得分:0)

严格来说,std::memset更快,但是当您打开优化功能时,现代编译器很可能仍会使用memset进行此类for循环。

当然,根据上下文的不同,编译器可能会找到更快的选项,但是我认为典型的for循环汇编会很少见。

使用-01优化与clang 9.0组装: https://godbolt.org/z/vvnE8T

答案 16 :(得分:0)

回答此问题的一种方法是通过Compiler Explorer快速运行代码:如果选中此link,则将看到以下代码的汇编:

void do_memset(std::array<char, 1024>& a) {
    memset(&a, 'q', a.size());
}

void do_fill(std::array<char, 1024>& a) {
    std::fill(a.begin(), a.end(), 'q');
}

void do_loop(std::array<char, 1024>& a) {
    for (int i = 0; i < a.size(); ++i) {
        a[i] = 'q';
    }
}

答案(至少对于clang而言)是,在优化级别为-O0-O1的情况下,程序集是不同的,并且std::fill会更慢,因为使用迭代器尚未优化。对于-O2及更高版本,do_memsetdo_fill产生相同的程序集。循环最终在数组中的每个项目上调用memset ,甚至使用-O3

假设发行版通常可以运行-O2或更高版本,则没有性能方面的考虑,我建议在可用的情况下使用std::fill,在C语言中使用memset

答案 17 :(得分:-1)

我认为你的意思是

memset(d, 0, length * sizeof(d[0]))

for (int i = length; --i >= 0; ) d[i] = 0;

就个人而言,我做了其中一个,但我认为std::fill()可能更好。