【问题标题】:Fastest way to zero out a 2d array in C?在 C 中将二维数组归零的最快方法是什么?
【发布时间】:2011-01-31 17:54:57
【问题描述】:

我想在 C 中反复将一个大型二维数组归零。这就是我现在所做的:

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

但这仅适用于一维数组。当我 printf 二维数组的内容时,第一行为零,但随后我得到了大量随机大数,它崩溃了。

【问题讨论】:

    标签: c arrays multidimensional-array zero memset


    【解决方案1】:

    如果array 确实是一个数组,那么您可以使用以下命令将其“归零”:

    memset(array, 0, sizeof array);
    

    但是有两点你应该知道:

    • 仅当 array 确实是“二维数组”时才有效,即,对于某些类型 T 声明为 T array[M][N];
    • 它仅在声明array 的范围内有效。如果将其传递给函数,则名称 array decays to a pointersizeof 不会为您提供数组的大小。

    让我们做一个实验:

    #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] 的大小是数组arr[0] 的大小,它是“int 的数组 [5]”。它不是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 对吧?
    • 奇怪的是,这似乎不适用于像 1 和 2 这样的值,而不是 0。
    • memset 在字节 (char) 级别工作。因为12 在底层表示中没有相同的字节,所以你不能用memset 做到这一点。
    • @AlokSinghal 也许在最小工作示例之前指出 "inton your system is 4 bytes",以便读者可以轻松计算总和。跨度>
    【解决方案2】:

    使用 calloc 代替 malloc 。 calloc 会将所有字段初始化为 0。

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

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

    【讨论】:

      【解决方案3】:

      你可以试试这个

      int array[20,30] = {{0}};
      

      【讨论】:

      • 请添加更多详细信息
      【解决方案4】:

      int array[N][M] = {0};

      ...至少在 GCC 4.8 中。

      【讨论】:

        【解决方案5】:

        如果您真的非常着迷于速度(而不是可移植性),我认为绝对最快的方法是使用 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/en-us/library/kcwz153a(v=vs.80).aspx

        【讨论】:

          【解决方案6】:
          memset(array, 0, sizeof(int [n][n]));
          

          【讨论】:

          • array[n][n] 是数组的 1 个元素的大小,因此只会初始化数组的第一个元素。
          • 糟糕。你是对的。我的意思是在括号中放置一个类型签名,而不是数组查找。修好了。
          【解决方案7】:

          我认为手动完成的最快方法是遵循代码。您可以将它的速度与 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;
          }
          

          【讨论】:

          • 对于 char 类型,您的代码很可能会比 memset 慢。
          【解决方案8】:

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

          【讨论】:

          • 如果我想反复归零我的数组,这比 memset 快吗?
          • calloc 将在初始化时将其归零一次,并且可能不会比调用 malloc 后跟 memset 快。之后你就靠自己了,如果你想再次将其归零,可以使用 memset。除非您的阵列非常庞大,否则性能并不是任何合理机器上的真正考虑因素。
          【解决方案9】:
          memset(array, 0, sizeof(array[0][0]) * m * n);
          

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

          【讨论】:

          • 它似乎不起作用。我在代码块上收到“进程返回 -1073741819”,这是一个段错误,对吗?
          • @Eddy:向我们展示数组的声明。
          • 我敢打赌它在其他行上崩溃了,而不是memset,因为你提到过因为只清零一行而崩溃。
          • 嗯。刚刚尝试测试一个声明为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:是的,但这取决于如何获取数组。如果您有一个带有参数int array[][10] 的函数,那么sizeof(array) == sizeof(int*) 因为第一个维度的大小是未知的。 OP 没有说明数组是如何获得的。
          【解决方案10】:

          嗯,最快的方法就是完全不这样做。

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

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

          实际上,它仍在清除数组,但仅在向数组写入内容时。这不是一个很大的优势。但是,如果二维数组是使用四叉树(不是动态思维)或数据行集合来实现的,那么您可以本地化布尔标志的效果,但您需要更多标志。在四叉树中为根节点设置空标志,在行数组中为每一行设置标志。

          这会导致“为什么要反复将大型二维数组归零”的问题?数组是干什么用的?有没有办法改变代码使数组不需要归零?

          例如,如果您有:

          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 
          

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

          【讨论】:

          • 有趣的替代方式来看待这个问题。
          • 我不确定在这种情况下为每次读取添加额外的比较/分支是否值得推迟数组的初始化(尽管可能是你的)。如果数组真的太大以至于初始化时间会引起严重的问题,那么他可以使用散列代替。
          【解决方案11】:

          这是因为 sizeof(array) 为您提供了 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 不是指针(如果它被声明为数组)。例如,请参阅我的答案。
          【解决方案12】:

          你的二维数组是如何声明的?

          如果是这样的:

          int arr[20][30];
          

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

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

          【讨论】:

          • 我使用了一个 char[10][10] 数组。但我得到了错误:函数“memset”的参数太少,memset(a, 0, sizeof(char)*10*10); 对我来说很好用。 ,这是怎么回事?
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-08-11
          • 1970-01-01
          • 2023-02-06
          • 2023-04-04
          • 2017-01-26
          相关资源
          最近更新 更多