У меня есть программа, которая проводит большую часть своего времени, вычисляя евклидово расстояние между значениями RGB (3 кортежа беззнакового 8-битного Word8
). Мне нужна быстрая, безветвленная функция без абсолютной разности без знака так, чтобы
unsigned_difference :: Word8 -> Word8 -> Word8
unsigned_difference a b = max a b - min a b
особенно,
unsigned_difference ab == unsigned_difference ba
Я придумал следующее, используя новые примулы из GHC 7.8:
-- (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
, и прост в реализации. Принадлежности SIMD в Haskell еще не достигли зрелости. Поэтому, пока SIMD-версии работают быстрее, я решил использовать скалярную версию.