Performance matters!
1.26K subscribers
11 photos
2 files
68 links
Канал про SRE, Linux и производительность от Александра Лебедева (@alebsys).

Разбираю сбои, ускоряю системы, делюсь опытом.

🔹 Обо мне: alebsys.github.io/about
🧑‍💻 Консультации: alebsys.github.io/mentoring/
💰 Поддержать: https://boosty.to/troubleperf
Download Telegram
Из серии "Смотрите что нашел!"

Low latency tuning guide - сборник техник по оптимизации системы для минимизации задержек.

Внутри как привычные подходы вроде изоляции ядер и отключения гиперпоточности, так и совсем для меня новые:

- сокращение прерываний таймера планировщика через nohz_full, что будет снижать накладные расходы на context swithing.
- закрепление страниц в RAM с помощью mlockall(MCL_CURRENT | MCL_FUTURE), чтобы избежать их выгрузки на диск.

Особую ценность добавляет обилие ссылок для более глубокого погружения в предмет.

Упражнение: задавать себе вопросы из разряда: "Для low latency советуют SCHED_RR и SCHED_FIFO, а что если важнее пропускная способность? Как бы я изменил подход? Почему?"

#kernel #tuning #low_latency
🔥24👍8👎1
Не знаю как и зачем пишутся статьи на 40 минут чтения, но факт есть:

The case of the vanishing CPU: A Linux kernel debugging story

Ещё не погружался, но все ключевые слова на месте: CPU throttling, ebpf, perf, kernel bug.

Будем разбирать.
👍16👎1
В умных книгах пишут, что к анализу производительности стоит подходить в несколько этапов:

1. определить сколько времени должна занимать работа;
2. измерить сколько работа занимает на самом деле;
3. выдвинуть гипотезу почему есть различия и что фиксить;
4. внести изменения в систему с целью привести (2) к (1);
5. GOTO (2)

Если первый этап обычно стабилен и редко меняется, то основное внимание уделяется последующим шагам, при этом пункты с измерениями выглядят наиболее значимыми. И (возможно) самыми сложными.

———————————

Пример анализа потребления CPU
(очень упрощенно)

* замерь общее потребление CPU на машине (top, vmstat);
* определи долю целевого процесса и её распределение на user/system mode (top, pidstat);
* найди самые "горячие" функции/методы/системные вызовы (perf, profile);
* изучи код, чтобы понять, что именно «бьет» по производительности.

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

Теперь внеси изменения и замерь их аффект.

А вот тут нас могут караулить неприятности: точно ли измерили то, что ожидали измерить?

———————————

В главе Measuring CPUs книги Understanding Software Dynamics разбирается случай измерения времени выполнения инструкции add в тактах процессора.

В качестве решения "в лоб" автор приводит:
start = RDTSC();
for (int n = 0; n < 5000; ++n) {
sum += 1;
}
delta = RDTSC() - start;


Здесь фиксируется начальное время (start), затем выполняется инкремент (add) в цикле, после чего рассчитывается разница между началом и концом операции (delta).

Разделив полученную дельту на число итераций, автор получил 6.76 тактов процессора на один проход, что довольно дорого для такой "элементарной инструкции".

(и это среднее значение, а значит, разброс по перцентилям может быть значительным)

На этом можно было бы остановиться: "померяли же!", но если копнуть глубже (куда уж глубже🙂), то окажется, что на ассемблере цикл for{} раскладывается в:
cmpl $999999999, -44(%rbp) # сравнение i с константой
jg .L3 # условный переход, если i > константа
addq $1, -40(%rbp) # sum += 1; значение sum хранится в памяти по адресу -40(%rbp)
addl $1, -44(%rbp) # ++i; значение i хранится в памяти по адресу -44(%rbp)
jmp .L4 # переход к началу цикла


Прямая речь:
[прим. alebsys: loop for{}] has five instructions, three of which access memory by three reads (cmpl, addq, addl) and two writes (addq, addl). So most of what we are measuring is in fact memory accesses, specifically to the L1 data cache.


