我是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;
}
我已经测量了初始化部分的时间,它需要13ms,这对于我的科学应用来说太大了(整个算法在22ms运行,这意味着初始化需要总时间的1/2)。请记住,初始化部分将针对同一向量重复多次。
如您所见,向量的大小不会除以4.有没有办法加速SSE的初始化?你能建议怎么样?我是否需要使用数组或SSE也可以与向量一起使用?
请,因为我需要你的帮助,让我们都避免a)“你是如何衡量时间”或b)“过早优化是所有邪恶的根源”,这对你来说是合理的,但a)测量的时间是正确的b)我同意,但我别无选择。我不想将代码与OpenMP并行化,因此SSE是唯一的后备。
感谢您的帮助
答案 0 :(得分:7)
使用vector的构造函数:
std::vector<int> nodeCost(size, myMax);
这很可能会使用优化的“memset”类型的实现来填充向量。
还要告诉编译器生成特定于体系结构的代码(例如GCC上的-march=native -O3
)。在我的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指令集的一部分。
答案 1 :(得分: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
。 E.g。
#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;
}
答案 2 :(得分:2)
这是Mats Petersson评论中的想法的延伸。
如果您真的关心这一点,您需要改善您的参考地点。通过72兆字节的初始化,只是稍后再回来覆盖它,对内存层次结构非常不友好。
我不知道如何在直接C ++中执行此操作,因为std::vector
始终初始化自己。但您可以尝试(1)使用calloc
和free
来分配内存; (2)将数组的元素解释为“0表示myMax
而n
表示n-1
”。 (我假设“成本”是非负的。否则你需要稍微调整一下这个方案。重点是避免显式初始化。)
在Linux系统上,这可以提供帮助,因为足够大的块calloc
不需要显式清零内存,因为直接从内核获取的页面已经归零。更好的是,它们只有在您第一次触摸它们时才会被映射和归零,这对缓存非常友好。
(在我的Ubuntu 13.04系统上,Linux calloc
非常聪明,不能显式初始化。如果不是,你可能需要mmap
/dev/zero
来使用这种方法...)
是的,这意味着每次访问数组都会涉及加/减1.(虽然不适用于“min”或“max”这样的操作。)相比之下,主内存相当慢,而像这样的简单算术可以经常与你正在做的其他事情同时发生,所以这有可能给你带来很大的表现。
当然这是否有助于平台依赖。