Что-то я ничего не писал почти 2 недели. А в целом потому что никаких красивых историй не происходило, не хотелось читать статьи, а работать надо было по 60-70+ часов в неделю. Будем менять, но историю по перформансу я вам привёз, конечно же.
В Google вместо LZ4 мы используем в большинстве мест Snappy, очень похожий на LZ4 кодек, тоже формат вида двух операций
1. Скопировать байты (литералы в терминах LZ4)
2. Скопировать по оффсету те байты, что уже были разжаты (матчи в терминах LZ4)
Отличия Snappy от LZ4 в том, что есть байты теги, мол, инструкции, что надо делать (копировать байты или копировать по оффсету), когда как в LZ4 они чередуются.
LZ4 доказал со временем, что у него скорость разжатия получше, но легаси, все дела, вряд ли мы избавимся от Snappy даже за десять лет. Используется везде правда.
И код понятное дело очень приложенный. Я комитил много туда оптимизаций, мы нашли удивительную ещё одну.
Когда мы копируем байты по оффсету (в этом месте мы знаем, что, копирование происходит меньше чем на 64 байта, так как размер в теге инструкции всегда запакован в 6 битах), то у нас написан такой код
memmove на 64 идёт, потому что это четыре 16 байтных load/store и не вызывается дополнительной функции. Так делать можно, потому что аллоцируют чуть больше, чтобы записать после конца.
На самом деле в идеале код мог бы выглядеть как
А если ещё подумать и почитать кода, то memmove можно заменить на memcpy. Для 64 версии используется memmove, так как указатели могут друг на друга накладываться с размером 64, но не с размером len.
Мы решили посмотреть, какое раcпределение этих len, оказалось, что почти все под 32, большинство под 16 (вы тоже можете из открытых бенчмарков). А мы аж целых 64 копируем для худшего случая.
То есть нам примерно дано:
Well, мы попробовали
Мы получили смешанные результаты
Skylake: +3% on average
Cascade Lake: +2% on average
AMD Zen 2: +20% on average
AMD Zen 3: +25% on average
Neoverse N1 (ARM): +14% on average
Neoverse N2 (ARM): +11% on average
И дальше возникает интересный вопрос, что ж случилось. Я перепроверил и оказалось, что действительно AMD новых поколений разжимают медленнее, чем Intel старых, но сжимают зато быстрее и в целом считаются CPU так процентов на 20 побыстрее.
Каким-то чудом Intel научился понимать, что записи по кешлиниям с байта 32 по 64 очень редко или поздно используются, а вот AMD и ARMы так не научились.
Конечно же, никто нам не расскажет, что произошло. Но это самый большой гэп между Intel и AMD, который я когда либо видел. Увидеть 25% прирост на одном и только 2% на другом это прям удивляет.
И всё ещё удивительно, что тут есть оптимизации. На скейле это очень много денег.
Чем я ещё занимался?
ZSTD соптимизировал для ARMов (в том числе ваших макбуках) на 5%. Готовится огромная история, что произошло, потому что я выбил лучше перф процентов на 20 на Apple M1 по таким функциям как memcmp и так далее. Выбил +5% перфа для хэш таблиц Google Abseil. И кажется ещё дойду до SIMDJSON. Однозначный вывод сделал, ни в Apple, ни в ARM не сидят инженеры, которые понимают за software performance своей архитектуры. Либо я открыл какой-то ящик пандоры. Пока я валидирую у своих коллег, что я не сошёл с ума, пишу уже неделю блог. Какой-то sneak peek вариант смотрите в ZSTD. Да, снова ассемблер армов, потому что это красиво, мать вашу. Я нанял себе аниматора, потому что понял, что про SIMD надо рассказывать с картинками.
В Google вместо LZ4 мы используем в большинстве мест Snappy, очень похожий на LZ4 кодек, тоже формат вида двух операций
1. Скопировать байты (литералы в терминах LZ4)
2. Скопировать по оффсету те байты, что уже были разжаты (матчи в терминах LZ4)
Отличия Snappy от LZ4 в том, что есть байты теги, мол, инструкции, что надо делать (копировать байты или копировать по оффсету), когда как в LZ4 они чередуются.
LZ4 доказал со временем, что у него скорость разжатия получше, но легаси, все дела, вряд ли мы избавимся от Snappy даже за десять лет. Используется везде правда.
И код понятное дело очень приложенный. Я комитил много туда оптимизаций, мы нашли удивительную ещё одну.
Когда мы копируем байты по оффсету (в этом месте мы знаем, что, копирование происходит меньше чем на 64 байта, так как размер в теге инструкции всегда запакован в 6 битах), то у нас написан такой код
const void* from =
tag_type ? reinterpret_cast<void*>(op_base + delta) : old_ip;
memmove(op_base + op, from, 64);
ip += len;
memmove на 64 идёт, потому что это четыре 16 байтных load/store и не вызывается дополнительной функции. Так делать можно, потому что аллоцируют чуть больше, чтобы записать после конца.
На самом деле в идеале код мог бы выглядеть как
memmove(op_base + op, from, len);
А если ещё подумать и почитать кода, то memmove можно заменить на memcpy. Для 64 версии используется memmove, так как указатели могут друг на друга накладываться с размером 64, но не с размером len.
Мы решили посмотреть, какое раcпределение этих len, оказалось, что почти все под 32, большинство под 16 (вы тоже можете из открытых бенчмарков). А мы аж целых 64 копируем для худшего случая.
То есть нам примерно дано:
memcpy(dst, src, len);
len <= 64
len <= 32 в 99% случаях, len <= 16 в 95% случаях
[dst, dst + len) и [src, src + len) непересекающиеся, но если + 64, то могут
dst+64 валидный регион памяти
Well, мы попробовали
// Если len <= 32, это правильно.
memmove(dst, src, 32);
// Если len > 32, мы не перезаписали байты так как [dst, dst + 32) и [src, src + 32) не пересекаются.
if (UNLIKELY(len > 32)) {
memmove(dst + 32, static_cast<const uint8_t*>(src) + 32, 32);
}
Мы получили смешанные результаты
Skylake: +3% on average
Cascade Lake: +2% on average
AMD Zen 2: +20% on average
AMD Zen 3: +25% on average
Neoverse N1 (ARM): +14% on average
Neoverse N2 (ARM): +11% on average
И дальше возникает интересный вопрос, что ж случилось. Я перепроверил и оказалось, что действительно AMD новых поколений разжимают медленнее, чем Intel старых, но сжимают зато быстрее и в целом считаются CPU так процентов на 20 побыстрее.
Каким-то чудом Intel научился понимать, что записи по кешлиниям с байта 32 по 64 очень редко или поздно используются, а вот AMD и ARMы так не научились.
Конечно же, никто нам не расскажет, что произошло. Но это самый большой гэп между Intel и AMD, который я когда либо видел. Увидеть 25% прирост на одном и только 2% на другом это прям удивляет.
И всё ещё удивительно, что тут есть оптимизации. На скейле это очень много денег.
Чем я ещё занимался?
ZSTD соптимизировал для ARMов (в том числе ваших макбуках) на 5%. Готовится огромная история, что произошло, потому что я выбил лучше перф процентов на 20 на Apple M1 по таким функциям как memcmp и так далее. Выбил +5% перфа для хэш таблиц Google Abseil. И кажется ещё дойду до SIMDJSON. Однозначный вывод сделал, ни в Apple, ни в ARM не сидят инженеры, которые понимают за software performance своей архитектуры. Либо я открыл какой-то ящик пандоры. Пока я валидирую у своих коллег, что я не сошёл с ума, пишу уже неделю блог. Какой-то sneak peek вариант смотрите в ZSTD. Да, снова ассемблер армов, потому что это красиво, мать вашу. Я нанял себе аниматора, потому что понял, что про SIMD надо рассказывать с картинками.