我有一个程序,它花费大部分时间计算 RGB 值之间的欧几里德距离(无符号 8 位的 3 元组)Word8
)。我需要一个快速、无分支的 unsigned int 绝对差函数,这样
unsigned_difference :: Word8 -> Word8 -> Word8
unsigned_difference a b = max a b - min a b
尤其,
unsigned_difference a b == unsigned_difference b a
我使用 GHC 7.8 中的新 primops 得出了以下结论:
-- (a < b) * (b - a) + (a > b) * (a - b)
unsigned_difference (I# a) (I# b) =
I# ((a <# b) *# (b -# a) +# (a ># b) *# (a -# b))]
which ghc -O2 -S
编译为
.Lc42U:
movq 7(%rbx),%rax
movq $ghczmprim_GHCziTypes_Izh_con_info,-8(%r12)
movq 8(%rbp),%rbx
movq %rbx,%rcx
subq %rax,%rcx
cmpq %rax,%rbx
setg %dl
movzbl %dl,%edx
imulq %rcx,%rdx
movq %rax,%rcx
subq %rbx,%rcx
cmpq %rax,%rbx
setl %al
movzbl %al,%eax
imulq %rcx,%rax
addq %rdx,%rax
movq %rax,(%r12)
leaq -7(%r12),%rbx
addq $16,%rbp
jmp *(%rbp)
编译用ghc -O2 -fllvm -optlo -O3 -S
生成以下 asm:
.LBB6_1:
movq 7(%rbx), %rsi
movq $ghczmprim_GHCziTypes_Izh_con_info, 8(%rax)
movq 8(%rbp), %rcx
movq %rsi, %rdx
subq %rcx, %rdx
xorl %edi, %edi
subq %rsi, %rcx
cmovleq %rdi, %rcx
cmovgeq %rdi, %rdx
addq %rcx, %rdx
movq %rdx, 16(%rax)
movq 16(%rbp), %rax
addq $16, %rbp
leaq -7(%r12), %rbx
jmpq *%rax # TAILCALL
因此,LLVM 设法用(更有效?)条件移动指令代替比较。不幸的是编译时使用-fllvm
对我的程序的运行时间影响不大。
然而,这个功能有两个问题。
- 我想比较
Word8
,但是比较 primops 需要使用Int
。这会导致不必要的分配,因为我被迫存储 64 位Int
而不是一个Word8
.
我已经分析并确认了使用fromIntegral :: Word8 -> Int
占该计划总拨款的 42.4%。
- 我的版本使用 2 次比较、2 次乘法和 2 次减法。我想知道是否有更有效的方法,使用按位运算或 SIMD 指令并利用我正在比较的事实
Word8
.
我之前已经标记过这个问题C/C++
以吸引那些更倾向于位操作的人的注意。我的问题使用 Haskell,但我会接受以任何语言实现正确方法的答案。
结论:
我决定使用
w8_sad :: Word8 -> Word8 -> Int16
w8_sad a b = xor (diff + mask) mask
where diff = fromIntegral a - fromIntegral b
mask = unsafeShiftR diff 15
因为它比我原来的要快unsigned_difference
功能齐全,实现简单。 Haskell 中的 SIMD 内在函数尚未成熟。因此,虽然 SIMD 版本速度更快,但我决定使用标量版本。