如何使用 SIMD 检查偶数/奇数通道是否在给定范围内?

2024-03-25

Given a __m128i其中存储 16char是,偶数索引车道指的是even车道(即 0、2、4、...、14 处的车道),奇数索引车道指的是odd车道(即 1、3、5、...15 处的车道)。

在我的应用程序中,偶数/奇数车道必须在给定范围内。例如,假设even_min is 1, even_max is 7, odd_min是 5,并且odd_max is 10:

# valid
vec1: [1, 5, 6, 10, 2, 6, 4, 6, 2, 7, 4, 9, 2, 7, 4, 8] 

# invalid because 0-th (even) is greater than even_max
vec2: [8, 5, 6, 10, 2, 6, 4, 6, 2, 7, 4, 9, 2, 7, 4, 8]

如何更高效地检查是否有效?

我目前的解决方案很简单,分别查看两个比较结果:

  __m128i even_min = _mm_set1_epi8(xxx);
  __m128i even_max = _mm_set1_epi8(xxx);
  __m128i even_mask =
      _mm_set_epi8(0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1, 0, -1);

  __m128i evenRange = _mm_and_si128(_mm_cmpge_epi8(vec, even_min),
                                    _mm_cmple_epi8(vec, even_max));
  bool isEvenOk = _mm_testc_si128(evenRange, even_mask);

// the code for checking odd bytes is similar

请注意,要比较无符号字符,请使用包括的条件,两个宏定义如下:

#define _mm_cmpge_epi8(a, b) _mm_cmpeq_epi8(_mm_max_epu8(a, b), a)

#define _mm_cmple_epi8(a, b) _mm_cmpge_epi8(b, a)

为每车道创建一个向量min值,以及每车道的另一个maxes.例如,_mm_set1_epi16((odd_min<<8) | (uint8_t)even_min)。 (注意强制转换以避免符号扩展)。

那么你只需要进行一次范围检查。您应该更有效地做到这一点,而不是通过模拟 cmpge 和 cmple 各有 2 条指令。正如安德烈在评论中建议的,一个简单的方法是v == min(max(v, a), b),这与你的想法相同v == min(v, a).

由于您在许多输入中使​​用相同的最小值/最大值,因此一些额外的设置工作是值得的,以使每个范围检查更便宜。这正常范围检查技巧 https://stackoverflow.com/questions/5196527/double-condition-checking-in-assembly of c - min < max-min使用无符号比较,但我们可以通过翻转两侧的 MSB(即添加或减去 0x80)来使用 SSE 有符号比较来实现。这就像将无符号范围转换为有符号范围。这可以是同一个减法的一部分,c - min - 0x80 < max - min - 0x80(签名比较)。 (谢谢@amonakov提醒这是可能的。)

// unsigned compare-trick, range-shifted for use with  pcmpgtb

// loop-invariant constants, set these up once
  __m128i mins = _mm_set1_epi16( ((odd_min<<8) | (uint8_t)even_min) ^ 0x8080);
  // if (odd_max == 0x7F && even_max == 0x7F){ ... }  // TODO: just check vec > mins
  __m128i maxes = _mm_set1_epi16( ((odd_max<<8) | (uint8_t)even_max) );
  __m128i rangelen = _mm_sub_epi8(maxes, mins);   // includes the 0x80 top bit from mins
   // compilers will constant-propagate through this, except maybe MSVC.  If that's a problem, write it a different way.

// Work inside the loop
  __m128i vsub = _mm_sub_epi8(vec, mins);
  __m128i vout_of_range = _mm_cmpgt_epi8(vsub, rangelen);
  // TODO: check for off-by-one errors in case I got this wrong, or inclusive vs. exclusive.
   // consider mins = 0^0x80, maxes = 1, rangelen=1^0x80 = -127.  
   // vec = 2: vsub = 2^0x80 = -126.  -126 > -127 so it's out-of-range (by 2; this range is exclusive at the top).

  bool isOk = !_mm_movemask_epi8(vout_of_range);  // ok if no bits set

@chtz 建议一个paddb + paddusb + pmovmskb如果范围的大小小于 128,则可能是这样。因此,范围内的值不会在每个字节中设置 MSB,但超出范围的值最终将大于 128。(并且无法环绕因为饱和。)pmovmskb获取每个字节的 MSB,因此无需比较结果即可工作。psubb / pcmpgtb在大多数 CPU 上应该同样好。 (检查!= 0便宜如== 0位图结果。)


