2

我对 C++ 比较陌生(从 Java 转移到我的科学应用程序的性能),我对 SSE 一无所知。不过,我需要改进以下非常简单的代码:

    int myMax=INT_MAX;
    int size=18000003;
    vector<int> nodeCost(size);

    /* init part */
    for (int k=0;k<size;k++){
     nodeCost[k]=myMax;
    }

我已经测量了初始化部分的时间,它需要 13 毫秒,这对于我的科学应用程序来说太大了(整个算法在 22 毫秒内运行,这意味着初始化需要总时间的 1/2)。请记住,对于同一个向量,初始化部分将重复多次。

如您所见,向量的大小未除以 4。有没有办法用 SSE 加速初始化?你能建议怎么做吗?我需要使用数组还是 SSE 也可以与向量一起使用?

拜托,既然我需要你的帮助,让我们都避免a)“你是如何测量时间的”或b)“过早的优化是万恶之源”这对你来说都是合理的,但是a)测量的时间是正确的b ) 我同意,但我别无选择。我不想将代码与 OpenMP 并行化,因此 SSE 是唯一的后备方案。

谢谢你的帮助

4

3 回答 3

8

使用向量的构造函数:

std::vector<int> nodeCost(size, myMax);

这很可能会使用优化的“memset”类型的实现来填充向量。

还要告诉你的编译器生成特定于体系结构的代码(例如-march=native -O3在 GCC 上)。在我的 x86_64 机器上,这会生成以下用于填充向量的代码:

L5:
    add     r8, 1                    ;; increment counter
    vmovdqa YMMWORD PTR [rax], ymm0  ;; magic, ymm contains the data, and eax...
    add     rax, 32                  ;; ... the "end" pointer for the vector
    cmp     r8, rdi                  ;; loop condition, rdi holds the total size
    jb      .L5

movdqa指令以 256 位操作为前缀,一次将 32 个字节复制到内存;它是 AVX 指令集的一部分。

于 2013-07-17T21:45:55.227 回答
5

按照已经建议的方式先尝试std::fill,然后如果仍然不够快,如果你真的需要,你可以去 SIMD。请注意,根据您的 CPU 和内存子系统,对于像这样的大型向量,您可能会达到 DRAM 的最大带宽,这可能是限制因素。无论如何,这是一个相当简单的 SSE 实现:

#include <emmintrin.h>

const __m128i vMyMax = _mm_set1_epi32(myMax);
int * const pNodeCost = &nodeCost[0];
for (k = 0; k < size - 3; k += 4)
{
    _mm_storeu_si128((__m128i *)&pNodeCost[k], vMyMax);
}
for ( ; k < size; ++k)
{
    pNodeCost[k] = myMax;
}

这应该在现代 CPU 上运行良好 - 对于较旧的 CPU,您可能需要更好地处理潜在的数据错位,即使用_mm_store_si128而不是_mm_storeu_si128. 例如

#include <emmintrin.h>

const __m128i vMyMax = _mm_set1_epi32(myMax);
int * const pNodeCost = &nodeCost[0];
for (k = 0; k < size && (((intptr_t)&pNodeCost[k] & 15ULL) != 0); ++k)
{                                              // initial scalar loop until we
    pNodeCost[k] = myMax;                      // hit 16 byte alignment
}
for ( ; k < size - 3; k += 4)                  // 16 byte aligned SIMD loop
{
    _mm_store_si128((__m128i *)&pNodeCost[k], vMyMax);
}
for ( ; k < size; ++k)                         // scalar loop to take care of any
{                                              // remaining elements at end of vector
    pNodeCost[k] = myMax;
}
于 2013-07-17T21:53:45.767 回答
2

这是 Mats Petersson 评论中想法的延伸。

如果您真的关心这一点,则需要改善您的参考位置。遍历 72 兆字节的初始化,然后稍后再覆盖它,这对内存层次结构极其不友好。

我不知道如何在直接的 C++ 中做到这一点,因为std::vector总是初始化自己。但是您可以尝试(1)使用callocfree分配内存;(2) 将数组的元素解释为“0 表示myMaxn表示n-1”。(我假设“成本”是非负的。否则你需要稍微调整一下这个方案。关键是要避免显式初始化。)

在 Linux 系统上,这会有所帮助,因为calloc足够大的块不需要显式地将内存归零,因为直接从内核获取的页面已经归零。更好的是,它们只会在您第一次触摸它们时被映射和归零,这对缓存非常友好。

(在我的 Ubuntu 13.04 系统上,Linuxcalloc足够聪明,不会显式初始化。如果你的不是,你可能需要做一个mmapof/dev/zero才能使用这种方法......)

是的,这确实意味着对数组的每次访问都将涉及加/减 1。(尽管不适用于“min”或“max”之类的操作。)相比之下,主内存非常慢,像这样的简单算术经常发生在与您正在做的任何其他事情并行,因此很有可能这会给您带来巨大的性能胜利。

当然,这是否有帮助将取决于平台。

于 2013-07-17T22:07:04.840 回答