CPU cache-friendly data structures in Go
Статья разбирает, как структуры данных влияют на производительность Go-программ под современными CPU. Автор подчеркивает, что чтение из оперативной памяти в 60 раз медленнее, чем из кэша L1, и что ложный обмен (false sharing) между ядрами может убить производительность. Показано, как добавление 56-байтовой прокладки между полями структуры устраняет проблему и ускоряет код в 6-10 раз. Другой совет — разделять «горячие» и «холодные» данные и использовать структуры, оптимизированные под кэш-линии. Показано, как профилировать кэш-промахи через perf и как тестировать эффективность структур данных.
Комментарии (48)
- False sharing и cache-line padding в Go приводят к 10-кратному ускорению при использовании структур, разделённых на разные ядра, но требуют ручного управления выравниванием и размером кэш-линии.
- Компилятор Go не переупорядочивает поля структур и не вставляет паддинг, что делает невозможным автоматическое устранение false sharing без кода, что ограничивает оптимизации только ручными методами.
- Пользователи отмечают, что большинство описанных приёмов применимы к другим языкам и что современные компиляторы должны бы справляться с большинством этих проблем автоматически, но в то же время признают, что для низкоуровневой оптимизации лучше подойдут другие языки и инструменты.
Java 25's new CPU-Time Profiler
Java 25: новый CPU-Profiler (1)
В JDK 25 появился экспериментальный CPU-Profiler — метод-сэмплер, который показывает, сколько процессорного времени тратит каждый метод, а не просто «время выполнения». Это важно: метод, ждущий I/O, занимает процессор лишь доли миллисекунды, и старый sampler не видит разницы между ним и вычислительно тяжёлым кодом.
Старый JFR-сэмплер каждые 10–20 мс выбирает 5 Java-потоков и 1 native, просто пробегая по списку. На 32-ядерной машине это превращает заявленный интервал 10 мс в фактические 53 мс, а при смеси Java и native потоков приоритет всегда получают Java. Результат — искажённая картина.
Новый профилировщик измеряет именно CPU-time, позволяя найти узкие места, которые реально жгут ядра, и повысить throughput без догадок.
Комментарии (98)
- JVM за последние годы стал двигателем инноваций: виртуальные потоки, Loom и быстрый цикл релизов делают Java снова «весёлой».
- Большинство участников рады избавлению от реактивного async-кода: «пусть всё будет синхронно и просто».
- Скептики напоминают: виртуальные потоки всё-таки тратят CPU на GC и не решают проблемы доступа к ограниченным ресурсам.
- Кто-то жалуется на качество современных Java-разработчиков, другие отвечают: плохие devы есть везде, язык тут не при чём.
- Автор серии постов анонсировал три продолжения про новый CPU-Tracing в Java 25.
How to slow down a program and why it can be useful
-
Зачем замедлять программу?
Искать race-conditions, «виртуально» оценить выгоду оптимизации (как в Coz) и проверять точность профилировщиков. Для этого меняют расписание потоков или вставляют задержки. -
Как замедляют сейчас?
Грубо:Thread.sleep, остановка потоков, вставка лишнего байт-кода. Это снижает точность. -
Наша идея
Вставлять задержки внутри basic block на уровне x86. Нужны инструкции, которые:
– надёжно тратят циклы;
– не искажают семантику;
– не оптимизируются CPU за счёт out-of-order. -
Проблема
Современные процессоры выполняют независимыеmovпараллельно, поэтому просто добавить «тяжёлые» команды недостаточно — нужно учитывать зависимости и микроархитектуру.
Комментарии (50)
- Используются разные способы замедления: от NOP-циклов и RDTSC до AVX-нагрузок и «тормозных» устройств вроде C64 Snail или Turbo-кнопки.
- Замедление помогает выявлять N+1-запросы, неработающие кеши и другие узкие места, которые на быстрых машинах незаметны.
- Современные CPU оптимизируют NOP/MOV на стадии переименования, поэтому они плохо подходят для точного контроля времени.
- Causal-profiling (Coz) и специальные прокси (Toxiproxy, dummynet) позволяют «ускорять» выбранный код, оставляя остальное медленным, чтобы заранее оценить выгоду оптимизации.