Далее автор рассматривает способы минимизировать оверхед от цикла и выходит на почти "чистый" замер add в 1.06 такта на инструкцию.

А казалось бы "че там мерить, зашел вышел на пять минут"')

———————————

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

Например, можно думать, что измеряем задержки на сети, хотя узкое место в локальной очереди на машине.
Или радоваться быстрым дискам, хотя по факту запись шла асинхронно через файловую систему.

P.S. Товарищ, будь бдителен!
👍13🔥5👎1🎉1
CPU Isolation: исследование в шести частях о применение техник CPU Isolation для задержкочувствительных нагрузок.

Недостаточно просто выгнать все процессы, кроме целевого, с ядра с помощью cpuset и привязать его к CPU через taskset. Надо не забыть и о фоновых задачах ядра, т.н. housekeeping work.

Housekeeping work – это совокупность фоновых операций, которые ядро Linux выполняет для поддержания своей внутренней инфраструктуры. Эти задачи включают обработку таймеров, обновление системного времени, управление очередями отложенной работы (workqueues), обработку прерываний, очистку ресурсов и прочее. Несмотря на то, что они обычно незаметны для пользователя, именно эти операции обеспечивают стабильность и корректное функционирование всей системы.


Оборотной стороной housekeeping work является то, что она привносит непредсказуемые задержки (jitter), прерывая выполнение пользовательских задач.

Борьба с этими задержками и есть центральная тема цикла.
👍7🔥5👎1
Профилируя процессы в Linux хорошо бы представлять оверхед от инструментов и выбирать подходящий под задачу.

На примере работы утилиты dd сравним накладные расходы strace, perf и bpftrace (eBPF).

Для начала запустим dd без оберток:
# dd if=/dev/zero of=/dev/null bs=512 count=100k
102400+0 records in
102400+0 records out
52428800 bytes (52 MB, 50 MiB) copied, 0.0229283 s, 2.3 GB/s


Скорость в 2.3GB/s будет эталонной, с которой и будем все сравнивать.

strace

# strace -c dd if=/dev/zero of=/dev/null bs=512 count=100k
102400+0 records in
102400+0 records out
52428800 bytes (52 MB, 50 MiB) copied, 1.73851 s, 30.2 MB/s
...


Падение скорости в ~76 раз, неплохо поработали!

perf

# perf stat -e 'syscalls:sys_enter_*' dd if=/dev/zero of=/dev/null bs=512 count=100k
102400+0 records in
102400+0 records out
52428800 bytes (52 MB, 50 MiB) copied, 0.0287921 s, 1.8 GB/s


Замедление на треть уже и не выглядит чем-то страшным:)

bpftrace

# bpftrace -e 'tracepoint:syscalls:sys_enter_* /comm == "dd"/
{ @[probe] = count(); }' -c '/usr/bin/dd if=/dev/zero of=/dev/null bs=512 count=100k'
102400+0 records in
102400+0 records out
52428800 bytes (52 MB, 50 MiB) copied, 0.0475401 s, 1.1 GB/s


