commit -m "better"
2.96K subscribers
868 photos
105 videos
3 files
2.07K links
just random thoughts
Download Telegram
Отличный текст от Google, про оптимизацию memcpy: https://storage.googleapis.com/pub-tools-public-publication-data/pdf/4f7c3da72d557ed418828823a8e59942859d677f.pdf #perf

Сравниваются 2 подхода - вручную написанный ассемблер, и автоматически настроенный(под нагрузку) код на С++. Второй способ побеждает(1% перфа Google). Я прямо ОЧЕНЬ советую прочесть хотя бы первую половину статьи, про использование SAT solver для автоматического построения алгоритма из базовых кубиков, это прямо огонь.

Так же дается описание того, как можно настроить memcpy под свою нагрузку.

На мой взгляд, конечно, оптимизацией memcpy должны авторы CPU(кто сказал "rep movsb"?):

1) Не нужно иметь сложный код от архитектуры к архитектуре(поэтому такой memcpy можно всегда инлайнить по месту)
2) Memcpy в процессоре имеет больше доступа к состоянию CPU, и может делать какие-то архитектурные оптимизации. Например, если поспекулировать, то memcpy может работать на уровне протокола синхронизации кешей - CPU читает cache line, и, вместо того, чтобы "прокачивать" его через регистры в output cache line, сразу пишет этот cache line по нужному адресу в свой cache, чтобы протокол синхронизации кешей сбросил этот cache line по нужному адресу в памяти. memcpy в CPU может игнорить write ordering.
3) Профит получит не только Google(не у всех есть 10 студентов, которых можно пустить на решение такой проблемы).

Тут, конечно, есть некоторые сложности(например, взаимодействие такой сложной инструкции и прерываний, взаимодействие с page cache), но они, кажется, решаемы. К сожалению, в x86 rep movsb проигрывает другим реализациям:

https://stackoverflow.com/questions/43343231/enhanced-rep-movsb-for-memcpy

Почему? Почему Intel оптимизирует AES, который не виден cluster wide, но не оптимизирует memcpy, который виден?

Для ARM у меня пока нет данных, но инструкции уже завезли:

https://news.ycombinator.com/item?id=28601386
#gold #terminal #foot #perf

https://zed.dev/

CRDT - прикольно, tree sitter - тоже(хочу его интегрировать в свой редактор), GPU accelerated GUI - ну такое. Я вот раньше думал, что GPU accelerated GUI это что-то очень крутое, а потом понял, что ерунда это все. IMHO c GPU GUI нужно собрать всего 3 программы - wayland compositor(потому что он много туда-сюда гоняет пикселей), browser, terminal(уже опционально).

Знаете, какая самая дорогая для CPU задача при отрисовке html странички в браузере?

Залить страницу белым фоном!

Ладно, это не совсем так(думаю, отрисовать много текста ЧУТЬ дороже), но мне же нужен шок-контент. Это очень дорогая операция для CPU, потому что он ограничен в своей полосе по памяти, и пишет нолики(или 0xFF) один за другим. Прикиньте, выполнить цикл 3000*2000*3*60rps раз - это сколько нужно тактов? GPU это делает очень быстро - у нее более широкий доступ к памяти, и дофига тупых ядер, которые медленно(но суммарно ОЧЕНЬ быстро(можно разбить всю заливаемую область по числу ядер GPU)) выполняют цикл for (i = x1; i < x2; ++i) mem[i + y*dimx] = 0xFF;

Cобственно, это самый важный факт, который нужно знать про GPU, чтобы понимать, почему они рулят в ML и прочей подобной нечисти, но об этом в другой раз.

