Я обычно говняю ржавую мерзость за мерзотный синтаксис, но вот на этой конференции мне понравилось послушать про это вселенское зло:
Видео с конференции С++ Russia. Называется «производительность языка Rust» https://www.youtube.com/watch?v=g9mjctBfo5c
Заявляется, что проверки индексов не дают ржавому векторизовать циклы. Что вообще указывает на какой-то недостаток в архитектуре существующих компиляторов. Я погуглил что-нибудь на эту тему, нашел вот такую ссылку: https://www.nickwilcox.com/blog/autovec/ где автор пытался заставить компилятор работать по человечески на вот таком вот банальном коде
pub fn mix_mono_to_stereo(dst: &mut [f32], src: &[f32], gain_l: f32, gain_r: f32) {
for i in 0..src.len() {
dst[i * 2 + 0] = src[i] * gain_l;
dst[i * 2 + 1] = src[i] * gain_r;
}
}
Посмотрел на выхлоп компилятора, потом накидал похожий код на С++, посмотрел что выдает llvm, gcc… В общем, llvm генерит что-то совершенно демоническое. GCC делает компактный код, но к сожалению совсем не векторит, а впридачу ко всему еще и хрень какую-то творит, например грузит одно и тоже значение дважды из памяти. И проверки индексов тут явно ни при чем. Оно просто генерит дерьмо.
Что должен был сделать векторизатор здорового человека? Для начала предположить что цикл будет пердолиться долго, и подготовить операнды.
#вариант1:
punpcklps xmm0,xmm1
movlhps xmm0,xmm0
#вариант2:
pshufps xmm0,xmm0,0
pshufps xmm1,xmm1,0
затем оценить сколько данных на входе:
cmp edx,4
jb loop_x1
sub edx,4
loop_x4:
невыровненное чтение на самом деле не такое уж разрушительное по скорости, поэтому используем его и драконим значения на пополам.
#вариант1:
movups xmm2,[rsi]
movaps xmm3,xmm2
pshufps xmm2,xmm2,0b01010000
pshufps xmm3,xmm3,0b11111010
pmulps xmm2,xmm0 ;первые 2 пары
pmulps xmm3,xmm0 ;вторые 2 пары
#вариант2:
movups xmm2,[rsi]
movaps xmm4,xmm2
pmulps xmm2,xmm0
pmulps xmm4,xmm1
movaps xmm3,xmm2
punpcklps xmm2,xmm4 ;первые 2 пары
punpckhps xmm3,xmm4 ;вторые 2 пары
Кстати можете прямо щас посмотреть на выхлоп аналогичного «векторизованного» кода на С++ или расте и ужаснуться. Насколько же он, черт подери ужасен. Дальше надо данные записать. Можно сразу проверить каким-то образом что место в буфере есть, этот момент я упущу потому что мне решительно лень и вообще я уверено что разобраться сравнить число с 4 и сделать условный переход каждая собака умеет. А запись вообще-то можно сделать той же командой movdqu. Она на современных процах вполне рабочая. Дальше мы увеличиваем rsi на 16, уменьшаем edx на 4 и делаем jno loop_x4, иначе add edx,4 + jmp loop_x1.
Если бы современные компиляторы не были такими наркоманскими внутре, то реализовать подобный оптимизатор для них можно было бы например за ~20 ящиков хорошего пива. Цикл короткий, мы видим отношение выхода ко входу фиксированно 2 к 1 и повторяется монотонно и шаг итерации 8 байт, или половина регистра. Значит строительная единица - 2 умножения , один вход повторяется, другой чередуется и это повторяется два раза чтоб работать с 16 байтами. Это компилятор сделать смог бы. То есть мою логику абстрактаный компилятор повторить мог бы без каких-то запредельно сложных алгоритмов. Причем не только с массивом, но и например с матрицей и возможно даже со списком или строкой если там внутре не будет какого-то лютого ветвления и логики. Если добавить маскируемые векторные команды, которых в x86 нету в нормальном виде, дело пойдет еще проще.
В случае с проверкой индексов, можно как-то научить компилятор делать выводы, например что если индекс нарастает монотонно, то в векторизованном цикле надо сразу проверять его на N-кратное увеличение. Опять же, это не нерешаемая задача.














