【问题标题】:C++ Adding 2 arrays together quicklyC++快速将2个数组加在一起
【发布时间】:2011-02-26 22:02:15
【问题描述】:

给定数组:

int canvas[10][10];
int addon[10][10];

所有值的范围是 0 - 100,在 C++ 中添加这两个数组以使画布中的每个单元格等于自身加上插件中相应的单元格值的最快方法是什么?

IE,我想实现类似:

canvas += another;

所以如果 canvas[0][0] =3 并且 addon[0][0] = 2 那么 canvas[0][0] = 5

速度在这里至关重要,因为我正在编写一个非常简单的程序来暴力破解背包类型的问题,并且会有数千万种组合。

作为一个额外的小问题(如果你能提供帮助,谢谢!)检查画布中的任何值是否超过 100 的最快方法是什么?循环很慢!

【问题讨论】:

  • 抱歉,我只能将此解决方案构建为控制台应用程序。
  • 你为什么要暴力破解背包问题?有一个更快的动态编程解决方案。通常,改进算法比使用代码破解更好。
  • 额外速度有多重要?如果它成为一个组合大的问题,您可能需要考虑并行化代码。你也特别想暴力破解这个问题吗?如果不是,我建议研究混合整数规划和分支定界算法。
  • 您在寻找直接的 C/C++ 答案吗?如果您愿意放弃特定于体系结构的东西,我会看看是否有 SIMD 样式的指令(如 SSE)可以帮助并行化操作(并不是说我确切知道它是如何工作的)。
  • 速度是相当重要的,在没有任何启发式的情况下,我认为我们正在寻找从几亿到多达 100 亿个可能的解决方案。现在唯一需要的计算是将数组加在一起,然后计算任何值是否超过 100。这是一个包装/背包问题,所以保证最佳解决方案的唯一方法是蛮力,我每次都需要找到最佳解决方案。

标签: c++ performance arrays micro-optimization simd


【解决方案1】:

两部分:首先,将您的二维数组 [10][10] 视为单个数组 [100]。 C++ 的布局规则应该允许这样做。其次,检查您的编译器是否有实现某种形式的SIMD instructions 的内在函数,例如英特尔的 SSE。例如Microsoft supplies a set。我相信 SSE 有一些关于检查最大值的说明,如果你愿意,甚至可以钳制到最大值。

【讨论】:

  • 感谢大家的帮助
  • @Tom Gullen,感谢某人的最佳方式是点击答案旁边数字上方的向上箭头。
  • 它不会让我因为我是新人但我很抱歉!
【解决方案2】:

在 C++ 中没有比循环更快的方法了。您将需要使用一些特定于平台的向量指令。也就是说,您需要深入到汇编语言级别。但是,有一些 C++ 库会尝试为您执行此操作,因此您可以在高级别编写并让库负责执行适合您使用编译器所针对的任何架构的低级别 SIMD 工作.

MacSTL 是您可能想要查看的库。它最初是一个 Macintosh 特定的库,但现在它是跨平台的。有关更多信息,请参见他们的主页。

【讨论】:

  • 感谢您的帮助,在我的其他优化问题中,我发现如果您知道数组大小,则在添加中“手动”编码:a[0] = a[0] + b[0] ; a[1] = a[1] + b[1]; .... a[20] = a[20] + b[20];在处理庞大的解决方案集时,比使用循环要快得多。
  • @Tom:对于小型数组,这几乎可以肯定是正确的。如果将它们设置得太大,可能会遇到缓存未命中问题。
  • @Tom:给定适当的标志,编译器还可以自动将循环展开到类似的位置,这样可以在不牺牲性能的情况下生成更简洁的代码。
【解决方案3】:

您在标准 C 或 C++ 中要做的最好的事情是将其重铸为 100 个数字的一​​维数组并将它们添加到一个循环中。 (单下标将使用比双下标少一点的处理,除非编译器可以优化它。你要知道有多大影响的唯一方法,如果有的话,就是测试。)

您当然可以创建一个添加一个简单 C++ 指令 (canvas += addon;) 的类,但这不会加快任何速度。所发生的只是简单的 C++ 指令会扩展为上面的循环。