其他方式:比 sub/cmpgt 差,比min/max/cmpeq

其他可能性包括(v < mins) | (v > maxes)并检查是否没有元素为真。_mm_movemask_epi8(or_result) == 0。这比 min/max/cmpeq 具有更好的关键路径延迟,因为我们有两个独立的比较,而不是 3 个操作的链。两种方式都需要原始向量的副本(除非您使用 AVX 进行编译以允许单独的目标)。

Or (v > min-1) & (v < max+1),这对于编译时间常数最小/最大是可行的。如果 min 已经是 INT8_MIN,则它始终为 true,因此它会优化为只需要其他条件。除了当 Even_min 为 -128 时这是一个问题,但 odd_min 是其他东西:没有值可以使pcmpgtb对于偶数通道中的所有输入始终为 true,同时仍检查奇数通道。我一直在想 AND 可以作为ptest (_mm_test_*),但实际上没有_mm_test_all_ones。如果 128 位 AND 结果中有任何非零位,则 ZF 被清除。 (CF 也是如此,基于 ANDN 结果。)

Or use cmpgt两次并反转其中一个结果作为组合它们的一部分,例如和_mm_andnot_si128 (pandn)


ptest比较结果的效率不是很高,因为它在大多数 CPU 上解码为 2 uop;pmovmskb+ 标量cmp or test也是 2 uop(https://uops.info https://uops.info),如果您在分支上进行分支,则 cmp 或 test 可以与分支进行宏融合。ptest确实避免了需要临时寄存器并可能节省movdqa如果您正在测试稍后也想使用的向量(不是比较结果),则使用寄存器复制,但通常只有当您实际上使用其仅检查某些元素的功能(例如奇数/偶数掩码)时才有效。

在你的情况下,即使你的策略是两个单独的比较,可能更好的策略是 2x_mm_movemask_epi8 and (evens & (odds>>1) & 0x5555 == 0x5555。 (0x5555 是 0b0101...0101,只是测试偶数元素)。

Or _mm_srli_epi16(odds, 8) / _mm_and_si128(evens, shifted_odds)得到一个向量,其中偶数元素具有您关心的结果。 (奇数元素为零,因为逻辑移位在那里产生了零,所以_mm_movemask_epi8(and_result) == 0x5555无需掩盖我们不关心的元素。)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用 SIMD 检查偶数/奇数通道是否在给定范围内? 的相关文章

  • 当执行 uop 计数不是处理器宽度倍数的循环时,性能是否会降低?

    我想知道不同大小的循环在最新的 x86 处理器上如何执行 作为 uop 数量的函数 这里引用 Peter Cordes 的一段话 他在 中 中提出了非 4 倍数的问题另一个问题 https stackoverflow com a 31027
  • 进行水平 SSE 向量和(或其他简化)的最快方法

    给定一个由三个 或四个 浮点数组成的向量 对它们求和的最快方法是什么 SSE movaps shuffle add movd 总是比 x87 快吗 SSE3 中的水平相加指令值得吗 转移到 FPU 然后是 faddp faddp 的成本是多
  • 混洗两个 __m128i 的 64 位部分的最佳方法

    我有两个 m128is a and b 我想进行洗牌 以便高 64 位a落在低 64 位dst和低 64 位b落在上64dst i e dst 0 63 a 64 127 dst 64 127 b 0 63 相当于 m128i dst mm
  • arm64 汇编:LDP 与 LD4 执行时间

    假设我想用连续内存位置的值加载四个连续的 aarch64 向量寄存器 一种方法是 ldp q0 q1 x0 ldp q2 q3 x0 32 根据ARM优化指南 https static docs arm com uan0016 a cort
  • 不使用相关性直接在ASM中调用/跳转(x86)

    我正在将一个 C DLL 注入到游戏中 并且想将一个函数挂接到我自己的一些代码上 由于DLL每次都会映射到不同的位置 因此直接跳转和调用会更容易 另外 因为这是一个钩子 所以当我返回该函数时 我不想更改堆栈或寄存器 我声明一个 char 来
  • 现代 x86 成本模型

    我正在编写一个带有 x86 后端的 JIT 编译器 并一边学习 x86 汇编程序和机器代码 我大约 20 年前使用过 ARM 汇编器 对这些架构之间成本模型的差异感到惊讶 具体来说 内存访问和分支在 ARM 上的成本很高 但等效的堆栈操作和
  • 使用标志寄存器作为布尔返回值是否被认为是不好的做法?

    我正在 x86 汇编程序中编写一些程序 这些程序修改 ZF 作为返回布尔值的方法 因此我可以执行以下操作 call is value correct jz not correct 我想知道这是否被认为是不好的做法 因为一些编码标准规定简单的
  • Little Endian 并推入 nasm

    我不明白为什么代码的输出是Ole 小字节序不应该影响push命令 global start section data x dd 3 section text start mov eax 4 mov ebx 1 mov dword x 0x0
  • 我无法在我的 Visual C Express Edition 2008 中汇编电影 (MMX) 指令

    当我尝试编译时movd指令显示错误为 error A2085 instruction or register not accepted in current CPU mode 我的代码如下 386 model flat c code add
  • x86 32 位汇编代码是否有效 x86 64 位汇编代码?

    所有 x86 32 位汇编代码都对 x86 64 位汇编代码有效吗 我想知道32位汇编代码是否是64位汇编代码的子集 即每个32位汇编代码都可以在64位环境中运行 我想答案是肯定的 因为64位Windows能够执行32位程序 但是后来我看到
  • 如果您的程序+库不包含 SSE 指令,那么使用 VZEROUPPER 有用吗?

    我明白使用它很重要VZEROUPPER混合 SSE 和 AVX 代码时 但如果我只使用 AVX 和普通 x86 64 代码 而不使用任何旧版 SSE 指令怎么办 如果我从未在代码中使用单个 SSE 指令 是否有任何性能原因导致我需要使用VZ
  • 对于Intel Haswell上的XMM/YMM FP操作,可以使用FMA代替ADD吗?

    这个问题适用于 Haswell 上带有 XMM YMM 寄存器的打包 单精度浮点运算 所以根据awesome awesome table http www agner org optimize instruction tables pdf由
  • 使用 gdb 在指定的可执行文件之外单步执行汇编代码会导致错误“无法找到当前函数的边界”

    我在 gdb 的目标可执行文件之外 甚至没有与该目标对应的堆栈 无论如何 我想单步执行 以便我可以验证汇编代码中发生了什么 因为我不是 x86 汇编方面的专家 不幸的是 gdb 拒绝进行这种简单的汇编级调试 它允许我设置并停止在适当的断点上
  • 逆向工程的汇编语言[关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 如何使用 Java 确定 Windows 是 32 位还是 64 位体系结构?

    如何使用 Java 确定 Windows 是 32 位还是 64 位体系结构 我不太相信读取 os arch 系统变量 如果用户在 64 位系统上运行 64 位 JVM 则它可以工作 如果用户在 64 位系统上运行 32 位 JVM 则它不
  • 字大小及其指示

    请参阅下面关于各种指令集架构中的字长以及它与汇编语言的关系的问题 感谢您提供的所有帮助 先说几个事实 如有错误 请指正 处理器架构的字长表示 编辑 其中一些是错误的 请参阅下面 Seva 的帖子 每个寄存器的最大尺寸 每个内存地址的最大尺寸
  • x86 分页如何工作?

    这个问题旨在填补有关该主题的优质免费信息的真空 我相信一个好的答案将适合一个大的 SO 答案 或者至少适合几个答案 主要目标是为初学者提供足够的信息 以便他们可以自己阅读本手册 并能够理解与分页相关的基本操作系统概念 建议指南 answer
  • C++ 错误:“_mm_sin_ps”未在此范围内声明

    我正在尝试对将函数应用于数组的不同方法进行基准测试 why is mm sin ps在我的范围内不知道但是 mm sqrt ps is 我怎样才能让它知道 并且编译没有错误 include
  • 使用指针作为函数参数时的段前缀

    我有一个汇编 c 问题 我刚刚读到了有关段前缀的内容 例如 ds varX 等 前缀对于逻辑地址的计算很重要 我也读到 默认值是 ds 一旦您使用 ebp 寄存器来计算地址 就会使用 ss 对于代码 cs 是默认值 这一切都是有道理的 现在
  • 用于读/写 XMM 和 YMM 寄存器的内联汇编代码?

    我有 2 个变量来模拟 X86 XMM 和 YMM 如下所示 uint64 t xmm value 2 uint64 t ymm value 4 现在我想使用内联汇编来读取和写入 XMM YMM 寄存器 如何编写GCC内联汇编来复制xmm

随机推荐