Which is faster/preferred: memset or for loop to zero out an array of doubles?

Viewed 40803
double d[10];
int length = 10;

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

//or

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

Memset will always be faster, if debug mode or a low level of optimization is used. At higher levels of optimization, it will still be equivalent to std::fill or std::fill_n. For example, for the following code under Google Benchmark: (Test setup: 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;
}

Gives the following results in release mode for 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

And the following results in debug mode (-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

While at -O3 or with clang at -O2, the following is obtained:

-----------------------------------------------------
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: use memset unless told you absolutely have to use std::fill or a for-loop, at least for POD types which are not non-IEEE-754 floating-points. There are no strong reasons not to.

(note: the for loops counting the array contents are necessary for clang not to optimize away the google benchmark loops entirely (it will detect they're not used otherwise))

One way of answering this question is to quickly run the code through Compiler Explorer: If you check this link, you'll see assembly for the following code:

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';
    }
}

The answer (at least for clang) is that with optimization levels -O0 and -O1, the assembly is different and std::fill will be slower because the use of the iterators is not optimized out. For -O2 and higher, do_memset and do_fill produce the same assembly. The loop ends up calling memset on every item in the array even with -O3.

Assuming release builds tend to run -O2 or higher, there are no performance considerations and I'd recommend using std::fill when it's available, and memset for C.

Related