Отрисовка текста - это просто bit blit(https://en.wikipedia.org/wiki/Bit_blit, а кто-то помнит, что это?) нескольких текстур на GPU. (Ладно, не совсем так, в оригинальном bit blit не было смешивания с альфа-каналом).

Вообще, конечно, рендер GUI на GPU - это из пушки по воробьям, тупой аппаратный bit blit справился не хуже бы, и стоил бы при той же скорости в 100 раз дешевле. Но имеем, что имеем.

Terminal emulator Foot, например, делает эту задачу во все мои 16 потоков CPU, и работает сравнимо с alacritty(https://codeberg.org/dnkl/foot/wiki/Performance). #foot

"Крутизна" 2D GPU рендеринга несколько преувеличена. Тупой 2D render для консоли(отрисовать сетку из прямоугольничков с текстом) - 20 строк кода(если не считать setup текстур со шрифтами, и все такое). Я однажды их даже написал - https://github.com/pg83/ted/blob/3c3f54a69b806bd7eb96f4c56189ce2a7f0507c5/gl#L325 Вот, inner loop 2D GPU accelerated rendering, не хуже, чем в Alacritty. Не такой sexy, конечно, на fixed pipeline, но я люблю тупые решения. Зачем я это сделал? Мне было интересно, насколько generic я сделал widget для редактирования текста. Вот, я перенес его из консоли в OpenGL, за 100 строк кода. Пользоваться не стал, незачем :)
https://www.phoronix.com/news/Go-1.21-RC

#perf

Меня расстраивают игры с PGO в Go.

Go сейчас - sweet point между скоростью компиляции и качеством генерируемого кода. Это, кстати, одна из тех причин, по которым мне НРАВИТСЯ писать на Go. Не просто "ок", а именно нравится - вспоминается детство, безумно быстрый borland pascal (безумно - потому что он рожал код моментально на моем pentium 75, а не на современных многоядерных монстрах), и это ощущение "потока", когда от изменения строчки кода до ее проверки проходит нисколько времени.

Но, как это обычно бывает, у разрабов Go закончились идеи на тему "как же ускорять получающийся код, не замедляя компилятор", а оплачивать ипотеку как-то надо, вот и пошли в дело не самые приятные идеи.

Много раз писал и буду писать, что я почти всегда предпочту компилятор, генерирующий код в 2 раза медленнее, но делающий это в 20 раз быстрее. Потому что ускорить целевую программу в 2 раза, в целом, решаемая задача, а нервные клетки не восстанавливаются.
👍12👎9🤔4💯1
commit -m "better"
#wasm #wasi #bootstrap Однажды начав, бывает сложно остановиться. Собрал еще 4 wasm рантайма: https://github.com/wasmx/fizzy https://github.com/wasm3/wasm3 https://github.com/WasmEdge/WasmEdge https://github.com/tetratelabs/wazero Из них только wasmedge…
#perf #wasm

Ну вот я, с помощью лома и такой-то матери, собрал нетривиальное приложение, которое actually do something - компрессор brotli.

И потестил его в разных runtime, которые у меня уже были, vs. нативное выполнение:

pg# cat g | time .../brotli -1 -c > qw.brotli.1
real 0m 0.50s
user 0m 0.46s
sys 0m 0.03s
pg# cat g | time .../wasmedge \
--enable-all \
.../brotli -1 -c > qw.brotli.2
real 1m 2.35s
user 1m 2.27s
sys 0m 0.04s
pg# cat g | time .../iwasm \
--llvm-jit \
.../brotli -1 -c > qw.brotli.3
real 0m 2.71s
user 0m 5.86s
sys 0m 0.06s
pg# cat g | time .../iwasm \
--fast-jit \
.../brotli -1 -c > qw.brotli.4
real 0m 1.21s
user 0m 2.20s
sys 0m 0.06s


Для того, чтобы сделать какие-то реальные выводы, у меня пока мало точек, но начало положено!

Про плохой результат wasmedge - это, кажется, что-то странное, скорее всего, я его криво собрал.
🔥9
commit -m "better"
#perf #wasm Ну вот я, с помощью лома и такой-то матери, собрал нетривиальное приложение, которое actually do something - компрессор brotli. И потестил его в разных runtime, которые у меня уже были, vs. нативное выполнение: pg# cat g | time .../brotli -1…
#wasm #perf

Разобрался с отставанием wasmedge.

Чтобы все работало быстро, нужно пройтись по wasm с помощью его AOT компилятора, и тогда получается вот такой результат:

real  0m 0.62s
user 0m 0.57s
sys 0m 0.03s

Это уже довольно близко к нативной скорости выполнения того же бинаря.

Впрочем, скорость его AOT не впечатляет:

pg# time .../bin/wasmedgec \
--optimize 3 \
--enable-threads \
.../brotli brotli
[2023-06-29 01:55:52.636] [info] compile start
[2023-06-29 01:55:52.669] [info] verify start
[2023-06-29 01:55:52.710] [info] optimize start
[2023-06-29 01:55:56.632] [info] codegen start
[2023-06-29 01:56:01.680] [info] output start
[2023-06-29 01:56:01.682] [info] compile done
[2023-06-29 01:56:01.686] [info] output start

real 0m9.117s
user 0m9.063s
sys 0m0.041s
🔥13
Вышел python 3.12

Думаю, самая крутая его фишка - это https://docs.python.org/3.12/howto/perf_profiling.html#perf-profiling

Одной строкой - интеграция с perf record/report.

Да, да, в python появился нормальный профайлер!

Тут, конечно, можно устроить срач на тему, что, если вы запускаете профайлер для python, то вы заранее делаете что-то не так. Но, тем не менее, штука очень крутая.
👍205🔥4👎1🤔1
https://ubuntu.com/blog/ubuntu-performance-engineering-with-frame-pointers-by-default #gold #LTO #perf

В ubutu включили -fno-omit-frame-pointer для 64 битных систем, по умолчанию.

Хорошая новость, из разряда "долго тупили, но опомнились, и сделали по уму".

Много раз писал, и буду писать, что вам, в среднем, не нужны оптимизации "последних 5% перфа", которые сильно усложняют отладку, и ухудшают время сборки:

- LTO. Коллеги, если вы включаете LTO в своих сборках, то вы подписываетесь на то, что будете регулярно чинить проблемы, которые просто не возникают у других людей. По двум причинам:

* Вы начинаете использовать код, который используется меньше, чем 1% пользователей вашего компилятора. Этот код хуже отлажен, в нем больше багов, он чаще будет генерить некорректный код в результате.

* Вы подвергаете свою кодовую базу стрессу, который мог совсем не предполагаться теми, кто пишет весь остальной код в вашем репозитории. Представьте себе монорепу, в которой вы пишете маленькую программу, она использует кучу библиотек из монорепы, и вы решили собрать ее с LTO. Вы наложили новый контракт на используемый вами код, и его никто не будет соблюдать, кроме вас.

Это все звучит очень теоретически, пока вы не начнете разгребать проблемы, когда компилятор что-то там хорошо "разынлайнил", и где-то закешировал регистр FS, потому что не ожидает перед собой переключение стеков, а кто-то из ваших коллег творчески поюзал код, переключающий контексты (тот или иной движок корутин, например).

Людям, которые принимают решение о выкатке кода в прод, дам совет. Если вам кто-то приносит 5% перфа, полученного за счет LTO, шлите его в хуй, спросите, за чей счет банкет, и кто будет этот код обслуживать дальше, и чинить всякие веселые баги в компиляторе, и в вашей кодовой базе. Это совершенно не бесплатные 5%, вы за них будете платить все время жизни вашего софта.

LTO имеет смысл для браузера, который собирается и отлаживается очень большой командой, и используется много кем.

Твоей программе, username, он не нужен.

- fomit-frame-pointer. Отладка затрудняется, корки совершенно бессмысленные, стектрейсы для записи в лог стоят дорого. Современные компиляторы сводят ущерб от frame pointer к минимуму.

Я иду дальше, и запрещаю UB в следующих случаях:

https://github.com/pg83/ix/blob/main/pkgs/lib/build/opt/safe/ix.sh#L4

Да, я отключаю UB в переполнениях с целыми числами, и выключаю strict aliasing.

Желающие отлаживать всякие интересные (нет) корки могут себе это отключить, на всю систему, или на отдельный пакет.
👍17🤔9👎3🔥2💩2🫡2🥰1
https://nickb.dev/blog/the-dark-side-of-inlining-and-monomorphization/ #perf

Зумеры, в очередной раз, переизобретают и переописывают то, что всем заинтересованным людям давно известно - чрезмерный inline-инг (современное его название - мономорфизация, но кому какое дело?) - не только полезно, но и очень вредно.

Статью я проглядел мельком, поэтому не знаю, был ли там озвучен самый главный вред от чрезмерного встраивания (загрязнение кешей для кода процессора, и, тем самым, замедление на реальных задачах (но не на бенчмарках)), поэтому озвучиваю его сейчас, да.
😁7🤮3🔥2
commit -m "better"
#fast_python https://www.opennet.ru/opennews/art.shtml?num=60352 А вот, пожалуйста, новый-кленовый JIT для python. Вот цифры про perf: "По сравнению с традиционным JIT-инструментарием (LLVM -O0) предложенный JIT обеспечивает в 100 раз более быструю генерацию…
#perf #fast_python

Мне стало интересно, что там за такой новый-кленовый JIT.

И почему он должен собираться именно LLVM? Это звучало вообще странно - как так, в runtime кодогенератор от LLVM не нужен, а во время сборки - нужен? Это что за магия такая?

Все оказалось довольно просто, достаточно было прочесть https://github.com/brandtbucher/cpython/blob/justin/Tools/jit/build.py, ну и немножко https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110899 (по этой ссылке содержится просьба к GCC добавить недостающие кусочки к gcc, чтобы можно было использовать не LLVM, а gcc)

(Немного в сторону от основной темы, авторы gcc, конечно, встали на дыбы, потому что как это так, не за ними все повторяют, а им надо что-то повторить за clang?

"Shouldn't we first discuss whether any of this, or particular aspects of it, are a good idea at all, before specifying behaviour that a random compiler happened to implement?")

В общем, суть в следующем: а давайте мы сконпелируем исходный текст интерпретатора с набором волшебных ключей, чтобы получившийся объектный код можно было растащить на кусочки, а потом из них собрать объектный код для байткода python?

Вот, допустим, у нас есть такой кусок байткода (это не питонячий байткод, но так будет понятнее):

...
mov A, B
...


Дальше в интерпретаторе есть огромный switch/case, где написано:

... 
if (opcode == 'mov') {
auto op1 = getNextOp();
auto op2 = getNextOp();
auto& g = interpContext->globals;
g[op1] = g[op2];
}
...


Далее мы компилируем этот файл в объектный код, и аккуратно вырезаем из него код, соответствующий только этому одному блоку
if () { ... }


После чего мы проходимся по всему байткоду, который был построен cpython, и заменяем в нем отдельные инструкции на вот эти вот кусочки заранее скомпилированного нативного кода.

Затем проходимся сверху скопипасченного объектного кода парой регулярок, и получаем код, который реально может быть выполнен!

От LLVM тут нужно, чтобы он сумел скомпилировать цикл интерпретатора для другого calling convention, а, конкретно, для такого специального CC, когда все регистры сохраняет callee. Видимо, так сильно проще получить код, который можно нарезать на независимые кусочки, которые далее можно "просто" конкатенировать. Я на получившиеся кусочки глазами не смотрел, поверил автору на слово.

UPD: вот paper, где описана эта техника copy and patch - https://arxiv.org/pdf/2011.13127.pdf , from https://aha.stanford.edu/sites/g/files/sbiybj20066/files/media/file/aha_050621_xu_copy-and-patch.pdf

UPD: утащю из комментов - https://t.iss.one/c/1469934025/21379
🔥12👍94🤔2🤯2😱1
https://blog.polybdenum.com/2024/01/17/identifying-the-collect-vec-memory-leak-footgun.html #perf

Текст про то, как стандартная библиотека #rust оказалось слишком умной, и переиспользовала память Vec после того, как этот Vec был превращен в range, а из этого range снова в Vec.

Насколько я понял, там случилось ажно две смешных штуки оптимизации:

1) into_iter().collect() переиспользовало тот же самый вектор, хотя можно было бы ожидать, что это будет похоже на shrink_to_fit()

2) into_iter().map().collect() переиспользовало старую выделенную память исходного вектора, если размер нового элемента <= размера старого элемента. Поэтому, если размер нового элемента был раз в 10 меньше, чем исходного элемента, то происходил дикий пережор памяти.

