在C中将二维数组归零的最快方法?


92

我想将C中的一个大型2d数组重复置零。这是我目前正在做的事情:

// Array of size n * m, where n may not equal m
for(j = 0; j < n; j++)
{
    for(i = 0; i < m; i++)
    {  
        array[i][j] = 0;
    }
}

我尝试使用memset:

memset(array, 0, sizeof(array))

但这仅适用于一维阵列。当我打印2D数组的内容时,第一行为零,但是随后我得到了一个随机大数的负载,它崩溃了。

Answers:


177
memset(array, 0, sizeof(array[0][0]) * m * n);

其中mn是二维数组的宽度和高度(在您的示例中,您有一个正方形二维数组,所以m == n)。


1
它似乎不起作用。我在代码块上收到“流程返回-1073741819”,这是段错误吗?
Eddy 2010年

8
@Eddy:向我们展示数组的声明。
GManNickG 2010年

1
我敢打赌,它在其他行上崩溃了memset,而不是在崩溃,因为您提到过也将零行归零。
Blindy 2010年

3
嗯 刚尝试测试一个声明为的数组int d0=10, d1=20; int arr[d0][d1],并按memset(arr, 0, sizeof arr);预期工作(gcc 3.4.6,使用-std=c99 -Wall标志进行编译)。我意识到“可以在我的机器上工作”是指深蹲,但memset(arr, 0, sizeof arr); 应该可以工作。sizeof arr 应该返回整个数组使用的字节数(d0 * d1 * sizeof(int))。 sizeof array[0] * m * n不会给您正确的数组大小。
John Bode 2010年

4
@John Bode:是的,但这取决于如何获取数组。如果您有一个带参数的函数int array[][10],则sizeof(array) == sizeof(int*)由于第一维的大小未知。OP没有指定如何获取数组。
James McNellis 2010年

77

如果array确实是数组,则可以使用以下方法“将其归零”:

memset(array, 0, sizeof array);

但是,您应该知道两点:

  • 仅当array确实是“二维数组”(即为T array[M][N];某种类型声明了)时才有效T
  • 它仅在array声明的范围内起作用。如果将其传递给函数,则名称array 会衰减为指针,而sizeof不会给出数组的大小。

让我们做一个实验:

#include <stdio.h>

void f(int (*arr)[5])
{
    printf("f:    sizeof arr:       %zu\n", sizeof arr);
    printf("f:    sizeof arr[0]:    %zu\n", sizeof arr[0]);
    printf("f:    sizeof arr[0][0]: %zu\n", sizeof arr[0][0]);
}

int main(void)
{
    int arr[10][5];
    printf("main: sizeof arr:       %zu\n", sizeof arr);
    printf("main: sizeof arr[0]:    %zu\n", sizeof arr[0]);
    printf("main: sizeof arr[0][0]: %zu\n\n", sizeof arr[0][0]);
    f(arr);
    return 0;
}

在我的机器上,上面打印:

main: sizeof arr:       200
main: sizeof arr[0]:    20
main: sizeof arr[0][0]: 4

f:    sizeof arr:       8
f:    sizeof arr[0]:    20
f:    sizeof arr[0][0]: 4

即使arr是数组,当传递给时,它也会衰减为指向其第一个元素的指针f(),因此打印的大小f()是“错误的”。同样,f()的大小arr[0]是array的大小arr[0],它是“的数组[5] int”。它不是an的大小int *,因为“衰减”仅发生在第一级,这就是为什么我们需要声明f()为采用指向正确大小的数组的指针的原因。

因此,正如我所说,只有满足上述两个条件,您的原始工作才能起作用。如果没有,您将需要做其他人说的话:

memset(array, 0, m*n*sizeof array[0][0]);

最后,严格意义上讲,您发布memset()for循环并不等效。可能存在(并且曾经存在过)某些类型的“所有位为零”不等于零的编译器,例如指针和浮点值。我怀疑您是否需要为此担心。


memset(array, 0, n*n*sizeof array[0][0]);我猜你的意思是m*n不是n*n正确的?
Tagc

古怪的是,这似乎并没有与像1和2的值工作,而不是0
阿希什阿华加

memset在字节(字符)级别工作。由于基本表示形式中的字节数相同12不同,因此您不能使用memset
Alok Singhal

@AlokSinghal可能会指出,在最小工作示例之前的某处int您的系统上为4字节”,以便读者可以轻松地计算总和。
71GA

9

好吧,最快的方法就是根本不做。

我知道这听起来有些奇怪,这是一些伪代码:

int array [][];
bool array_is_empty;


void ClearArray ()
{
   array_is_empty = true;
}

int ReadValue (int x, int y)
{
   return array_is_empty ? 0 : array [x][y];
}

void SetValue (int x, int y, int value)
{
   if (array_is_empty)
   {
      memset (array, 0, number of byte the array uses);
      array_is_empty = false;
   }
   array [x][y] = value;
}

实际上,它仍在清除数组,但仅在将某些内容写入数组时才清除。这不是很大的优势。但是,如果使用例如四叉树(不是动态思维)或数据行集合来实现2D数组,则可以对布尔标志的作用进行本地化,但需要更多标志。在四叉树中,只需为根节点设置空标志,在行数组中,只需为每行设置标志。

