假设我们有一个T myarray[100]
with T = int, unsigned int, long long int 或 unsigned long long int,将其所有内容重置为零的最快方法是什么(不仅用于初始化,而且在我的程序中多次重置内容) ? 也许与 memset?
对于像T *myarray = new T[100]
.
memset
(from <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
(from <algorithm>
):
std::fill(myarray, myarray+N, 0);
可以自动优化为memset
; 我很确定它会像 s 一样快地工作memset
,int
而如果优化器不够聪明,它对于较小的类型的性能可能会稍差一些。尽管如此,当有疑问时,配置文件。
这个问题虽然很老,但需要一些基准,因为它要求的不是最惯用的方式,也不是可以用最少的行数编写的方式,而是最快的方式。在没有实际测试的情况下回答这个问题是愚蠢的。所以我比较了四种解决方案,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 不应该是最终解决方案,尤其是对于这些笨拙的中等大小的数组,因为它不是专门用于位清除的,而且它没有比编译器自己做的更好的手动优化。
来自memset()
:
memset(myarray, 0, sizeof(myarray));
如果在编译时已知sizeof(myarray)
大小,则可以使用。myarray
否则,如果您使用动态大小的数组,例如通过malloc
or获得new
,您将需要跟踪长度。
您可以使用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)
这将为您提供类似 C++ 的功能,让您可以“重置为零”任何类型的对象数组,而无需求助于memset
. 基本上,这是 C++ 函数模板的 C 模拟,除了您必须显式指定类型参数。
最重要的是,您可以为非衰减数组构建“模板”
#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);
还值得注意的是,专门针对标量类型的对象,可以实现与类型无关的宏
#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);
对于静态声明,我认为您可以使用:
T myarray[100] = {0};
对于动态声明,我建议以同样的方式:memset
zero(myarray);
是你在 C++ 中所需要的。
只需将其添加到标题中:
template<typename T, size_t SIZE> inline void zero(T(&arr)[SIZE]){
memset(arr, 0, SIZE*sizeof(T));
}
这是我使用的功能:
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);
}
你可以这样称呼它:
//fixed arrays
int a[10];
setValue(a, 0);
//dynamic arrays
int *d = new int[length];
setValue(d, length, 0);
以上是比使用 memset 更多的 C++11 方式。如果您使用指定大小的动态数组,也会出现编译时错误。