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

时间:2012-02-05 02:22:17

标签: c++ c arrays memset

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

T *myarray = new T[100]等动态数组的相同问题。

7 个答案:

答案 0 :(得分:150)

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);

哪些可以自动优化为memset;我非常确定它对memset s的工作速度和int一样快,如果优化器不够智能,它对于较小的类型可能会稍差一些。不过,如果有疑问,请说明。

答案 1 :(得分:12)

这个问题,虽然相当陈旧,但需要一些基准测试,因为它要求的不是最惯用的方式,或者可以用尽可能少的行编写的方式,而是最快的方式。没有一些实际的测试,回答这个问题是愚蠢的。所以我比较了四个解决方案,memset与std :: fill对比AnT的答案与使用AVX内在函数的解决方案的ZERO。

请注意,此解决方案不是通用的,它仅适用于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万次迭代。

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字节阵列要慢得多(~220ms)并且与gcc相当。但是,使用800字节数组的非零memset不会产生速度差异,这可能就是为什么在这种情况下,它们的memset性能比我的实现更差 - 专业化仅适用于小型数组,而截止时间恰好大约为800字节。另请注意,gcc'fill'和'ZERO'没有优化到memset(查看生成的代码),gcc只是生成具有相同性能特征的代码。

结论:memset并没有真正针对此任务进行优化,因为人们会假装它(否则gcc和msvc和llvm的memset将具有相同的性能)。如果性能很重要,那么memset不应该是最终解决方案,特别是对于这些笨拙的中型阵列,因为它不专门用于位清除,并且它不是手动优化,而是编译器可以独立完成。

答案 2 :(得分:10)

来自memset()

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

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

答案 3 :(得分:5)

您可以使用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);

答案 4 :(得分:2)

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

T myarray[100] = {0};

对于动态声明,我建议采用相同的方式:memset

答案 5 :(得分:2)

zero(myarray);就是你在C ++中所需要的。

只需将其添加到标题中:

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

答案 6 :(得分:1)

这是我使用的功能:

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方式。如果使用动态数组并指定大小,也会出现编译时错误。