Оптимизации, конечно, семантически корректные, но спорные.
🔥8🤔6👍4🆒3
#perf

Когда-то, году в 2007, когда я "изобретал" равномерную балансировку нагрузки, и последовательно испытывал и отбрасывал разные схемы, один очень умный человек мне сказал, что я занимаюсь херней, и все уже давно придумано за нас.

Алгоритм, который он мне тогда рассказал, был устроен предельно просто - надо опросить 3 случайных хоста, и выбрать наименее нагруженный из всех трех (например, по длине очереди).

Я тогда ссыканул так сделать, потому что сеть была еще хуже, чем сейчас, хотя это сложно себе представить, и, если бы я так сделал на 3 - 4 уровнях балансировки, это легко бы отъело 100 - 150ms времени ответа.

На днях натолкнулся на https://brooker.co.za/blog/2012/01/17/two-random.html. Там написано, что надо брать не 3 случайных хоста, а два, уже на 10 бекендах разница становится заметной.

https://www.eecs.harvard.edu/~michaelm/postscripts/handbook2001.pdf - какие-то размышления, почему именно 2.

Век живи, век учись.
👍18🤔86🔥4
https://www.opennet.ru/opennews/art.shtml?num=60736 - пишут, что в новой freebsd появились новые-кленовые строковые (семейство str*, mem*) функции, оптимизированные вручную, для ассемблера x86_64. #perf

