C++ 哪一种更快/更可取:memset还是for-loop将double数组归零?

C++ 哪一种更快/更可取:memset还是for-loop将double数组归零?,c++,c,performance,optimization,C++,C,Performance,Optimization,memset(d,10,0)是错误的,因为它只为空10个字节。 更喜欢std::fill,因为它的意图是最清楚的。如果你真的在意,你应该尝试测量。但是,最可移植的方法是使用std::fill(): 可能会更快。就像他们说的,你也可以 memset(d,0,10*sizeof(*d)); 但这很可能是一种更漂亮的循环方式。如果您真正关心性能,请不要忘记比较一个经过适当优化的for循环 如果数组足够长,并且前缀--i而不是后缀--(尽管大多数编译器可能会自动更正),则使用Duff设备的一些变体 尽

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

如果你真的在意,你应该尝试测量。但是,最可移植的方法是使用std::fill():

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

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

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

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

如果数组足够长,并且前缀--i而不是后缀--(尽管大多数编译器可能会自动更正),则使用Duff设备的一些变体

尽管我怀疑这是否是最有价值的优化。这真的是系统的瓶颈吗?

我想你是说

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


就个人而言,我会选择其中一种,但我认为
std::fill()
可能更好。

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

for (int i = length; --i >= 0; ) d[i] = 0;
memset可以更快,因为它是在汇编程序中编写的,而
std::fill
是一个模板函数,它只是在内部执行循环


<>但是对于类型安全和更可读代码<强>我建议使用代码> STD::FILL()/CUT>——这是C++的做事方式,如果代码中的某个地方需要性能优化,请考虑<代码> MyStuts<代码>。< /P> < P>如果您不需要使用STL…< /P>
memset(d, 0, sizeof(double)*length);

ZeroMemory至少清楚地表明了目的。

作为所有提议的替代方案,我可以建议您不要在启动时将数组设置为全零。相反,仅当您首次访问特定单元格中的值时,才将值设置为零。这将避开您的问题,并且可能会更快。

一般来说,memset会更快,请确保您的长度正确,显然您的示例没有(m)分配或定义double数组。现在,如果它真的要结束只有少数双打,那么循环可能会更快。但是当到达填充循环阴影的点时,memset通常会使用较大的、有时是对齐的块来最大化速度


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

尝试一下这个,如果只是为了酷xD的话

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

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

{
    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);
    }
}

根据IEEE-754,正零的位表示都是零位,要求符合IEEE-754没有什么错。(如果需要将数组归零以重用它,请选择上述解决方案之一)。

根据这篇维基百科文章,所有0的位模式确实会正确地将double初始化为0.0。不幸的是,您的memset代码不能做到这一点

以下是您应该使用的代码:

calloc(length, sizeof(double))
作为更完整包的一部分

memset(d, 0, length * sizeof(double));
当然,这会删除您应该对malloc的返回值执行的错误检查
sizeof(d[0])
略优于
sizeof(double)
,因为它对d类型的更改非常健壮


此外,如果您使用
calloc(长度,大小(d[0])
它将为您清除内存,并且不再需要后续的memset。我没有在示例中使用它,因为这样看来,您的问题似乎无法得到回答。

该示例将不起作用,因为您必须为数组分配内存。您可以在堆栈或堆上执行此操作

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

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

之后不需要任何内存集。

假设循环长度是一个整数常量表达式,那么一个好的优化器很可能会同时识别for循环和内存集(0)。结果将是生成的程序集本质上是相等的。可能寄存器的选择不同,或者设置不同。但是每一倍的边际成本实际上应该是相同的。

如果使用调试模式或低水平的优化,Memset将总是更快。在更高级别的优化中,它仍然相当于std::fill或std::fill\n。 例如,对于Google Benchmark下的以下代码: (测试设置:xubuntu 18,GCC 7.3,Clang 6.0)

并且在调试模式(-O0)下会出现以下结果: 在-O3下或在-O2下发出叮当声时,可获得以下结果:

-----------------------------------------------------
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
TLDR:除非被告知必须使用std::fill或for循环,否则请使用memset,至少对于非IEEE-754浮点的POD类型。没有强有力的理由不这样做


(注意:计算数组内容的for循环是clang不完全优化google基准测试循环所必需的(它将检测到它们没有被使用))

回答这个问题的一种方法是通过编译器资源管理器快速运行代码:如果选中此项,您将看到以下代码的汇编:

void do_memset(std::array&a){
memset(&a,'q',a.size());
}
空白填充(标准::数组&a){
std::fill(a.begin(),a.end(),'q');
}
void do_循环(std::array&a){
对于(int i=0;i
答案(至少对于
clang
)是,对于优化级别
-O0
-O1
,程序集是不同的,
std::fill
会更慢,因为迭代器的使用没有优化。对于
-O2
及更高版本,
do_memset
do_fill
生成相同的组件。循环最终对数组中的每个项调用
memset
,即使使用
-O3

假设发布版本倾向于运行
-O2
或更高版本,那么就没有性能方面的考虑,我建议使用
std::fill
whe
memset(d, 0, length * sizeof(double));
{
    double *d;
    int length = 10;
    d = malloc(sizeof(d[0]) * length);
    memset(d, 0, length * sizeof(d[0]));
}
double d[50] = {0.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;
}
-----------------------------------------------------
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
-----------------------------------------------------
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
-----------------------------------------------------
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