【发布时间】:2016-02-22 19:12:18
【问题描述】:
我正在尝试找到使用 SSE(最高 SSE 4.2)执行 8 位无符号比较的最佳方式。
我正在处理的最常见情况是比较 > 0U,例如
_mm_cmpgt_epu8(v, _mm_setzero_si128()) // #1
(当然也可以认为是对非零的简单检验。)
但我也对更一般的情况有些感兴趣,例如
_mm_cmpgt_epu8(v1, v2) // #2
第一种情况可以用 2 条指令实现,使用各种不同的方法,例如与 0 比较,然后反转结果。第二种情况通常需要 3 条指令,例如从两个操作数中减去 128 并执行有符号比较。 (各种3指令解决方案见this question。)
理想情况下,我正在寻找#1 的单指令解决方案和#2 的双指令解决方案。如果这些都不可能,那么我也有兴趣思考各种可能的 2 或 3 指令实现中哪一个在现代 Intel CPU(Sandy Bridge、Ivy Bridge、Haswell)上最有效。
迄今为止案例 #2 的最佳实现:
- 与无符号最大值比较等于并取反:
#define _mm_cmpgt_epu8(v0, v1) \ _mm_andnot_si128(_mm_cmpeq_epi8(_mm_max_epu8(v0, v1), v1), \ _mm_set1_epi8(-1))
两条算术指令 + 一条按位 = 1.33 吞吐量。
- 反转两个参数的符号位(== 减 128)并使用有符号比较:
#define _mm_cmpgt_epu8(v0, v1) \ _mm_cmpgt_epi8(_mm_xor_si128(v0, _mm_set1_epi8(-128)), \ _mm_xor_si128(v1, _mm_set1_epi8(-128)))
一条算术指令 + 两条按位 = 1.16 吞吐量。
案例 #1 的最佳实现,源自上述案例 #2 的实现:
- 1.
#define _mm_cmpgtz_epu8(v0) \ _mm_andnot_si128(_mm_cmpeq_epi8(v0, _mm_set1_epi8(0)), \ _mm_set1_epi8(-1))
一条算术指令 + 一条按位 = 0.83 吞吐量。
- 2.
#define _mm_cmpgtz_epu8(v0) \ _mm_cmpgt_epi8(_mm_xor_si128(v0, _mm_set1_epi8(-128)), \ _mm_set1_epi8(-128)))
一条算术指令 + 一条按位 = 0.83 吞吐量。
【问题讨论】:
-
显然有“异或而不是减法”,吞吐量稍好..除此之外,我什么也没得到,#1 的一条指令对我来说似乎非常乐观
-
A) #2 的非严格版本(又名 ge)可以用 your own idea 在 2 条指令中完成。 B) 一条指令
_mm_subs_epu8足以将问题#2 简化为问题#1。 C) 使用superoptimization 可以一劳永逸地解决这个问题。我认为可以对 2 个 SSE 指令的所有序列执行暴力搜索,只需 2 个寄存器和一组有限的常量(例如由_mm_set1_epi8生成的那些)。 -
英特尔需要停止将 AVX512 悬挂在我们面前,并像猫面前的玩具一样将其拉开。
-
AMD 在很久以前就提供了 XOP。
-
@PeterCordes:是的,这通常是 8 或 16 次循环迭代的序列,因此可以假定任何掩码或常量是“免费的”。 CPU 仅限 Intel,Sandy Bridge 及以上版本。