我有 int 的向量,我需要找到和替换一些具有特定值的元素。它们都是相同的 例如:对所有元素替换4到8。
我正在c ++中尝试循环内存访问。但它对我来说仍然很慢。
更新
我正在使用Mat
上的OpenCV x86
对象:
for (int i = 0; i < labels.rows; ++i) {
for (int j = 0; j < labels.cols; ++j) {
int& label = labels.at<int>(i, j);
if (label == oldValue) {
label = newValue;
}
}
}
Mat.at()
函数只是在发布模式下通过指针返回值
template<typename _Tp> inline
_Tp& Mat::at(int i0, int i1)
{
CV_DbgAssert(dims <= 2);
CV_DbgAssert(data);
CV_DbgAssert((unsigned)i0 < (unsigned)size.p[0]);
CV_DbgAssert((unsigned)(i1 * DataType<_Tp>::channels) < (unsigned)(size.p[1] * channels()));
CV_DbgAssert(CV_ELEM_SIZE1(traits::Depth<_Tp>::value) == elemSize1());
return ((_Tp*)(data + step.p[0] * i0))[i1];
}
答案 0 :(得分:5)
您没有提及您正在开发的架构,因此无法告诉您使用哪些内在函数。幸运的是,您的编译器应该能够自动向量化像
这样的东西for (int i = 0 ; i < N ; i++)
foo[i] = (foo[i] == 4) ? 8 : foo[i];
假设您的数据已充分对齐,-mavx2 -O3
GCC将使用vpcmpeqd和vpblendvb。
答案 1 :(得分:2)
让编译器自动向量化的关键是始终分配给元素,即使您将其分配给自身也是如此。 (三元运算符在这里很好,请参阅@nemequ的答案)。这使编译器可以读取/重写未更改的值,因此可以使用load + compare和blend + store进行矢量化。
编译器无法向C ++源代码不写入的内存位置发明写入,因为这可能会踩到另一个线程的写入。它不是用于读取/写入相邻数组元素的不同线程的数据竞争。如果编译器不知道的另一个函数也使用具有不同搜索/替换值的向量加载/混合/存储循环,则它们的存储将相互衔接。所以这个矢量化策略只有在源写入所有元素时才有效。编译器可以自由地优化它(例如,如果它没有矢量化)。
对另一个答案的评论指出了无条件存储的缺点:it dirties the cache even if the data doesn't change。如果搜索命中率很少,则可能值得分支跳过存储并节省内存带宽,特别是如果多个线程将在大块内存上运行。包括在同一台机器上运行的程序的多个实例,尤其是在共享内存情况下。
AVX引入了解决此问题的掩码存储指令。 AVX2 vpmaskmovd
和AVX1 vmaskmovps
都具有32位粒度,因此您可以直接将它们用于int
数据。对于较窄的元素,您可以将+ blend与字节或单词粒度进行比较,然后使用dword粒度检查更改以生成掩码。
我认为vpmaskmovd
(至少在Skylake中)的实现确实可以避免在掩码为全0时弄脏缓存行。根据{{3}},使用蒙面商店 - &gt;任何重新加载:如果掩码全为0,则加载不依赖于被屏蔽的存储。因此存储队列知道全零掩码使存储成为无操作。
我还没有测试过,但我希望在这种情况下避免弄脏缓存线,至少在Skylake上(包括不支持AVX512的Skylake客户端;但它确实具有AVX512所需的微架构特性,如高效的蒙面商店)。甚至允许屏蔽元素在没有故障的情况下触摸非法地址,并且一些CPU可以这样做(至少对于全零屏蔽情况)而不捕获微码辅助。这意味着他们有办法完全压制商店。
所以asm你想要编译器(通过内在函数或自动向量化)是:
;; outside the loop: ymm4 = set1_epi32(4); ymm5 = set1_epi32(8);
vpcmpeqd ymm0, [rdi], ymm4 ; ymm0 = _mm256_cmpeq_epi32
vpmaskmovd [rdi], ymm0, ymm5 ; store 8 in elements where ymm0 is -1
add rdi, 32
我没有对此进行基准测试,看它是否实际上更快(或者至少在内存带宽不是瓶颈时相等,这将是一个更容易设计的微基准测试)。
Skylake上的vpmaskmovd
商店只有3 uops(p0
+商店地址+商店数据)。哈斯韦尔是4 uops。
根据Intel's optimization manual: 11.9 CONDITIONAL SIMD PACKED LOADS AND STORES,vmaskmovps
- 商店在Skylake上是4 uops。很奇怪它与行为相同的整数指令不匹配。
使用条件掩码存储意味着您不需要原始数据,因此它允许将负载折叠到vpcmpeqd
。 load + cmp + blend + store将需要1 + 2 + 1个指令,而vpblendvb
是2个uop。 (所以vblendps
)。因此理论上掩盖的商店更快。
vpblendvb
只能在端口5上运行,因此这会限制您每隔一个时钟处理32个字节,而不是每1.25个时钟处理一个向量(具有无限展开)。大多数情况下,每2个时钟32个字节是可以的,但如果您的数据在L1D缓存中很热,那么它就是瓶颈。
使用AVX512,您可能以相同的方式实现它,但使用AVX512BW,您可以使用相同的屏蔽存储策略,以比32位更小的粒度。比较k1
和vmovdqu8 [mem]{k1}, zmm8
没有AVX:请勿使用SSE maskmovdqu
;它很慢,而且是隐式NT,所以它会刷新缓存行,以及所有这些。使用load + blend + store。