这个问题虽然很老,但需要一些基准,因为它要求的不是最惯用的方式,也不是可以用最少的行数编写的方式,而是最快的方式。在没有实际测试的情况下回答这个问题是愚蠢的。所以我比较了四种解决方案,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 和 long long 数组的性能,包括静态分配和动态分配,但除了 msvc 对静态数组进行了死代码消除之外,结果非常具有可比性,因此我将仅展示动态数组的性能。使用 time.h 的低精度时钟功能,100 万次迭代的时间标记为 ms。
clang 3.8(使用 clang-cl 前端,优化标志= /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=native -mtune=native -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 /Oi /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 相当。但是,具有 800 字节数组的非零 memsetting 没有速度差异,这可能就是为什么在这种情况下,他们的 memset 的性能比我的实现差 - 专门化仅适用于小型数组,并且截止值正好在 800 字节左右。另请注意,gcc 'fill' 和 'ZERO' 并未针对 memset 进行优化(查看生成的代码),gcc 只是生成具有相同性能特征的代码。
结论:memset 并没有像人们想象的那样真正针对这个任务进行优化(否则 gcc 和 msvc 以及 llvm 的 memset 将具有相同的性能)。如果性能很重要,那么 memset 不应该是最终解决方案,尤其是对于这些笨拙的中等大小的数组,因为它不是专门用于位清除的,而且它没有比编译器自己做的更好的手动优化。