C ++快速添加2个数组

时间:2010-06-02 16:10:31

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

鉴于阵列:

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的最快方法是什么?循环缓慢!

6 个答案:

答案 0 :(得分:8)

这是一个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

答案 1 :(得分:3)

你不能比C ++中的循环更快地做任何事情。您需要使用一些特定于平台的向量指令。也就是说,您需要进入汇编语言级别。但是,有一些C ++库试图为您执行此操作,因此您可以在较高级别编写并让库负责执行适合您所针对的任何体系结构的低级SIMD工作。你的编译器。

MacSTL是您可能想要查看的库。它最初是Macintosh特定的库,但它现在是跨平台的。有关详细信息,请参阅其主页。

答案 2 :(得分:3)

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

你当然可以创建一个类,其中添加一个简单的C ++指令(canvas += addon;),但这不会加速任何事情。所有会发生的事情是简单的C ++指令将扩展到上面的循环中。

您需要进入较低级别的处理才能加快速度。许多现代CPU上还有其他说明可以执行您可以使用的处理。您可以使用Cuda之类的东西在GPU上运行类似的东西。您可以尝试将操作并行并在多个内核上运行,但在这么小的实例上,您必须知道缓存如何在您的CPU上运行。

替代方案是改进您的算法(在背包式问题上,您可能以某种方式使用dynamic programming - 没有您的更多信息,我们无法告诉您),或接受表现。 10×10阵列上的数万次操作变成了数以亿计的数字操作,并不像过去那样令人生畏。当然,我不知道您的使用场景或性能要求。

答案 3 :(得分:2)

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

答案 4 :(得分:2)

这是另一种选择。

如果您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];
}

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

答案 5 :(得分:1)

你应该看看CUDA。这种问题是正确的向上CUDA的街道。推荐Programming Massively Parallel Processors本书。

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

祝你好运!