Contenuto
Store Forwarding penalties Представьте вы пишете по указателю данные. Далее по этому указателю или где-то рядом вы читаете эти данные. Современные процессоры нынче очень хитрые и могут не читать память при такой инструкции. Это называется «store-to-load forwarding» и ускоряет программы, поскольку load инструкции не нужно ждать, пока данные будут записаны в кэш, а затем снова считываться. Пример такой последовательности очень простой: movaps %xmm0, (%rsp) # Сохраняем 16 байт по адресу mov 2(%rsp), %eax # Читаем 4 байта с адреса +2 Процессоры могут в регистр %eax сразу писать из регистра %xmm0 не дожидаясь пока в память будет что-то записано. Там есть всякие пенальти, если данные поменялись в другом треде походу этой оптимизации, но речь немного не о них сегодня. Так получилось, что мы нашли какие-то безумные штрафы на Intel и Arm. Например Intel: 1. Загрузить 2 байта write_unaligned<2>(ptr) 2. Прочитать второй следующей инструкцией read_unaligned<1>(ptr + 1) Работает в 2-3 раза дольше, чем 1. Загрузить 2 байта write_unaligned<2>(ptr) 2. Прочитать первый read_unaligned<1>(ptr) Решил сделать бенчмарк, который загружает Х байт (X = 2,4,8,16), читает Y байт (Y = 1,2,4,8,16) по оффсету Z (Z = 0..15) с условием, что Y + Z <= X. Получились цифры как на картинках для AMD Rome, Intel Skylake и Graviton 2. Выводы 1. AMD всегда хорошо применяет эту оптимизацию 2. Intel плохо работает, если грузить 2-й байт при загрузке 2. Также плохо, если при загрузке 16 байт мы читаем 4/8, и они переходят границу в 8 байт (размер регистра, походу) 3. Arm работают плохо, кроме выравнивания по 0, reg_size/2. Это важно всяким small string optimization, rope/cord, compressors, потому что они используют стек как данные и одновременно как идентификаторы, а большие или маленькие они. Решил сделать бенчмарк, на Rust. https://github.com/danlark1/store_forwarding Agner Fog писал про это, но мало. Если поменять layout absl::Cord на обратный, Arm ускоряется, по табличке грузить 1 байт по оффсету 0 лучше, чем по 15 :)