将 Cint 数组重置为零: 最快的方法?

假设我们有一个 T myarray[100],其中包含 T = int、 unsignedint、 long long int 或 unsignedlong long int,那么什么是将其所有内容重置为零的最快方法(不仅用于初始化,而且用于在我的程序中多次重置内容) ?也许和我一起?

Same question for a dynamic array like T *myarray = new T[100].

248128 次浏览

memset(从 <string.h>)可能是最快的标准方法,因为它通常是一个例程直接写在汇编和手工优化。

memset(myarray, 0, sizeof(myarray)); // for automatically-allocated arrays
memset(myarray, 0, N*sizeof(*myarray)); // for heap-allocated arrays, where N is the number of elements

顺便说一下,在 C + + 中,惯用的方法是使用 std::fill(来自 <algorithm>) :

std::fill(myarray, myarray+N, 0);

may被自动优化为 memset; 我很确定它的工作速度将与 intmemset一样快,而如果优化器不够聪明,它可能对较小的类型表现稍差。尽管如此,如果有疑问,侧写。

来自 memset():

memset(myarray, 0, sizeof(myarray));

如果在编译时知道 myarray的大小,则可以使用 sizeof(myarray)。否则,如果您使用的是动态大小的数组,比如通过 mallocnew获得的数组,则需要跟踪长度。

对于静态声明,我认为您可以使用:

T myarray[100] = {0};

对于动态声明,我建议使用同样的方法: memset

您可以使用 memset,但这仅仅是因为我们选择的类型仅限于整数类型。

一般情况下,在 C 语言中实现宏是有意义的

#define ZERO_ANY(T, a, n) do{\
T *a_ = (a);\
size_t n_ = (n);\
for (; n_ > 0; --n_, ++a_)\
*a_ = (T) { 0 };\
} while (0)

This will give you C++-like functionality that will let you to "reset to zeros" an array of objects of any type without having to resort to hacks like memset. Basically, this is a C analog of C++ function template, except that you have to specify the type argument explicitly.

On top of that you can build a "template" for non-decayed arrays

#define ARRAY_SIZE(a) (sizeof (a) / sizeof *(a))
#define ZERO_ANY_A(T, a) ZERO_ANY(T, (a), ARRAY_SIZE(a))

在您的示例中,它应用为

int a[100];


ZERO_ANY(int, a, 100);
// or
ZERO_ANY_A(int, a);

It is also worth noting that specifically for objects of scalar types one can implement a type-independent macro

#define ZERO(a, n) do{\
size_t i_ = 0, n_ = (n);\
for (; i_ < n_; ++i_)\
(a)[i_] = 0;\
} while (0)

还有

#define ZERO_A(a) ZERO((a), ARRAY_SIZE(a))

把上面的例子变成

 int a[100];


ZERO(a, 100);
// or
ZERO_A(a);

在 C + + 中只需要 zero(myarray);

只要把这个添加到标题:

template<typename T, size_t SIZE> inline void zero(T(&arr)[SIZE]){
memset(arr, 0, SIZE*sizeof(T));
}

这个问题虽然相当古老,但是需要一些基准,因为它要求的不是最惯用的方法,或者可以用最少的行编写的方法,而是 最快方法。没有经过实际测试就回答这个问题是很愚蠢的。因此,我比较了四种解决方案,memset 与 std: : fill 与 AnT 的答案的零,以及我使用 AVX 内部特性制作的解决方案。

请注意,这个解决方案不是通用的,它只适用于32或64位的数据。如果此代码正在执行不正确的操作,请注释。