Хваленный eBPF дал оверхеда более чем в 2 раза! А говорили, что "eBPF это про скорость" :(

——————————————————

С strace всё ясно: он через ptrace приостанавливает dd на каждом syscall, с переходом в kernel mode и обратно.

Но в чем eBPF не справился? Давайте обсудим!

Важно помнить: dd генерирует много системных вызовов, поэтому такой большой оверхед у `strace`. Цифры выше это скорее утрированный пример, но суть отражают верно.
👍24🔥3👎1
Об IPC
(конспект по книге Performance Analysis and Tuning on Modern CPUs)

Я уже писал о показателе Instructions Per Cycle (IPC) (например тут и тут). Сейчас разберём детали глубже.

Instruction Per Cycle (IPC) — это среднее количество инструкций, завершённых за один такт процессора:

IPC = Retired Instructions / Core Cycles

Определим ключевые понятия.

Инструкции делятся на executed и retired.

- Executed инструкции уже выполнены, но результат ещё не записан в память. Они могут выполняться вне порядка (out of order) и быть отменены, например, из-за miss branch prediction;

- Retired инструкции полностью завершены, то есть и выполнены и их результаты записаны (committed). Отменить их уже нельзя.

Executed напрямую не отслеживаются, а для retired есть отдельный счётчик:
perf stat -e instructions -- ./a.exe

2173414 instructions # 0.80 insn per cycle


Cycles (циклы) процессора бывают двух видов:
- core;
- reference.

Разница важна при динамическом изменении частоты процессора:
1. если CPU работает на штатной частоте: core = reference.
2. если CPU разогнан: core > reference.

Core Cycles отражают реальную текущую, когда Reference Cycles базовую (по паспорту) частоту процессора.
perf stat -e cycles,ref-cycles -- ./a.exe

43340884632 cycles # 3.97 GHz <= Core Cycles
37028245322 ref-cycles # 3.39 GHz <= Reference Cycles


Следовательно IPC показывает единицу A) выполненной B) полезной работы в текущий момент.

IPC не зависит от изменения тактовой частоты, так как всегда рассчитывается на один цикл.


Факторы, ограничивающие IPC
(перечислены в случайном порядке, список неполный):

- скорость памяти и cache misses;
- архитектура процессора: скалярность, загрузка слотов пайплайна;
- тип и сложность инструкций;
- branch misprediction (пенальти на ошибку по 10–25 ns);
- ...

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

На практике этот максимум недостижим: процессор может одновременно выполнять только определённые типы инструкций. Например, в 6-wide архитектуре за такт можно провести четыре операции сложения/вычитания, одну загрузку и одну запись, но не шесть загрузок одновременно.

to be continued...

#cpu #theory
👍18🔥9👎1
Продолжаем!
1👍13🔥8👎1
Perf Weekly | №1
нструменты, системы и производительность)

Troubleshooting Slow TCP Transfers: A Stack-Level Approach

Большинство проблем в production решаются без сложных инструментов. Обычно достаточно стандартных утилит, доступных из любого дистрибутива. Главное понимать, как и где их использовать. Например ss и nstat.

Why strong engineers are rarely blocked
Заблокированными могут быть не только треды, но и инженеры. По ссылке о важности менеджмента своих активностей, чтобы двигаться быстрее.

Can We Know Whether a Profiler is Accurate?
Профайлеры, как и любой инструмент, имеют погрешность измерений. В статье разбирается любопытный способ оценивать их точность через контролируемое замедление программы.
1🔥15👍9
Perf Weekly | №2
нструменты, системы и производительность)

Good Performance for Bad Days
Часто перф анализ проводят при нормальной/обычной нагрузке (задержки низкие, пропускная способность высокая, поведение предсказуемо).

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

Introduce tseries() For Capturing Time Series Data #3838
В v0.24.0 версии bpftrace наряду с привычными hist / lhist появился ещё один способ визуализации: tseries.

Режим позволяет увидеть, как значения меняются во времени. И это уже не просто циферки в терминале, а настоящие time series!

NUMA Cost & Performance
Статья о том, почему многосокетные (NUMA) серверы часто оказываются дороже, сложнее в настройке и зачастую медленнее, чем односокетные системы, и что с этим делать.
👍9🔥81
Perf Weekly | №3
нструменты, системы и производительность)

Linux perf-top basics: understand the %
perf top базовая утилита, которая в три секунды покажет чем же занят процесс. Полезно делать это эффективно!

All Roads Lead to IPC: Rethinking CPU Performance Design
IPC как главная метрика CPU. Архитектура процессора, узкие места, latency vs occupation и как это всё применять, чтобы ускорить код. Дорогу осилит идущий 🙂.

How can I learn about performance optimization?
Большой тред о том, как расти в performance engineering: как учиться, на чем фокусироваться, типичные ловушки и куча полезных ссылок.

