使用SSE进行矢量初始化
Using SSE for vector initialzation
我对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是唯一的后备方案。
感谢您的帮助
使用向量的构造函数:
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指令集的一部分。
按照建议先尝试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;
}
这是Mats-Petersson评论中思想的扩展。
如果你真的关心这一点,你需要改进你的参考位置。费力地完成72兆字节的初始化,但后来又回来覆盖它,这对内存层次结构极不友好。
我不知道如何在纯C++中做到这一点,因为std::vector
总是初始化自己。但是您可以尝试(1)使用calloc
和free
来分配内存;以及(2)将阵列的元素解释为"0表示myMax
,n
表示n-1
"。(我假设"成本"是非负的。否则你需要稍微调整一下这个方案。重点是避免显式初始化。)
在Linux系统上,这可能会有所帮助,因为足够大的块的calloc
不需要显式地将内存归零,因为直接从内核获取的页面已经归零。更好的是,只有当你第一次触摸它们时,它们才会被映射并归零,这对缓存非常友好。
(在我的Ubuntu 13.04系统上,Linux calloc
足够聪明,不会显式初始化。如果你的系统不是,你可能需要做/dev/zero
的mmap
才能使用这种方法…)
是的,这确实意味着对数组的每次访问都将涉及加/减1。(虽然不适用于"min"或"max"这样的运算。)相比之下,主内存相当慢,而且像这样的简单运算通常可以与你正在做的其他运算并行进行,所以这很有可能会让你在性能上大获全胜。
当然,这是否有帮助将取决于平台。
- 是否可以初始化不可复制类型的成员变量(或基类)
- C++使用整数的压缩数组初始化对象
- C++初始化基类
- 多成员Constexpr结构初始化
- 复制列表初始化的隐式转换的等级是多少
- 内联映射初始化的动态atexit析构函数崩溃
- 如何在C++中初始化嵌套类中的2个memeber
- 如何声明特征矩阵,然后通过嵌套循环初始化它
- 没有用于初始化C++中的变量模板的匹配构造函数
- 在未初始化映射的情况下,将值插入到映射的映射中
- C++成员初始化
- 为什么在C++中首先初始化成员类
- 同时具有"聚合初始化"和"模板推导"
- 初始化具有非默认构造函数的std::数组项的更好方法
- 是否可以在编译时初始化数组,以便在运行时不会花费时间?
- 我可以使用条件运算符初始化C风格的字符串文字吗
- 在C和C++中初始化结构中的数组
- 像“float[10][10]”初始化的数组是否已经针对 SIMD/SSE 进行了内存对齐
- 初始化 SSE 常量的位置
- 使用SSE进行矢量初始化