Со ссылкой на https://freebsdfoundation.org/blog/a-sneak-peek-simd-enhanced-string-functions-for-amd64/, но там написано только про пререлиз 15.0, а про 14.0 и 13.3 не написано, собственно, я пока их код не нашел, и не смотрел.

Раньше:

* Ты мог использовать LGPL вариант из glibc, без возможности статической линковки.

* Ты мог использовать всратые заглушки от musl (TBD - написать про пару программ, которые у меня проводили 30% CPU time в memcpy от musl)

* Ты мог использовать подход Google, это когда они написали кучу вариантов memcpy, с разными размерами блоков, в надежде, что LTO/PGO выберет нужный вариант и без всякого ассемблера. https://t.iss.one/itpgchannel/1113 https://t.iss.one/itpgchannel/54 Увы, не все имеют ресурсы, чтобы собирать весь код с LTO/PGO.

* Ты мог использовать asmlib от Agner Fog, со всеми вытекающими. Например, этот сумрачный гений не умеет в систему контроля версий, и вообще, в версионирование своих артефактов. Вот url к asmlib, любой версии, не очень удобно для CI - https://www.agner.org/optimize/asmlib.zip Как хотите, так и ебитесь с этим. Ну и еще они под GPL, со всеми вытекающими.

Собственно, я использовал вариант от Agner Fog, потому что у меня все собирается из исходников, а бинарные артефакты я не распространяю.

