Чтобы мне было слегка дискомфортно, давайте эту неделю я попробую писать каждый день какие-то интересные, но возможно поменьше штуки.
Clang PGO (profile guided optimization) -- отличная тулза, чтобы ускорять программы по собранным профайлам в проде. Все браузеры собраны им, очень однообразные workflow отлично для этого подходят, в целом выигрыш стоит ожидать около 5-15%, например, в трейдинге очень любят эти оптимизации. Формат примерно такой:
1. Вы собираете бинарный файл с помощью GCC или Clang
2. Собираете perf record на бинарях
3. Перекомпилируете, а clang/gcc во всех циклах, if statement, которые может опознать, пытается понять, стоит ли там векторизовать код в зависимости от типичного размера цикла или как поставить блоки правильно в зависимости от ветвления кода.
Вот буквально вчера обнаружил такой сниппет в коде:
Далее
Разгадка: если поставить LIKELY/UNLIKELY атрибуты, clang действительно генерирует div инструкцию
https://gcc.godbolt.org/z/WjW5f4dsK. Про gcc вообще молчу, там всё очень плохо
Сгорело.
Пример из очень серьёзной реальности, которой пользуются повсеместно -- absl::Cord.
Вот так микрооптимизируешь, а потом компиляторы сами тебе дают пощёчины. Ещё такой кодген очень тяжело обнаружить (так как он постпродакшн), нашёл просто случайно.
[1] Godbolt
[2] Пример из absl::Cord
[3] Синтетика
Clang PGO (profile guided optimization) -- отличная тулза, чтобы ускорять программы по собранным профайлам в проде. Все браузеры собраны им, очень однообразные workflow отлично для этого подходят, в целом выигрыш стоит ожидать около 5-15%, например, в трейдинге очень любят эти оптимизации. Формат примерно такой:
1. Вы собираете бинарный файл с помощью GCC или Clang
2. Собираете perf record на бинарях
3. Перекомпилируете, а clang/gcc во всех циклах, if statement, которые может опознать, пытается понять, стоит ли там векторизовать код в зависимости от типичного размера цикла или как поставить блоки правильно в зависимости от ветвления кода.
Вот буквально вчера обнаружил такой сниппет в коде:
size_t DivUp(size_t n, size_t m) { return (n + m - 1) / m; }Казалось бы, здесь происходит округление по степеням двойки во вложенном тернарном операторе. Clang производит великолепный кодген, заменяя деление (что логично со степенями двойки) на
size_t RoundUp(size_t n, size_t m) { return DivUp(n, m) * m; }
size_t RoundUpForTag(size_t size) {
return RoundUp(size, (size <= 512) ? 8 : (size <= 8192 ? 64 : 4096));
}
(n + m - 1) & (~m + 1)
cmpq $8193, %rdiТеперь давайте попробуем собрать профиль на синтетике, где распределение размеров в тернарном операторе будет неравномерным. Код можно прочитать здесь.
movl $64, %eax
movl $4096, %ecx
cmovbq %rax, %rcx
cmpq $513, %rdi
movl $8, %eax
cmovaeq %rcx, %rax
leaq (%rdi,%rax), %rcx
decq %rcx
negq %rax
andq %rcx, %rax
retq
Далее
# КомпилируемОткуда, блин, деление? Деление занимает 30-40 тактов. Бенчмарк на моей машине стал на 12% медленнее.
$ clang++ A.cpp -O3 -o test -std=c++17 -stdlib=libc++ -fno-exceptions -g -fprofile-instr-generate -gline-tables-only
# Собираем профайл
$ perf record -b ./test 123 12383 1238
# Конвертируем профиль
$ create_llvm_prof --binary=test --out=code.prof
# Компилируем с профилем
$ clang++ A.cpp -O3 -o test -std=c++17 -stdlib=libc++ -fno-exceptions -g -fprofile-instr-generate -gline-tables-only -fprofile-sample-use=code.prof
$ objdump -dCs --no-addresses ./test | grep div -C 5
mov %rcx,%rax
shr $0x20,%rax
je <RoundUpForTag(unsigned long)+0x74>
mov %rcx,%rax
xor %edx,%edx
div %rsi
sub %rdx,%rcx
mov %rcx,%rax
ret
mov %ecx,%eax
xor %edx,%edx
div %esi
sub %rdx,%rcx
mov %rcx,%rax
ret
Разгадка: если поставить LIKELY/UNLIKELY атрибуты, clang действительно генерирует div инструкцию
https://gcc.godbolt.org/z/WjW5f4dsK. Про gcc вообще молчу, там всё очень плохо
Сгорело.
Пример из очень серьёзной реальности, которой пользуются повсеместно -- absl::Cord.
Вот так микрооптимизируешь, а потом компиляторы сами тебе дают пощёчины. Ещё такой кодген очень тяжело обнаружить (так как он постпродакшн), нашёл просто случайно.
[1] Godbolt
[2] Пример из absl::Cord
[3] Синтетика