#include<immintrin.h>
#define intrin_ZERO(a,n){\
size_t x = 0;\
const size_t inc = 32 / sizeof(*(a));/*size of 256 bit register over size of variable*/\
for (;x < n-inc;x+=inc)\
_mm256_storeu_ps((float *)((a)+x),_mm256_setzero_ps());\
if(4 == sizeof(*(a))){\
switch(n-x){\
case 3:\
(a)[x] = 0;x++;\
case 2:\
_mm_storeu_ps((float *)((a)+x),_mm_setzero_ps());break;\
case 1:\
(a)[x] = 0;\
break;\
case 0:\
break;\
};\
}\
else if(8 == sizeof(*(a))){\
switch(n-x){\
case 7:\
(a)[x] = 0;x++;\
case 6:\
(a)[x] = 0;x++;\
case 5:\
(a)[x] = 0;x++;\
case 4:\
_mm_storeu_ps((float *)((a)+x),_mm_setzero_ps());break;\
case 3:\
(a)[x] = 0;x++;\
case 2:\
((long long *)(a))[x] = 0;break;\
case 1:\
(a)[x] = 0;\
break;\
case 0:\
break;\
};\
}\
}

我不会声称这是最快的方法,因为我不是一个低水平的优化专家。相反,它是一个正确的体系结构相关实现的例子,比 memset 更快。

现在,看看结果。我计算了大小为100 int 和长长的数组(包括静态和动态分配的数组)的性能,但是除了 msvc,它对静态数组做了一个死码删除,结果是非常可比的,所以我将只展示动态数组的性能。使用 Time.h 的低精度时钟函数,100万次迭代的时间标记为 ms。

clang 3.8 (Using the clang-cl frontend, optimization flags= /OX /arch:AVX /Oi /Ot)

int:
memset:      99
fill:        97
ZERO:        98
intrin_ZERO: 90


long long:
memset:      285
fill:        286
ZERO:        285
intrin_ZERO: 188

Gcc 5.1.0(优化标志:-O3-March = national-mtune = national-mavx) :

int:
memset:      268
fill:        268
ZERO:        268
intrin_ZERO: 91
long long:
memset:      402
fill:        399
ZERO:        400
intrin_ZERO: 185

Msvc 2015(优化标志:/OX/arch: AVX/Ai/OT) :

int
memset:      196
fill:        613
ZERO:        221
intrin_ZERO: 95
long long:
memset:      273
fill:        559
ZERO:        376
intrin_ZERO: 188

这里发生了很多有趣的事情: llvm 扼杀了 gcc,这是 MSVC 典型的不稳定优化(它在静态数组上做了一个令人印象深刻的死码删除,然后在填充方面表现糟糕)。尽管我的实现要快得多,但这可能仅仅是因为它认识到比特清除操作比其他任何设置操作的开销都要小得多。

Clang 的实现值得更多地关注,因为它的速度明显更快。一些额外的测试表明,它的 memset 实际上专门用于零——400字节数组的非零 memset 要慢得多(约220ms) ,并且与 gcc 的 memset 相当。但是,使用800字节数组的非零 memset 没有速度差异,这可能就是为什么在这种情况下,它们的 memset 的性能比我的实现更差——这种特殊化只适用于小数组,截止值大约在800字节左右。还要注意,gcc 的‘ fill’和‘ ZERO’并没有对 memset 进行优化(查看生成的代码) ,gcc 只是生成具有相同性能特征的代码。

结论: memset 并没有像人们假装的那样为这个任务进行优化(否则 gcc 和 msvc 以及 llvm 的 memset 的性能会相同)。如果性能很重要,那么 memset 不应该是最终的解决方案,特别是对于这些笨拙的中等大小的数组,因为它不是专门用于位清除的,而且它也没有比编译器自己能够做的更好的手工优化。

下面是我使用的函数:

template<typename T>
static void setValue(T arr[], size_t length, const T& val)
{
std::fill(arr, arr + length, val);
}


template<typename T, size_t N>
static void setValue(T (&arr)[N], const T& val)
{
std::fill(arr, arr + N, val);
}

You can call it like this:

//fixed arrays
int a[10];
setValue(a, 0);


//dynamic arrays
int *d = new int[length];
setValue(d, length, 0);

以上是比使用 memset 更多的 C + + 11方式。如果使用指定大小的动态数组,还会出现编译时错误。