Но, если новость - правда, то это просто какое-то счастье, потому что все пользователи musl получат нормальную (по скорости, и по лицензии) альтернативу заглушкам из musl.
🔥12👍82🤯1
commit -m "better"
https://www.opennet.ru/opennews/art.shtml?num=60871 В копилочку. #fork О-че-редь. Огромная очередь. За любым популярным проектом. Стоит лишь раз ошибиться. И, в целом, это очень хорошо, потому что нехуй набирать пользователей под лозунгом "халява", а потом…
#perf #fork

Авторы форка Redis занялись его оптимизацией, и запилили неплохую статью по этому поводу.

https://valkey.io/blog/unlock-one-million-rps-part2/

Там представлена интересная техника хождения по ссылочным структурам данных, за счет параллельного хождения одновременно по нескольким структурам получилось лучше использовать кеш памяти.

Другое дело, что там надо, прежде всего, выкинуть списочные структуры, ну или выделять память для узлов в каком-нить пуле, чтобы она и так рядом ложилась.
👍22
#gold

> Одмен, а в чем прикол делать дистр с фулл статик линковкой? У тебя пост на канальчике про это есть какой-нить?

У меня про это много чего написано, достаточно погрепать канал по "статическая".

В целом, аргументация такая:

* динлинковка - это вынужденная мера, появившаяся, когда у компухтеров было мало памяти
* динамическая линковка - сложнее, тулинг хуже (санитайзеры требуют, чтобы почти весь код (кроме того, что перехватывается), был собран статически использовать msan с кучей .so - заградительно сложно, отладчик работает хуже, код ходит через GOT/plt, а не напрямую).
* динамическая линковка - сложнее, например, во взаимодействии с другими системами (fork(), threads, tls, etc)
* динамический загрузчик - сложный, а еще он #suid, есть известные проблемы с безопасностью
* плагины лежат хз где, и часто их не хватает, потому что приложения не могут сказать, чтобы в gstreamer был такой-то #plugins
* в целом, загружать в runtime сторонний код в свое приложение (не в песочнице) - очень странная идея, потому что CI с ним, у вас, скорее всего, не было, и как он будет ездить по вашим данным - неизвестно. Для плагинов сейчас норм решение - #WebAssembly в песочнице, или там https://github.com/libriscv/libriscv
* code bloat, больше поверхность для rop
* #ABI - это бич C/C++
* #perf - 5 - 10% CPU на дороге не валяются
* динамически слинкованные бинари дольше запускаются

В общем, я не за статлинковку, я против динамической.

Статлинковка простая, как 5 копеек, и современные окружения (go, rust) это понимают.
👍606🤡6🔥4🤔3🗿2
commit -m "better"
Авторы форка Redis занялись его оптимизацией, и запилили неплохую статью по этому поводу.

https://valkey.io/blog/unlock-one-million-rps-part2/

Там представлена интересная техника хождения по ссылочным структурам данных, за счет параллельного хождения одновременно по нескольким структурам получилось лучше использовать кеш памяти.
https://www.opennet.ru/opennews/art.shtml?num=63335

Представлены результаты тестирования свежих выпусков СУБД Redis 8.0 и Valkey 8.1, в которых были заявлены значительные оптимизации производительности. Во всех проведённых тестах развиваемый сообществом форк обогнал оригинальный проект, в основном благодаря внедрению в Valkey нового механизма для многопоточной обработки ввода/вывода в асинхронном режиме, переданного проекту компанией Amazon.

#perf #fork
👍27🤡4🆒2
https://www.agner.org/forum/viewtopic.php?t=287&start=10

TL;DR - разбор Zen 5 на микроархитектурном уровне, от Agner Fog (https://t.iss.one/itpgchannel/1737)

Удивительно, насколько детально он умеет понимать микроархитектуру CPU только по внешним, косвенным, признакам.
🔥154🆒2