ARM, который мы заслужили
Когда люди только в последнее десятилетие смотрели на SIMD x86_64, параллельно развивался AArch64, на который очень мало обращали внимания. Сейчас обращают больше и хочется рассказать одну историю, о которой редко говорят. И вообще, про ARM никто не пишет, надо исправляться!
У AArch64 намного более серьёзный instruction set, когда дело доходит до conditional moves. В x86_64 есть
И в целом всё, есть только cmov. А вот в AArch64 аналогом является
oper может быть равно eq, ne и так далее. То есть csel if equal, csel if not equal и так далее.
Но помимо conditional select есть ещё многие другие, такие как conditional select increase/inverse/negate:
Но по мне самая недооцененная иструкция это conditional compare, дада, выглядит оно так
То есть можно манипулировать флагами сравнений после cmp через и таким образом проносить флаги дальше. Скажем, чтобы сравнить 16 байт между собой можно сделать
Такой трюк можно использовать, чтобы проносить результаты сравнений. Учитывая то, что ccmp занимает 1 цикл на Neoverse-n1 как и cmp, то это помогает сравнивать регионы памяти с тем же ptest/movmask на x86.
Вообще одно из самых сложных отличий SIMD x86 и ARM Neon заключается в movemask инструкции. Если коротко, для 16 байт оно забирает верхний бит каждого байта (если суффикс b) и обычно используется так
На Arm такой инструкции нет и эмулируется 3-4 инструкциями. Это огромная боль, когда ты хочешь мигрировать одно на другое, теряешь много перфа.
В итоге это один трюк как на AArch64 за примерно такое же количество циклов сравнивать регионы памяти.
Можете оценить сколько movemask используется в ClickHouse [2] -- дада, я сделал огромный issue, чтобы наконец-то починить все перформанс проблемы на AArch64.
В след раз расскажу ещё пару трюков, так как через movemask можно ещё находить первый несовпавший бит в коде выше через всякие count trailing zeros, и текущий трюк уже не работает.
Полезные ссылки.
[1] Этот трюк используется в memcmp в glibc
[2] Можете оценить сколько movemask используется в ClickHouse
[3] Оптимизации через csinc в Google Snappy
[4] Neoverse-N1 optimization guide
[5] Movemask на x86
[6] Как я чинил movemask на AArch64 в Vectorscan, который выдавал некорректные результаты
Когда люди только в последнее десятилетие смотрели на SIMD x86_64, параллельно развивался AArch64, на который очень мало обращали внимания. Сейчас обращают больше и хочется рассказать одну историю, о которой редко говорят. И вообще, про ARM никто не пишет, надо исправляться!
У AArch64 намного более серьёзный instruction set, когда дело доходит до conditional moves. В x86_64 есть
cmov, когда вы двигаете регистр в другой в зависимости от результатов предущего сравненияcmp %rax, %rcx
cmovzq $REG1, $REG2 # move reg1 into reg2 if flag ZF was set
И в целом всё, есть только cmov. А вот в AArch64 аналогом является
csel, conditional select.cmp $something1, $something2
csel $reg1, $reg2, $reg3, oper # reg1 = oper ? reg2 : reg3
oper может быть равно eq, ne и так далее. То есть csel if equal, csel if not equal и так далее.
Но помимо conditional select есть ещё многие другие, такие как conditional select increase/inverse/negate:
csinc $reg1, $reg2, $reg3, oper # reg1 = oper ? reg2 : (reg3 + 1)
csinv $reg1, $reg2, $reg3, oper # reg1 = oper ? reg2 : ~reg3
csneg $reg1, $reg2, $reg3, oper # reg1 = oper ? reg2 : -reg3
Но по мне самая недооцененная иструкция это conditional compare, дада, выглядит оно так
cmp $something1, $something2
ccmp $reg1, $reg2, $flags, oper # flags = oper ? (cmp $reg1, $reg) : $flags
То есть можно манипулировать флагами сравнений после cmp через и таким образом проносить флаги дальше. Скажем, чтобы сравнить 16 байт между собой можно сделать
ldp x3, x4, [src1] # load 8 byte into x3, load 8 byte into x4
ldp x5, x6, [src2] # load 8 byte into x5, load 8 byte into x6
cmp x3, x5 # compare x3 and x5
ccmp x4, x6, 0, eq # if eq, compare x4, x6, otherwise flags are 0
b.ne L_SOMEWHERE # if flags are zero, branch
Такой трюк можно использовать, чтобы проносить результаты сравнений. Учитывая то, что ccmp занимает 1 цикл на Neoverse-n1 как и cmp, то это помогает сравнивать регионы памяти с тем же ptest/movmask на x86.
Вообще одно из самых сложных отличий SIMD x86 и ARM Neon заключается в movemask инструкции. Если коротко, для 16 байт оно забирает верхний бит каждого байта (если суффикс b) и обычно используется так
pcmpeqb %xmm1, %xmm2 # compare 2 regions, those who are equal, set to 0xff, otherwise 0
pmovmskb %xmm2, %ecx # move high bits, those who are equal, set to 1
cmp %ecx, 0xffff # compare
jne L_SOMEWHERE # jump
На Arm такой инструкции нет и эмулируется 3-4 инструкциями. Это огромная боль, когда ты хочешь мигрировать одно на другое, теряешь много перфа.
В итоге это один трюк как на AArch64 за примерно такое же количество циклов сравнивать регионы памяти.
Можете оценить сколько movemask используется в ClickHouse [2] -- дада, я сделал огромный issue, чтобы наконец-то починить все перформанс проблемы на AArch64.
В след раз расскажу ещё пару трюков, так как через movemask можно ещё находить первый несовпавший бит в коде выше через всякие count trailing zeros, и текущий трюк уже не работает.
Полезные ссылки.
[1] Этот трюк используется в memcmp в glibc
[2] Можете оценить сколько movemask используется в ClickHouse
[3] Оптимизации через csinc в Google Snappy
[4] Neoverse-N1 optimization guide
[5] Movemask на x86
[6] Как я чинил movemask на AArch64 в Vectorscan, который выдавал некорректные результаты
GitHub
[Umbrella] Make ClickHouse great on AArch64 · Issue #37005 · ClickHouse/ClickHouse
Objective Improve performance of ClickHouse for AArch64 and share the knowledge on how to do that. Plan This is list of what can be done to improve performance for AArch64. Contrib/third_party libr...
👍50🔥23