您需要进入较低级别的处理以加快速度。许多现代 CPU 上都有额外的指令来执行您可能可以使用的此类处理。您可能可以使用Cuda 之类的东西在 GPU 上运行类似的东西。您可以尝试使操作并行运行并在多个内核上运行,但在如此小的实例上,您必须了解缓存在 CPU 上的工作原理。

替代方案是改进您的算法(在背包类型的问题上,您可能能够以某种方式使用dynamic programming - 没有您提供的更多信息,我们无法告诉您),或者接受性能.对 10 x 10 数组的数千万次操作变成了对数的数千亿次操作,这不再像以前那样令人生畏了。当然,我不知道你的使用场景或性能要求。

【讨论】:

    【解决方案4】:

    这是一个 SSE4 实现,应该在 Nehalem (Core i7) 上表现良好:

    #include <limits.h>
    #include <emmintrin.h>
    #include <smmintrin.h>
    
    static inline int canvas_add(int canvas[10][10], int addon[10][10])
    {
        __m128i * cp = (__m128i *)&canvas[0][0];
        const __m128i * ap = (__m128i *)&addon[0][0];
        const __m128i vlimit = _mm_set1_epi32(100);
        __m128i vmax = _mm_set1_epi32(INT_MIN);
        __m128i vcmp;
        int cmp;
        int i;
    
        for (i = 0; i < 10 * 10; i += 4)
        {
            __m128i vc = _mm_loadu_si128(cp);
            __m128i va = _mm_loadu_si128(ap);
    
            vc = _mm_add_epi32(vc, va);
            vmax = _mm_max_epi32(vmax, vc);   // SSE4 *
    
            _mm_storeu_si128(cp, vc);
    
            cp++;
            ap++;
        }
        vcmp = _mm_cmpgt_epi32(vmax, vlimit); // SSE4 *
        cmp = _mm_testz_si128(vcmp, vcmp);    // SSE4 *
        return cmp == 0;
    }
    

    使用 gcc -msse4.1 ... 或针对您的特定开发环境的等效项进行编译。

    对于没有 SSE4 的旧 CPU(以及更昂贵的未对齐加载/存储),您需要 (a) 使用 SSE2/SSE3 内在函数的合适组合来替换 SSE4 操作(上面标有 *)理想情况下 (b) 确保您的数据是 16 字节对齐的,并使用对齐的加载/存储 (_mm_load_si128/_mm_store_si128) 代替 _mm_loadu_si128/_mm_storeu_si128

    【讨论】:

      【解决方案5】:

      这是另一种选择。

      如果您 100% 确定所有值都在 0 到 100 之间,则可以将类型从 int 更改为 uint8_t。然后,您可以使用 uint32_t 一次将 4 个元素添加在一起,而不必担心溢出。

      那是……

      uint8_t  array1[10][10];
      uint8_t  array2[10][10];
      uint8_t  dest[10][10];
      uint32_t *pArr1 = (uint32_t *) &array1[0][0];
      uint32_t *pArr2 = (uint32_t *) &array2[0][0];
      uint32_t *pDest = (uint32_t *) &dest[0][0];
      
      int  i;
      
      for (i = 0; i < sizeof (dest) / sizeof (uint32_t); i++) {
          pDest[i] = pArr1[i] + pArr2[i];
      }
      

      它可能不是最优雅的,但它可以帮助您避免使用特定于架构的代码。此外,如果您要这样做,我强烈建议您评论您在做什么以及为什么这样做。

      【讨论】:

        【解决方案6】:

        您应该查看 CUDA。这种问题在 CUDA 的街道上正确。推荐Programming Massively Parallel Processors这本书。

        但是,这确实需要支持 CUDA 的硬件,并且 CUDA 需要花费一些精力才能在您的开发环境中进行设置,所以这取决于它的重要性!

        祝你好运!

        【讨论】:

          猜你喜欢
          • 2018-10-16
          • 2016-05-28
          • 1970-01-01
          • 1970-01-01
          • 2016-10-10
          • 1970-01-01
          • 2021-02-09
          • 1970-01-01
          • 2015-11-13
          相关资源
          最近更新 更多