当前位置: 首页 > 知识库问答 >
问题:

是否可以使用SIMD指令进行替换?

田兴朝
2023-03-14

我有int的向量,我需要找到并用特定的值替换一些元素。他们都是一样的
例如:将所有元素的4替换为8。

我正在尝试c中循环中的直接内存访问。但对我来说还是很慢。

更新:
我正在x86上使用OpenCVMat对象:

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];
}

共有2个答案

栾昂雄
2023-03-14

让编译器自动向量化的关键是始终指定给元素,即使您将其指定给自身。(这里的三元运算符很好,请参见@Nemeq的答案)。这允许编译器对未更改的值进行读取/重写,因此可以使用负载比较和混合存储进行矢量化。

编译器不能发明对C源不写入的内存位置的写入,因为这可能会踩到来自另一个线程的写入。不同线程读取/写入相邻数组元素不是数据竞争。如果编译器不知道的另一个函数也使用具有不同搜索/替换值的向量加载/混合/存储循环,它们的存储将相互踩踏。因此,这种矢量化策略仅在源写入所有元素时有效。编译器可以自由优化它(例如,如果它没有矢量化)。

另一个答案上的评论指出了无条件存储的缺点:即使数据不变,也会污染缓存。如果搜索命中率很低,那么进行分支以跳过存储并节省内存带宽可能是值得的,尤其是如果多个线程将在大内存块上运行此功能。包括在同一台机器上运行的程序的多个实例中,尤其是在共享内存的情况下。

AVX引入了屏蔽存储指令来解决这个问题。AVX2和AVX1都具有32位的粒度,因此您可以直接将它们用于int数据。对于较窄的元素,可以将blend与字节或单词粒度进行比较,然后检查dword粒度的变化以生成掩码。

我认为vpmaskmovd的实现(至少在Skylake中)确实避免了在掩码为全0时弄脏缓存行。根据英特尔的优化手册:11.9 CONDITIONAL SIMD PACKED LOADS AND STORES,带有掩码存储-

我还没有测试过,但我希望在这种情况下它可以避免弄脏缓存行,至少在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

我还没有对此进行基准测试,看看它是否真的更快(或者至少在内存带宽不是瓶颈的情况下是相等的,这将是一个更容易设计的微基准)。

vpmaskmovd存储在Skylake上只有3 uops(p0store-地址store-data)。它在Haswell上是4 uops。

根据Agner Fog的测试,vmaskmovps在Skylake上的存储容量为4个UOP。很奇怪,它与行为相同的整数指令不匹配。

使用条件屏蔽存储意味着您不需要原始数据,因此它允许将负载折叠到vpcmpeqd中。加载cmp混合存储需要1 2 1指令,而vpblendvb是2 UOP。(vblendps也是如此)。因此,理论上,蒙面店速度更快。

Haswell上的vpblendvb只能在端口5上运行,因此将限制您每隔一个时钟处理32字节,而不是每1.25个时钟处理一个向量(无限展开)。不过,大多数情况下,每2个时钟32个字节是可以的,但如果您的数据在L1D缓存中很热,那么这就是一个瓶颈。

对于AVX512,您可能会以相同的方式实现它,但对于AVX512BW,您可以使用相同的屏蔽存储策略来实现比32位更小的粒度。比较成k1和vmovdqu8[mem]{k1},zmm8

没有AVX:不要使用SSE maskmovdqu;它的速度很慢,并且隐式为NT,因此它会刷新缓存线等等。使用加载混合存储。

郎和志
2023-03-14

您没有提到要开发的体系结构,因此无法告诉您要使用哪些内部函数。幸运的是,您的编译器应该能够自动向量化如下内容

for (int i = 0 ; i < N ; i++)
  foo[i] = (foo[i] == 4) ? 8 : foo[i];

假设您的数据充分对齐,-mavx2-O3GCC将使用vpcmpeqd和vpblendvb。

 类似资料:
  • 在C/C中,可以对SIMD(如AVX和AVX2)指令使用内部函数。有没有办法在Rust中使用SIMD?

  • 有几种使用SIMD指令优化HOG描述符计算的尝试:OpenCV、Dlib和SIMD。它们都使用标量代码将结果幅值添加到HOG直方图中: 在那里,大小的值取决于实现,但一般意义相同。 我知道使用SIMD计算直方图的问题并没有简单有效的解决方案。但在这种情况下,我们有小尺寸(18)的直方图。它能帮助SIMD优化吗?

  • 问题内容: 我不想使用jQuery,但我想使用Ajax进行文件上传。那可能吗? 如果是这样,我在哪里可以找到有关信息/教程? 问题答案: 不,无法使用javascript执行此操作。 但是,为了给人“ AJAX”的感觉,您可以向隐藏的iframe提交表单,然后将脚本结果输出到其中,然后从那里进行处理。Google 并从那里开始。 如果您使用的是jQuery,并且您的表单中包含任何文件字段,也可以使

  • 我已经能够升级angularjs元素指令以用于angular 4。下面是一个示例代码: [myScores.js] [myScores.ts] 请注意,我正在使用 UpgradeComponent 来升级 myScores 元素指令。我已经在属性指令上尝试了相同的方法,但得到了一个错误。有没有办法升级属性指令? 下面是我升级属性指令的尝试: [绿色.js] [makeGreen.ts] 当加载一个

  • 问题内容: 如何监视进程的SIMD(SSE,AVX,AVX2,AVX-512)指令使用量?例如,可用于监视常规CPU使用率,但不能监视SIMD指令使用率。 问题答案: 我认为,对 所有 SIMD指令(不仅仅是FP数学)进行计数的唯一可靠方法是动态检测(例如,通过诸如Intel PIN / SDE之类的方法)。 请参阅如何通过获取指令类型明细来表征工作负载?以及如何确定在C程序中执行的x86机器指令

  • 有没有我们可以用来进行指令集检测的宏? 我知道我们有运行时程序: 但我们是否已经定义了这方面的符号,例如。 如果没有,我们可以创建一个或我们有一个解决办法吗? 在玩反编译器的时候,我发现了一个奇怪的“递归”。但我想ILSpy漏了。 此外,当我查看一个简单的代码时,我注意到以下内容: ASM 正如您所看到的,它不知怎么弄明白了Avx是受支持的,并且不包括分支。这是正常和明确的行为吗?