Such an effect happens because of how the data of the int** a
is stored in memory (as per C/C++). This question on StackOverflow has answers with some more details (in particular, a difference between int**
and int[][]
that many users noted in the comments), and how it
looks like an array of arrays - it's just laid out contiguously in memory.
It's worth to mention, that since several malloc
s are used, the whole array might not be stored completely contiguously in memory; however, each individual row – will. Here comes the catch: to take full advantage of the "partial" contiguous layout of the array in memory, you want to access the elements (if possible) in the order they are stored. That makes the code cache-friendly. This question on making the code cache-friendly has many good advices, but here, the memory layout is the important one.
Looking inside the memory of how the array is stored is very useful: this StackOverflow answer illustrates that on a concrete example for a static array. In C/C++, the layout is row-major (as opposed to Matlab and Fortran); therefore, accessing the array row-wise is advantageous:
a[0][0] = 0.;
a[0][1] = 0.;
a[0][2] = 0.;
...
a[0][N-1] = 0.;
a[1][0] = 0.;
a[1][1] = 0.;
a[1][2] = 0.;
...
a[N-1][0] = 0.;
a[N-1][1] = 0.;
...
a[N-1][N-1] = 0.;
where N
is the size of the array (assuming row-size == column-size).
Now, this piece corresponds to the "unwrapped" second implementation:
for(int i = 0; i < 10000; i++){
for(int j = 0; j< 10000; j++){
a[i][j] = 0;
}
}
which is much faster on my machine (gcc -O3
): 1577.997070
ms (for the first variant) vs
45.952000
ms (for the second one). The actual numbers are not accurate and not important, it's the qualitative comparison that matters.
It's important to note, that malloc
s don't have to do (but can) the next allocation contiguously (thanks to @Brian Borcher's comment). Therefore, with your way of allocating the int ** a
array, you are prone to not walk fully contiguously in memory with the aforementioned loop; however, you still are taking much more advantage of the code cache-friendliness with the second implementation.
To make the code even faster, consider:
- allocating the whole array
a
at once with one malloc
of the appropriate size
- treating a matrix as a 1-D array using
a[i+j*numRows]
indexing
- read the best practices of correctly allocating multi-dimensional arrays
- use an allocator that gives you data aligned in memory.
mkl_malloc
is one example; however, there are many other possibilities, and you might already have one available based on the libraries already used in your application. Choose an appropriate alignment.
So, it's the second implementation that is faster.
int a[10000][10000] = malloc(10000*10000*sizeof(int)); memset(&a, 0, 10000*10000*sizeof(int));
$\endgroup$malloc(...);memset(...,0,...);
? You meancalloc()
? $\endgroup$