【发布时间】:2014-04-22 02:33:25
【问题描述】:
我有一个程序花费大部分时间计算 RGB 值之间的欧几里得距离(无符号 8 位 Word8 的 3 元组)。我需要一个快速、无分支的 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))]
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 版本更快,但我决定使用标量版本。
【问题讨论】:
-
(a - b) & 127工作吗? -
@cdk 我怀疑最好的答案是上一级。解释为什么代码需要 RGB 之间的欧几里得距离以及如何使用该值。也许欧几里得距离的平方就足够了。
(a - b)*(a - b) -
@cdk:我猜 chux 的意思是加法和乘法形成一个以 2^8 为模的环,所以
(a-b)*(a-b) = (b-a)*(b-a) -
为什么需要
abs函数来计算欧几里得距离?你只需要abs就可以达到最高标准。 -
x86 SSE2 有一个
psadbw指令,它为您提供 8 个Word8SAD 操作的总和。因此,如果您将 2 个输入字节零扩展为 XMM 寄存器,psadbw会满足您的需求。它专为对多像素块进行运动搜索的视频编解码器而设计,但对于您的用例,您可以 SSE4.1pmovzxbq将 2 个字节加载到 2 个 qwords 中以并行检查 2 个像素分量。也pmuludq对 2 个结果求平方。我不知道如何让 Haskell 编译器发出它;我根本不知道 Haskell。
标签: performance haskell bit-manipulation simd