А возникшие вопросы можно обсудить в треде;)
🔥11👍62
Замечали "странные" проценты в скобках в выводе perf stat?
$ perf stat -e cycles,instructions,LLC-loads,LLC-load-miss,... -C 1 -- sleep 5

Performance counter stats for 'CPU(s) 1':

15989951459 cycles (62.40%)
8367516992 instructions (74.96%)
6897420 LLC-loads (75.04%)
1258051 LLC-load-miss (75.05%)
1977812325 dTLB-loads (75.05%)
1059510 dTLB-load-misses (75.05%)
1957630660 mem_load_retired.l1_hit (49.91%)
24843220 mem_load_retired.l1_miss (49.91%)


Это multiplexing.

Аппаратных счётчиков в CPU может быть меньше, чем событий, которые мы хотим измерить. И perf выкручивается тем, что по очереди переключается между событиями и далее масштабирует результат.

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

Если хочется разобраться глубже (а как иначе?), читаем статью PMU counters and profiling basics.
🔥132
Про L3 Cache (LLC)

L3 последний из “быстрых” уровней памяти, он способен отдавать данные за 30-60 циклов процессора.

А дальше идет DRAM, где стоимость доступа Local / Remote может составлять до 150–300 cycles!

Потому если перформанс для нас важен, то хорошо когда L3 Hit Rate большой, а L3 Miss Rate маленький ;)

Осознав масштаб трагедии, идём смотреть, как чувствуют себя наши системы.


Node exporter любезно предоставляет набор метрик (через perf коллектор) для оценки обращений к L3:
- node_perf_cache_refs_total
- node_perf_cache_misses_total
- node_perf_cache_ll_read_hits_total
- node_perf_cache_ll_read_misses_total


В perf им соответствуют ивенты:
- cache-references
- cache-misses
- LLC-loads
- LLC-load-miss


Там и подсчитаем:
$ perf stat -e cache-references,cache-misses \
-e LLC-loads,LLC-load-misses \
-- sleep 5

166680 cache-references
38376 cache-misses # 23.0% miss rate
65552 LLC-loads
8997 LLC-load-misses # 13.7% miss rate


Разница существенна, какой метрике будем доверять? :)

cache-references / cache-misses

Это самые “шумные” ивенты, включают в себя:

- запросы как на read так и на store;
- prefetch;
- instruction fetch;
- спекулятивные выполнения.

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


То есть по этим событиям сложно сделать вывод о реальном положении дел с L3.

LLC-loads / LLC-load-misses

Здесь детализации побольше: отслеживаются только запросы на read (для write есть отдельные LLC-store* события) + спекулятивные обращения.

В целом это уже то, с чем можно работать и делать более менее обоснованные выводы из полученных цифр.

Но может есть что-то более точное?

mem_load_retired.l3_*

Для процессоров Intel доступны события mem_load_retired.l3_hit и mem_load_retired.l3_miss. Они учитывают только те загрузки, которые были retired (реально выполнены и завершены процессором), то есть действительно повлияли на ход выполнения программы:
$ perf stat -e cache-references,cache-misses \
-e LLC-loads,LLC-load-misses \
-e mem_load_retired.l3_hit,mem_load_retired.l3_miss \
-- sleep 5

199082 cache-references
55283 cache-misses # 27.7% miss rate
65554 LLC-loads
8796 LLC-load-misses # 13.4% miss rate
16790 mem_load_retired.l3_hit
3694 mem_load_retired.l3_miss # 22.0% miss rate


А еще они precise (PEBS)! Но и об этом в другой раз:)

Итоги
1. если хочется быстро оценить ситуацию, то пользуйся LLC-load* (или соответствующие им метрики в Node exporter)
2. если хочется точнее разобраться откуда идут промахи, то mem_load_retired.l3_* в помощь.

Гудлак 😊


p.s. поддержать плюсом на linkedin тут.
1🔥2132