对于 SIMD 循环处理的数据太窄?

问题描述 投票:0回答:1

处理太小而无法填充寄存器的一行数据的剩余部分的最佳方法是什么?

考虑处理 32 位像素数据的 AVX512 循环:

fnAVX512(npixels) {
    while (npixels >= 16) {process_row; npixels -= 16;}
}

完成后,

npixels
可能不为零。或者该函数可能一开始就使用窄数据调用。

我可以想到三种可能的解决方案:

  1. 无论如何,通过使用排除当前感兴趣的数据的掩码执行加载,将数据整理到寄存器中。这可能会变得混乱,特别是如果初始数据不够宽:您无法回溯指针以确保位于“安全区域”。例如,

    _mm256_maskz_loadu_epiN
    是否保证不从与
    0
    掩码位相对应的内存位置读取,或者即使掩码已安全设置,它也会导致异常吗?

  2. 回落到标量,而不考虑还剩下多少像素;在这种情况下,在 15 到 1 之间?这很容易,但感觉不对;相比之下,标量代码看起来很糟糕,但相应地执行。 (就我而言,大约是 AVX 速度的 10%。)

  3. 如果您已经有 128 和 256 位矢量代码来支持旧架构,您可能会遇到这些问题:

fnAVX512(npixels) {
    while (npixels >= 16) {process_row; npixels -= 16;}
    fnAVX2(npixels);
}

fnAVX2(npixels) {
    while (npixels >= 8) {process_row; npixels -= 8;}
    fnSSE(npixels)
}
...

最终,使用 3 个或更少的像素,进行标量。关于试图避免这种情况的主题(也许这应该是一个单独的问题):2023 年 MMX 怎么样?尽管它没有大量的指令集,但它应该(?)对于某些类型的数据有用,例如打包为 32 位整数的 8 位颜色通道,其中标量代码需要大量操作。

simd sse avx
1个回答
0
投票

如果您的问题允许(纯垂直 SIMD,因此可以重新处理同一元素两次),则在数组末尾结束的最终向量是好的。它可能会也可能不会与早期数据重叠。如果您将结果复制到单独的目的地,只要您的输入始终比一个向量宽,这种方法就非常有效。否则,在就地操作时可能需要注意才能正确且高效。

您可以将 8 字节或 4 字节加载/存储与 XMM 寄存器结合使用,作为清理策略的一部分,例如 SSE2
movq xmm, [rdi]

。无需涉及 MMX,也无需运行缓慢的

emms
指令!
最近的内联函数(如 

_mm_storeu_si64

和带有

_mm_loadu_si32
操作数的
void*
)是比相同指令的早期内联函数更干净的包装。
_mm_loadl_epi64(__m128i *)
存在于
movq
,但
movd
没有旧的内在函数。 (也许回到过去的糟糕日子,英特尔认为您应该在取消引用
_mm_cvtsi32_si128
后使用
int*
,因为他们的编译器(ICC)和 MSVC 不关心严格别名,也许也不关心
对齐 UB
? ) 请注意,某些 GCC 版本对

_mm_loadu_si32

的定义有问题,加载后将数据改组为高 32 位。请参阅 Ubuntu 上的 GCC 无法识别的

_mm_loadu_si32 
- 幸运的是,他们在修复更明显的错误时还修复了严格别名和对齐 UB 错误,因此没有任何 GCC 版本具有似乎不安全的静默版本工作。

SIMD 屏蔽加载和存储(如 AVX-512
_mm256_maskz_loadu_epiN

)确实可以抑制未映射页面中屏蔽元素的错误。仅使用 AVX2,您只有 32 位或 64 位掩码粒度,并且需要矢量掩码。 (除了

SSE2 
_mm_maskmoveu_si128
;它是否抑制错误取决于实现。此外,它是绕过缓存和逐出的 NT 存储,部分行 NT 存储很糟糕。它通常在现代 CPU 上没有用,因为它甚至也很慢最好的情况下。它只能作为商店提供。)
如果您可以控制如何分配缓冲区,则可以将分配大小四舍五入为向量宽度的倍数,以使事情变得更容易。

对于图像,如果您加载的向量中的一些像素来自一行的末尾,一些像素来自下一行的开头,那么这可能并不重要。但是,如果您确实需要对每一行执行不同的操作,那么填充存储几何图形是有意义的,因此行之间的步长是向量宽度的倍数。即,如果实际图像宽度不是 4 像素/16 字节的倍数,则在每行末尾有一些填充像素。对于宽向量和展开的循环,这可能会浪费大量缓存占用空间,因此可能只填充最多 16 字节的倍数,并让循环处理奇数大小到 16 字节的倍数,但不能更窄。

另请参阅

    使用 AVX 的两个 C++ 整数向量的快速 int32_t 点积并不更快
  • - 作为减少清理的一部分,较窄的负载,因此它不能只是做一个可能重叠的最终向量。
  • SIMD (avx) 处理如何工作?例如,如果我想要 10 个 32 位浮点数,我如何适合 256 位 avx 向量?
  • 处理剩余元素的清理策略的讨论,特别是对于纯垂直复制和修改问题,其中重新处理相同的元素会给出相同的结果。
  • 使用未对齐缓冲区进行矢量化:使用 VMASKMOVPS:根据未对齐计数生成掩码?或者根本不使用那个insn
© www.soinside.com 2019 - 2024. All rights reserved.