这就引出了一个问题:“为什么要重复将大型2d数组归零”?数组有什么用?有没有一种方法可以更改代码,以使数组不需要归零?

例如,如果您有:

clear array
for each set of data
  for each element in data set
    array += element 

也就是说,将其用于累积缓冲区,然后像这样进行更改将无休止地提高性能:

 for set 0 and set 1
   for each element in each set
     array = element1 + element2

 for remaining data sets
   for each element in data set
     array += element 

这不需要清除数组,但仍然可以使用。这将比清除阵列快得多。就像我说的,最快的方法是一开始就不要这样做。


看问题的有趣替代方法。
Beska 2010年

1
在这种情况下,我不确定为每次读取添加额外的比较/分支是否值得推迟数组的初始化(尽管可能是您的)。如果数组真的很大,以至于初始化时间引起了严重的关注,那么他可以使用哈希代替。
tixxit 2010年

8

如果您真的非常着迷于速度(而不是那么着迷于便携性),那么我认为绝对最快的方法是使用SIMD向量内在函数。例如在Intel CPU上,您可以使用以下SSE2指令:

__m128i _mm_setzero_si128 ();                   // Create a quadword with a value of 0.
void _mm_storeu_si128 (__m128i *p, __m128i a);  // Write a quadword to the specified address.

每条存储指令将在一次命中中将四个32位整数设置为零。

p必须是16字节对齐的,但是此限制也有利于提高速度,因为它将帮助缓存。另一个限制是p必须指向分配的大小,该大小是16字节的倍数,但这也是很酷的,因为它使我们可以轻松展开循环。

将其循环,然后循环几次,您将得到一个疯狂的快速初始化程序:

// Assumes int is 32-bits.
const int mr = roundUpToNearestMultiple(m, 4);      // This isn't the optimal modification of m and n, but done this way here for clarity.    
const int nr = roundUpToNearestMultiple(n, 4);    

int i = 0;
int array[mr][nr] __attribute__ ((aligned (16)));   // GCC directive.
__m128i* px = (__m128i*)array;
const int incr = s >> 2;                            // Unroll it 4 times.
const __m128i zero128 = _mm_setzero_si128();

for(i = 0; i < s; i += incr)
{
    _mm_storeu_si128(px++, zero128);
    _mm_storeu_si128(px++, zero128);
    _mm_storeu_si128(px++, zero128);
    _mm_storeu_si128(px++, zero128);
}

还有一个变体 _mm_storeu绕过缓存(即,将阵列清零不会污染缓存),这在某些情况下可能会给您带来一些次要的性能优势。

请参阅此处以获取SSE2参考:http : //msdn.microsoft.com/zh-cn/library/kcwz153a (v=vs.80) .aspx


5

如果您使用初始化数组malloc,请calloc改用; 它将免费将您的数组清零。(显然,性能与memset相同,只需要更少的代码。)


如果我想将数组重复置零,这比memset快吗?
Eddy 2010年

calloc会在初始化时将它清零一次,并且可能不会比调用malloc和memset更快。之后,您就自己一个人了,如果要再次将其调零,则可以使用memset。除非您的阵列真的很大,否则在任何合理的机器上都不会真正考虑性能。
Ben Zotto


2

您的2D数组如何声明?

如果是这样的话:

int arr[20][30];

您可以通过以下方法将其归零:

memset(arr, sizeof(int)*20*30);

我使用了char [10] [10]数组。但是我有一个错误:函数'memset'的参数太少了,memset(a, 0, sizeof(char)*10*10);对我来说很好用。,它是怎么发生的?
2013年

1

使用calloc而不是malloc。calloc会将所有字段初始化为0。

int * a =(int *)calloc(n,size((int)));

// a的所有单元格都已初始化为0


0

我认为手动完成此操作的最快方法是遵循代码。您可以将它的速度与memset函数进行比较,但不应该慢一些。

(如果数组类型与int不同,则更改ptr和ptr1指针的类型)


#define SIZE_X 100
#define SIZE_Y 100

int *ptr, *ptr1;
ptr = &array[0][0];
ptr1 = ptr + SIZE_X*SIZE_Y*sizeof(array[0][0]);

while(ptr < ptr1)
{
    *ptr++ = 0;
}


您的代码很可能会比memsetchar类型的代码慢。
tofro

0
memset(array, 0, sizeof(int [n][n]));

1
array [n] [n]是数组1个元素的大小,因此只有数组的第一个元素会被初始化。
EvilTeach 2010年

哎呀。你是对的。我的意思是将类型签名放置在括号中,而不是数组查找。解决它。
swestrup


-2

发生这种情况是因为sizeof(array)为您提供了array指向的对象的分配大小。(数组只是指向多维数组第一行的指针)。但是,您分配了j个大小为i的数组。因此,您需要将sizeof(array)返回的一行的大小乘以您分配的行数,例如:

bzero(array, sizeof(array) * j);

还要注意,sizeof(array)仅适用于静态分配的数组。对于动态分配的数组,您可以编写

size_t arrayByteSize = sizeof(int) * i * j; 
int *array = malloc(array2dByteSite);
bzero(array, arrayByteSize);

第一部分是错误的。对于sizeof操作符,array不是指针(如果已声明为数组)。请参阅我的答案作为示例。
Alok Singhal 2010年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.