Кожен огляд CPU виглядає переконливо, доки ви не спробуєте обслуговувати реальний трафік. Ваш сервіс — це не «Cinebench». Це хаотичний коктейль: виклики ядра, TLS, JSON, паузи GC, переривання, кеш-промахи та звернення до бази даних, яка іноді прокидається з «неправильного боку» NUMA.
Якщо ви коли-небудь випустили «безпечне» оновлення CPU і все одно отримали оповіщення про p99-латентність, ви вже знаєте проблему: ви не вимірювали правильну річ. Це прагматичний метод тестування CPU на вашому навантаженні — з повторюваними прогонами та рішеннями, які ви зможете захистити в постмортемі.
Принципи: що означає «продуктивність CPU» в продакшні
Тестування CPU йде не туди, коли ставлять неправильне питання. Неправильне питання: «Який CPU швидший?» Правильне питання: «Який CPU забезпечує моєму навантаженню потрібну латентність та пропускну здатність з прийнятними витратами та ризиком операційної експлуатації?»
Продуктивність CPU в продакшні — це проблема з трьома чинниками:
- Виконана робота (throughput): запити/сек, завдань/хв, рядків проскановано/сек.
- Час виконання (latency): особливо хвостова латентність (p95/p99/p999).
- Стабільність: варіативність під шумом — фонові завдання, джиттер, тротлінг, шумні сусіди.
І «CPU» часто — це не лише «CPU». Це:
- Мікроархітектура: IPC, кеші, предиктор переходів, префетчери.
- Підсистема пам’яті: пропускна здатність, латентність, NUMA, page faults.
- Поведіка ядра: планування, переключення контексту, переривання, cgroups.
- Терміка та ліміти потужності: turbo-поведінка, яка чудово виглядає 30 секунд, а потім «здає позиції».
- Програмне забезпечення: прапори компілятора, бібліотеки криптографії, налаштування GC, блокування.
Мета тестування — не знайти одне число. Мета — знайти експлуатаційний діапазон: при якому навантаженні підстрибує p99, коли формуються черги виконання, коли починаєся thrash кешів і який регулятор справді змінює ситуацію.
Перефразована ідея (John Allspaw, operations/reliability): «Ви не можете претендувати на надійність, якщо не можете її продемонструвати в реалістичних умовах.» Це застосовується і до продуктивності.
Одне правило, якого я буду надзвичайно послідовно дотримуватися: пріоритезуйте латентність спочатку, потім витрати. Тести лише на throughput роблять погані CPU-рішення, бо ховають страждання користувачів на хвості.
Жарт №1: Бенчмаркінг на порожній лабораторній машині — це як тестувати пожежну сигналізацію у вакуумі: технічно вражає, операційно марно.
Цікаві факти та історичний контекст
Це не дрібниці заради дрібниць. Вони пояснюють, чому сучасне тестування CPU не зводиться до одного ярлика «GHz».
- «Війни MHz» закінчились не випадково. Близько середини 2000-х частота перестала масштабуватися через густину потужності та витік; продуктивність перемістилась у багатоядерність та мікроархітектурні поліпшення.
- Спекулятивне виконання змінило все. Out-of-order та спекуляція покращили IPC, але також породили класи вразливостей (Spectre/Meltdown) та міри пом’якшення, що впливають на деякі навантаження.
- Turbo — це не обіцянка. Сучасні CPU динамічно підвищують частоту залежно від потужності/температури/поточного навантаження; два «ідентичні» прогони можуть відрізнятися, якщо відрізняються охолодження або BIOS.
- NUMA старий, але досі один із головних порушників. Багатосокетні NUMA-системи існують десятиліттями; режим відмови (віддалена латентність пам’яті) все ще дивує команди при міграції з менших вузлів.
- Лічильники perf у Linux не створювалися для дашбордів. Апаратні лічильники призначались для проєктувальників чипів і низькорівневого профілювання; їхнє операційне використання вимагає уважної інтерпретації.
- Hyper-threading (SMT) залежить від навантаження. Він може підвищити throughput на застоєних ділянках, але погіршити хвостову латентність на сервісах, чутливих до блокувань або кешу.
- Віртуалізація доробилась, а потім cgroups усе ускладнили. Наклад VM зменшився, але квоти CPU в контейнерах ввели новий режим відмови: тротлінг, що виглядає як «таємнича латентність».
- Великі сторінки не завжди корисні. Huge pages зменшують TLB misses, але підвищують фрагментацію й можуть нашкодити при динамічній або надкомітованій пам’яті.
- «Швидший CPU» може уповільнити систему. Якщо CPU обробляє запити швидше, ви можете пересунути вузьке місце на сторидж, блокування або до downstream-сервісів — і тоді ваш p99 погіршиться.
Простий метод: побудуйте надійний стенд навантаження
Ось метод, який я рекомендую, коли ви хочете порівняти CPU або перевірити зміну налаштувань. Він простий, але не примітивний. Ви запускатимете те саме навантаження на кандидатах, збиратимете невеликий набір метрик і прийматимете рішення на основі хвостової латентності та сигналів насичення.
1) Визначте навантаження як дорослий
«API-трафік» — це не навантаження. Навантаження — це розподіл:
- Мікс запитів (ендпойнти, типи запитів, співвідношення читання/запис)
- Розміри payload (малі, типові, патологічні)
- Модель конкурентності (потоки, async, розміри пула з’єднань)
- Час думки / патерн приходу (псевдо-Пуассонівський проти вибухового)
- SLO, яке вас цікавить (p99 при N RPS)
Виберіть 1–3 репрезентативні сценарії. Не створюйте цілий всесвіт. Але й не обирайте лише щасливий шлях.
2) Зробіть середовище свідомо нудним
Повторюваність — це функція. Ваше тестове середовище має усувати змінний шум, де можливо:
- Зафіксуйте governor частоти CPU в
performanceдля тестів (або принаймні зафіксуйте його). - Вимкніть фонові cron-штормі, оновлення пакетів і опортуністичні антивірусні сканування.
- Забезпечте однакові версії ядра, налаштування BIOS, мікрокод і міри пом’якшення між кандидатами — або свідомо прийміть відмінності й зафіксуйте їх.
- Не порівнюйте bare-metal хост з overcommitted VM і називайте це «тестування CPU». Це комедія.
3) Тестуйте для стабільного стану, а не для театру розігріву
Більшість сервісів мають ефекти розігріву: JIT-компіляція, кеші, пули з’єднань, файловий кеш, ARC, page cache, тренування предиктора переходів, навіть DNS-кеші. Вам потрібні дві фази:
- Розігрів: достатньо довгий, щоб досягти стабільної поведінки.
- Вимірювання: фіксована тривалість, під час якої ви знімаєте метрики.
Коли команди пропускають це, вони оптимізують для «першої хвилини продуктивності», а потім дивуються, чому на 30-й хвилині все плавиться. Ваші CPU не оцінюють харизму.
4) Вимірюйте розподіл латентності й сигнали насичення CPU разом
Якщо ви записуєте тільки завантаження CPU — ви сліпі. Якщо тільки латентність — не знаєте, куди дивитись. Поєднайте їх:
- Латентність: p50/p95/p99, плюс max (з обережністю).
- Пропускна здатність: RPS, QPS, jobs/sec.
- Насичення CPU: run queue, throttling, iowait (інтерпретовано уважно).
- Perf-лічильники (семплінг): cycles, instructions, branches, cache misses.
5) Порівнюйте за рівнем «болю користувача», а не за рівнем завантаження
При порівнянні двох CPU не вирівнюйте «CPU = 70%». Вирівнюйте SLO-точку (наприклад, «p99 < 200ms»). Один CPU може бути на 70% і спокійно працювати, інший при 50% уже thrash через поведінку кешу або ліміти частоти.
6) Вирішіть до запуску: який результат змінить ваше рішення
Запишіть критерії приймання:
- «При 10k RPS p99 < 150ms і помилки < 0.1%.»
- «Тротлінг CPU < 1% під квотою.»
- «Perf показує IPC в межах 10% від базової; немає аномального сплеску cache miss.»
Якщо ви не вирішили наперед, ви будете домовлятись із графіками постфактум. І графіки завжди перемагають.
Практичні завдання (команди, вивід, рішення)
Нижче — практичні завдання, які можна виконати на Linux. Кожне містить: команду, що означає вивід, і яке рішення прийняти. Оберіть підмножину, що відповідає вашому середовищу, але не пропускайте ті, які виявляють «CPU — не справжній CPU».
Завдання 1: Запишіть модель CPU, топологію і статус SMT
cr0x@server:~$ lscpu
Architecture: x86_64
CPU(s): 32
Thread(s) per core: 2
Core(s) per socket: 16
Socket(s): 1
Model name: Intel(R) Xeon(R) Gold 6338 CPU @ 2.00GHz
NUMA node(s): 1
L3 cache: 48 MiB
Що це означає: Тепер ви знаєте, що саме тестуєте: ядра, сокети, SMT, розміри кешу, NUMA. «32 CPU» можуть бути 16 ядрами з SMT.
Рішення: Якщо порівнюєте машини, переконайтеся, що топологія порівнювана, або явно включіть відмінності SMT/NUMA у висновок. Якщо ваш сервіс чутливий до латентності, заплануйте A/B-прогін зі SMT увімкненим і вимкненим.
Завдання 2: Перевірте governor частоти та поведінку частоти
cr0x@server:~$ cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
ondemand
Що це означає: «ondemand» може додавати джиттер і недостатнє підвищення під час коротких сплесків, залежно від ядра та платформи. На серверах часто бажана послідовна поведінка.
Рішення: Для бенчмаркінгу встановіть governor у performance (або зафіксуйте його й прийміть варіабельність). Якщо не можете змінити (в хмарі), принаймні зафіксуйте його й запускайте довше, щоб усереднити поведінку boost.
Завдання 3: Перевірте можливість turbo/boost (якщо доступно)
cr0x@server:~$ cat /sys/devices/system/cpu/intel_pstate/no_turbo
0
Що це означає: 0 означає, що turbo дозволено. Якщо там 1, ваш «швидкий CPU» може поводитися як чемний CPU.
Рішення: Тримайте turbo послідовним між тестовими хостами. Якщо один хост має turbo вимкнений (BIOS або OS), порівняння вже спотворене.
Завдання 4: Перевірте тротлінг через cgroups (контейнери/Kubernetes)
cr0x@server:~$ cat /sys/fs/cgroup/cpu.stat
usage_usec 987654321
user_usec 800000000
system_usec 187654321
nr_periods 12345
nr_throttled 2345
throttled_usec 456789012
Що це означає: nr_throttled і throttled_usec вказують, що ядро примусово зупиняє ваше навантаження для забезпечення квоти CPU.
Рішення: Якщо тротлінг суттєвий під час тесту, ваш «бенчмарк CPU» — це бенчмарк квоти. Збільшіть квоту, зніміть ліміти для тесту або розгляньте «тротлінг на запит» як первинну метрику.
Завдання 5: Слідкуйте за run queue та завантаженням CPU в часі
cr0x@server:~$ vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
2 0 0 8123456 123456 789012 0 0 0 2 900 1400 25 5 70 0 0
8 0 0 8121200 123456 789100 0 0 0 0 1200 6000 80 10 10 0 0
10 0 0 8119000 123456 789200 0 0 0 0 1300 7000 85 10 5 0 0
9 0 0 8118000 123456 789250 0 0 0 0 1250 6800 83 12 5 0 0
3 0 0 8120000 123456 789300 0 0 0 0 1000 3000 50 7 43 0 0
Що це означає: r — runnable процеси. Якщо r послідовно перевищує кількість ядер, ви маєте насичення або контенцію CPU. Сплески cs (context switches) можуть підказувати блокування або перезавантаження потоку.
Рішення: Якщо черга виконання зростає одночасно зі сплесками латентності, ймовірно, потрібно більше CPU (або менше потоків) або ви потрапили на блокування. Якщо черга низька, але латентність висока — вузьке місце в іншому місці.
Завдання 6: Перевірте завантаження по ядрах і %steal (віртуалізація)
cr0x@server:~$ mpstat -P ALL 1 3
Linux 6.1.0 (server) 01/12/2026 _x86_64_ (32 CPU)
12:00:01 AM CPU %usr %nice %sys %iowait %irq %soft %steal %idle
12:00:02 AM all 62.00 0.00 8.00 0.10 0.00 1.50 5.00 23.40
12:00:02 AM 0 90.00 0.00 5.00 0.00 0.00 0.00 2.00 3.00
12:00:02 AM 1 10.00 0.00 5.00 0.00 0.00 0.00 20.00 65.00
12:00:03 AM all 64.00 0.00 7.50 0.00 0.00 1.20 6.50 20.80
Що це означає: %steal показує, що гіпервізор відбирає час CPU. Високий steal робить латентність гучною та анулює порівняння між прогонами.
Рішення: Якщо steal >1–2% під час тестів, переходьте на виділені хости/інстанси або вважайте середовище непридатним для порівняння CPU.
Завдання 7: Перевірте NUMA-розклад і чи «стрибає» процес по пам’яті
cr0x@server:~$ numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
node 0 size: 64250 MB
node 0 free: 12000 MB
node 1 cpus: 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
node 1 size: 64250 MB
node 1 free: 15000 MB
node distances:
node 0 1
0: 10 21
1: 21 10
Що це означає: Віддалений доступ до пам’яті (distance 21 проти 10) значно повільніший. Якщо процес планується на node 0, але часто звертається до пам’яті node 1, ваш CPU виглядає «повільним».
Рішення: Для тестів закріпіть локальність CPU та пам’яті для навантаження (або явно протестуйте з прикріпленням і без). Якщо ви мігруєте на двосокетні системи, розглядайте NUMA як первинне вимога.
Завдання 8: Виявити міграції CPU (поширений вбивця латентності)
cr0x@server:~$ perf stat -e task-clock,context-switches,cpu-migrations,page-faults -p 1234 -- sleep 10
Performance counter stats for process id '1234':
10012.34 msec task-clock # 0.999 CPUs utilized
120,345 context-switches # 12.014 K/sec
3,210 cpu-migrations # 320.593 /sec
8,765 page-faults # 875.539 /sec
10.012345678 seconds time elapsed
Що це означає: Тисячі міграцій CPU за секунду можуть знищити локальність кешу і спричинити джиттер. Context switches можуть вказувати на пересичення потоків, блокування або блокований IO.
Рішення: Якщо міграцій багато, розгляньте pinning CPU, зменшення числа runnable-потоків або виправлення тиску планувальника (наприклад, розміщення cgroup). Якщо context switches високі і латентність стрибкоподібна — шукайте блокування або надмірні пулі потоків.
Завдання 9: Виміряйте IPC і поведінку кешу через perf (макропогляд)
cr0x@server:~$ perf stat -e cycles,instructions,branches,branch-misses,cache-references,cache-misses -a -- sleep 10
Performance counter stats for 'system wide':
32,100,000,000 cycles
45,500,000,000 instructions # 1.42 insn per cycle
8,900,000,000 branches
120,000,000 branch-misses # 1.35% of all branches
2,100,000,000 cache-references
210,000,000 cache-misses # 10.00% of all cache refs
10.000987654 seconds time elapsed
Що це означає: IPC і відсоток кеш-промахів значно змінюються між CPU та навантаженнями. CPU з вищими тактовими можуть програвати тому, що має кращу поведінку кешу на реальних сервісах.
Рішення: Якщо IPC падає або spike-ить кількість cache misses під навантаженням, зосередьтесь на локальності пам’яті, структурах даних та моделі конкурентності — не лише «купіть швидші ядра». Використовуйте це, щоб пояснити, чому «гірший» синтетичний CPU може перемогти для вашого сервісу.
Завдання 10: Підтвердіть, чи дійсно ви CPU-bound, або стоїте на пам’яті
cr0x@server:~$ perf stat -e cycles,instructions,stalled-cycles-frontend,stalled-cycles-backend -p 1234 -- sleep 10
Performance counter stats for process id '1234':
5,400,000,000 cycles
6,900,000,000 instructions # 1.28 insn per cycle
1,900,000,000 stalled-cycles-frontend
2,700,000,000 stalled-cycles-backend
10.001234567 seconds time elapsed
Що це означає: Високі backend stalls часто корелюють з латентністю/пропускною пам’яті, кеш-промахами або обмеженнями ресурсів конвеєра.
Рішення: Якщо домінують backend stalls, CPU з кращою підсистемою пам’яті (більші кеші, більше каналів пам’яті) може перевершити CPU з вищою частотою. Також врахуйте NUMA і швидкість пам’яті, перш ніж переписувати половину коду.
Завдання 11: Визначте топ-споживачів CPU і чи це користувач чи ядро
cr0x@server:~$ top -b -n 1 | head -n 15
top - 00:00:10 up 12 days, 3:12, 2 users, load average: 12.50, 11.20, 9.80
Tasks: 310 total, 8 running, 302 sleeping, 0 stopped, 0 zombie
%Cpu(s): 82.0 us, 10.0 sy, 0.0 ni, 8.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
MiB Mem : 128000.0 total, 22000.0 free, 53000.0 used, 53000.0 buff/cache
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1234 app 20 0 4567890 512000 25000 R 750.0 0.4 10:12.3 myservice
2345 root 20 0 0 0 0 S 80.0 0.0 1:20.1 ksoftirqd/3
Що це означає: Високий sy (system) плюс піки ksoftirqd підказують, що мережа/обробка переривань споживає CPU. Це все ще CPU, але виправлення не обов’язково у «оптимізації JSON-парсингу».
Рішення: Якщо CPU в ядрі високий — перевірте переривання, налаштування NIC, швидкість пакетів, conntrack і варіанти offload для TLS. Якщо користувацький CPU високий — профілюйте додаток.
Завдання 12: Перевірте розподіл переривань (поширене при високих пакетних швидкостях)
cr0x@server:~$ cat /proc/interrupts | head -n 8
CPU0 CPU1 CPU2 CPU3
24: 1234567 1200345 98012 87000 IR-PCI-MSI eth0-TxRx-0
25: 98012 87000 1123456 1198765 IR-PCI-MSI eth0-TxRx-1
NMI: 1234 1234 1234 1234 Non-maskable interrupts
LOC: 98765432 98765000 98764900 98764800 Local timer interrupts
Що це означає: Якщо один CPU тоне у NIC-перериваннях, ви побачите «гарячі точки» і хвостову латентність. Ідеально переривання збалансовані між ядрами (або навмисно прив’язані).
Рішення: Якщо переривання перекошені, увімкніть/перевірте irqbalance, налаштуйте RSS-черги або прив’яжіть переривання подалі від критичних потоків.
Завдання 13: Виміряйте тиск планувальника (PSI) для підтвердження контенції
cr0x@server:~$ cat /proc/pressure/cpu
some avg10=0.50 avg60=0.80 avg300=0.75 total=123456789
full avg10=0.10 avg60=0.25 avg300=0.20 total=23456789
Що це означає: PSI показує час, проведений у очікуванні CPU. «full» вказує на завдання, які взагалі не можуть виконатись через відсутність CPU. Це надзвичайно дієво для відповіді на питання «чи це справді контенція CPU?»
Рішення: Якщо PSI «full» зростає під час сплесків латентності, у вас CPU-недостатність або тротлінг. Додайте CPU, зменшіть конкурентність або зніміть квоти. Якщо PSI низьке — припиніть звинувачувати CPU і шукати інше.
Завдання 14: Підтвердіть, що диск і файлові системи не спотворюють ваш тест CPU
cr0x@server:~$ iostat -xz 1 3
Linux 6.1.0 (server) 01/12/2026 _x86_64_ (32 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
70.00 0.00 10.00 0.20 0.00 19.80
Device r/s w/s rkB/s wkB/s rrqm/s wrqm/s %util await
nvme0n1 10.00 20.00 800.00 900.00 0.00 1.00 5.00 0.40
Що це означає: Низьке %util і низьке await підказують, що сховище не є вузьким місцем. Високий iowait не завжди означає «диск повільний», але це дає базу.
Рішення: Якщо використання диска і await зростають під час «CPU-тестів», ви бенчмаркуєте IO або поведінку fsync. Ізолюйте IO, перемістіть тимчасові дані у tmpfs для чистих CPU-тестів або прийміть IO як частину навантаження (часто правильно для баз даних).
Завдання 15: Запустіть контрольований нагрузковий тест і зберіть перцентилі латентності
cr0x@server:~$ hey -z 60s -c 200 -q 50 http://127.0.0.1:8080/api/v1/search
Summary:
Total: 60.0012 secs
Slowest: 0.4123 secs
Fastest: 0.0102 secs
Average: 0.0451 secs
Requests/sec: 9700.12
Response time histogram:
0.010 [1] |
0.050 [520000] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
0.100 [55000] |■■■
0.200 [4000] |
0.400 [120] |
Latency distribution:
50% in 0.0400 secs
90% in 0.0800 secs
99% in 0.1500 secs
Що це означає: Це зовнішній погляд: що бачить клієнт. p99 — ваш «лічильник болю клієнта».
Рішення: Порівнюйте CPU при однаковому навантаженні та дивіться p99/p999. Якщо CPU A дає на 20% більше RPS, але дублює p99, CPU A — пастка для сервісів чутливих до латентності.
Завдання 16: Профілюйте «гарячі точки» без змін коду (швидкий вхід для flamegraph)
cr0x@server:~$ perf record -F 99 -p 1234 -g -- sleep 30
[ perf record: Woken up 8 times to write data ]
[ perf record: Captured and wrote 12.345 MB perf.data (12345 samples) ]
Що це означає: Ви зібрали стеки викликів. Тепер можна визначити, чи час витрачається на крипто, парсинг JSON, виділення пам’яті або системні виклики ядра.
Рішення: Якщо гарячі точки в ядрі мережі — налаштуйте kernel/NIC. Якщо в алокаціях/GC — налаштуйте рантайм або зменшіть алокації. Якщо в блокуванні — виправляйте конкурентність. Тестування CPU без профілювання — це вгадування з гарними графіками.
Швидкий плейбук діагностики
Це версія «я на чергуванні і графік горить». Ви хочете швидко вирішити, чи ви CPU-bound, тротлінг чи просто страждаєте від іншого вузького місця під маскою CPU.
Перше: підтвердіть біль користувача і чи корелює він з тиском на CPU
- Перевірте p95/p99 латентність (з виводу нагрузкового тесту або метрик сервісу). Якщо хвостова латентність стабільна, не ганяйтесь за CPU лише тому, що завантаження виглядає високим.
- Перевірте PSI (
/proc/pressure/cpu). Якщо «full» піднімається під сплесками латентності, контенція чи тротлінг CPU реальні. - Перевірте run queue (
vmstat 1,uptime). Стійка черга виконання вище кількості ядер — класичний сигнал насичення.
Друге: виключіть двох головних «мімів» CPU
- Тротлінг: квоти контейнерів (
/sys/fs/cgroup/cpu.stat) та cloud steal time (mpstat). - NUMA/міграції: якщо у вас multi-socket або строгі SLO, перевірте
numactl --hardwareіperf stat ... cpu-migrations.
Третє: вирішіть, чи потрібен вам більше CPU, інший CPU або інший код
- Більше CPU коли run queue + PSI високі, і профілювання показує реальні обчислення.
- Інший CPU коли perf показує затримки пам’яті, кеш-промахи або падіння частоти. Можливо потрібні більші кеші, більше каналів пам’яті або вищий all-core turbo — не тільки більше ядер.
- Інший код коли гарячі точки очевидні і їх можна виправити (блокування, хвилі алокацій, часті системні виклики).
Жарт №2: Якщо ваш p99 покращується лише коли ви перестаєте його вимірювати — ви винайшли «продуктивність через спостереження» — будь ласка, не релізьте це.
Три корпоративні міні-історії з передової
Міні-історія №1: Інцидент через хибне припущення (SMT «завжди дає безкоштовну продуктивність»)
Компанія запускала latency-чутливе API, що робило аутентифікацію, валідацію JSON і невеликий запит до БД. Вони перейшли на нове покоління CPU і побачили чудові синтетичні числа. Хтось помітив, що можна ввімкнути SMT у всіх місцях і «отримати на 30% більше CPU» без витрат. Розгортання почалося з упевненістю і святковою таблицею в спредішіті.
За кілька годин p99 для логін-запитів почав хитатись. Спочатку не драматично — достатньо, щоб порушити внутрішні SLO-оповіщення. Рівень помилок залишався низьким, середня латентність виглядала добре, і завантаження CPU навіть трохи впало, бо запити «завершувались». Інженер чергування отримав найгірший графік: той, що не кричить, а просто розчаровує.
Вони ганялися за базою даних. Налаштовували пули з’єднань. Навіть звинуватили балансувальник навантаження. Справжнім винуватцем виявилось банально фізичне: SMT збільшило контенцію у спільних ресурсах виконання і кешах для lock-heavy, branchy шляху аутентифікації. Під сплесками додаткові потоки підвищили переключення контексту і погіршили хвостову латентність.
Виправлення не було ідеологічним («SMT поганий»). Вони виконали контрольовані тести SMT on/off і виявили розділення: batch-ендпойнти любили SMT, login — ні. Вони вимкнули SMT для latency-рівня і лишили його для batch-рівня. Інцидент не був «SMT зламав продакшн». Інцидент був про хибне припущення: що CPU-фічі універсально корисні.
Чого навчитися: ставтесь до SMT як до будь-якої іншої змінної. Вимірюйте під вашим реальним ступенем конкурентності і слідкуйте за хвостовою латентністю і міграціями. Бенчмарки тільки по throughput вас обдурять з прямим виразом обличчя.
Міні-історія №2: Оптимізація, що наздогнала назад (апгрейд CPU змістив вузьке місце і погіршив p99)
Інша організація мала сервіс, що був частково CPU-bound: багато TLS-термінації і стиснення відповіді. Вони перейшли на CPU з вищою частотою та кращим апаратним прискоренням криптографії. До/після RPS покращився. Графіки після апгрейду виглядали чисто. Усі розслабились.
Але через тиждень p99 підскакував під час звичних піків. Не завжди. Не передбачувано. Нові CPU були «швидшими», але скарги клієнтів зросли. Команда вчинила як зазвичай: додали більше подів, більше вузлів, підняли межі автоскейлінгу. Це допомогло, але витрати виросли, а проблема не зникла повністю.
Справжня проблема: прискоривши фронтенд, вони збільшили навантаження на downstream кеш і спільне key-value сховище. Це сховище мало CPU-важку операцію компакції, яка раніше була невидима, бо фронтенд не генерував достатньо тиску. Тепер генерував. Вузьке місце перемістилось, і хвостова латентність пішла за найслабшою ланкою.
Вони вирішили проблему, обмеживши конкурентність до downstream сервісу, додавши backpressure і налаштувавши TTL кешів, щоб зменшити thundering-herd. Апгрейд CPU не був помилкою; помилкою було припущення, що CPU — локальна властивість.
Чого навчитися: поліпшення CPU змінюють динаміку системи. Коли тестуєте CPU, включайте downstream-поведінку або ізолюйте залежність явно. Якщо ви тестуєте компонент в ізоляції, ви робите демо, а не планування ємності.
Міні-історія №3: Сумна, але правильна практика, що врятувала ситуацію (повторюваний стенд + pinned середовище)
Команда, що працює зі зберіганням, готувалась купувати нові вузли для внутрішньої аналітичної платформи. Вони мали два варіанти CPU: більше ядер, або менше, але швидші ядра з більшим кешем. Думки були сильні, як це часто буває при виборі заліза.
Замість суперечок, вони побудували нудний, дисциплінований стенд. Такий же образ ОС, те саме ядро, ті самі налаштування BIOS, однаковий мікрокод. Вони зафіксували governor, вимкнули непотрібні служби і робили розігрів перед вимірюванням. Збирали p50/p95/p99 для латентності запитів, а також perf-лічильники та PSI. Кожен прогін маркували точною ревізією git і міткою часу. Ніхто не мав права «ще одну правку» без її запису.
Вони виявили щось контрінтуїтивне: CPU з «більше ядер» перемагав за сирим throughput, але CPU з «більшим кешем» перемагав за p99 для важких join-запитів. Perf-лічильники показали більшу частоту кеш-промахів і backend stalls на варіанті з багатьма ядрами. Висновок не був філософським; він був даними: якщо вони хотіли стабільної інтерактивної продуктивності, CPU з більшим кешем був безпечнішим вибором.
Коли procurement запитав, чому вибирають SKU з меншим числом ядер, у них був односторінковий звіт з графіками та відтворюваними командами. Ніяких героїчних дій. Просто нудна компетентність. Це врятувало мільйонний спір від перетворення в мільйонну помилку.
Чого навчитися: найцінніший інструмент продуктивності — повторюваний стенд. Це не гламурно. Це спосіб уникнути жалю після покупки.
Поширені помилки: симптоми → корінь → виправлення
Цей розділ для діагностики режимів відмов, які повторюються в реальних тестах CPU. Шаблон важливий: симптом — що ви бачите; корінь — що насправді відбувається; виправлення — що робити далі.
1) Симптом: CPU «низький», але p99 латентність висока
- Корінь: IO-waits, блокування, або латентність downstream-залежностей. Середні значення CPU приховують стази.
- Виправлення: Перевірте
iostat -xz, перевірте PSI для пам’яті/IO (якщо доступно) і профілюйте на предмет блокувань. Додайте таймінги залежностей до виходу тесту.
2) Симптом: CPU у піку, але throughput не зростає при більшій кількості клієнтів
- Корінь: Глобальне блокування, точка сериалізації або один «гарячий» потік (наприклад, GC, event loop, логування).
- Виправлення:
top -H, щоб знайти гарячі потоки; використайтеperf record; зменшіть контенцію; перепроєктуйте критичну секцію.
3) Симптом: Результати дуже різняться між прогонами
- Корінь: Скалінг частоти CPU/turbo відмінності, термальне тротлінг, steal time, фоновий шум або ефекти розігріву.
- Виправлення: Зафіксуйте governor, моніторьте частоти/температури, переконайтесь у відсутності steal, запускайте довше з розігрівом і логайте деталі середовища.
4) Симптом: «Кращий CPU» перемагає за середньою латентністю, але програє за p99
- Корінь: Планувальний джиттер, SMT-контенція, міграції або thrash кешу під сплеском.
- Виправлення: Перевірте міграції, run queue і perf-лічильники; експериментуйте з вимкненням SMT; закріпіть ядра для критичних потоків.
5) Симптом: Тротлінг CPU з’являється тільки в контейнерах
- Корінь: Квота cgroup занадто низька, занадто малий період або вибухоподібне навантаження, що потрапляє на межі квоти.
- Виправлення: Збільшіть ліміти, обережно налаштовуйте requests/limits, або уникайте жорстких квот для latency-критичних сервісів.
6) Симптом: Високий system CPU і сплески ksoftirqd
- Корінь: Тиск переривань/softirq: висока швидкість пакетів, малі пакети, conntrack або неоптимальне чергування NIC.
- Виправлення: Збалансуйте переривання, налаштуйте RSS/черги, перегляньте налаштування conntrack і обережно розгляньте offload-и (вимірюйте!).
7) Симптом: CPU виглядає насиченим лише на двосокетних системах
- Корінь: Віддалена пам’ять NUMA або блокування між вузлами; алокації пам’яті не локальні для виконуваних ядер.
- Виправлення: Закріпіть процеси за допомогою
numactl, використовуйте NUMA-aware allocators, вирівняйте IRQs і потоки, тестуйте масштабування по сокетах.
8) Симптом: Ви «оптимізуєте», збільшуючи потоки, і отримуєте гіршу продуктивність
- Корінь: Наклад переключення контексту, блокування, thrash кешу. Більше потоків може знизити ефективність CPU.
- Виправлення: Зменшіть конкурентність; використовуйте async де доречно; підбирайте розміри пулів під кількість ядер; вимірюйте context switches і міграції.
Чеклисти / покроковий план
Чеклист A: Здоровий план порівняння CPU (bare metal або виділені VM)
- Виберіть сценарії: 1–3 мікси навантаження, що представляють продакшн (включіть один сценарій «поганого дня»).
- Заморозьте середовище: однакова ОС, ядро, мікрокод, та конфіг сервісу і залежностей.
- Контролюйте поведінку CPU: зафіксуйте governor/turbo; уникайте термальних відмінностей; забезпечте схожу систему охолодження і ліміти потужності.
- Розігрів: прогін розігріву (5–15 хв залежно від кешів/JIT/DB).
- Фаза вимірювання: фіксована тривалість (10–30 хв) зі стабільним навантаженням.
- Збирайте метрики: розподіл латентності, throughput, error rate, run queue, PSI, тротлінг cgroup, знімок perf-лічильників.
- Повторіть: не менше 3 прогонів на сценарій; викидайте очевидні викиди лише з причини (steal spike, розгортання).
- Порівнюйте за SLO: який CPU досягає вашого p99-ціля з найнижчими витратами й ризиком?
- Документуйте стенд: команди, конфіги і односторінкове «що змінилось».
Чеклист B: Швидкий план «це CPU чи ні?»
- Погляньте на p99 латентність і рівень помилок спочатку. Якщо користувачі не страждають — не панікуйте.
- Перевірте PSI і run queue. Якщо обидва низькі — CPU не є обмежувальним ресурсом.
- Перевірте тротлінг cgroup і steal time. Якщо присутні — виправте середовище перед інтерпретацією результатів.
- Перевірте системний CPU і переривання. Якщо системний час високий — ви боретесь з шляхом ядра/NIC.
- Лише потім профілюйте додаток. Не профілюйте сліпо; приносіть докази.
Чеклист C: Вирішуємо, що купувати (або чи варто налаштувати замість цього)
- Якщо навантаження чутливе до кешу/пам’яті: віддавайте пріоритет більшим кешам, кращій пропускній пам’яті і NUMA-дружності понад піковий single-core boost.
- Якщо навантаження легко розпаралелюється: важливі ядра, але стежте за спадною віддачею через блокування та спільні ресурси.
- Якщо навантаження критичне до латентності: зменшіть джерела джиттера (SMT, міграції, тротлінг), віддавайте перевагу стабільній all-core продуктивності і вимірюйте p99 під сплеском.
- Якщо ви в контейнерах: вважайте тротлінг першокласним обмеженням; «швидкий CPU» може стати повільним політикою.
FAQ
1) Чи варто використовувати синтетичні бенчмарки взагалі?
Використовуйте їх як орієнтири, а не як остаточні рішення. Вони допомагають виявити зламані хости (погане охолодження, неправильний BIOS, turbo off). Вони не замінюють тестів на робочому навантаженні.
2) Скільки прогонів — «достатньо» для впевненості?
Для стабільного середовища три прогони на сценарій — розумний мінімум. Якщо варіативність висока, виправлення варіабельності — це справжня робота; більше прогонів лише кількісно вимірює хаос.
3) Яка найкорисніша метрика CPU, окрім завантаження?
CPU Pressure Stall Information (PSI) — надзвичайно практична. Вона відповідає на питання: «Чи чекають завдання часу CPU?» Вона добре корелює з видимою користувачем латентністю при насиченні.
4) Чи є iowait надійним сигналом, що я IO-bound?
Це підказка, а не вирок. iowait може бути низьким навіть коли IO — ваш вузький пляшок (асинхронний IO, блокування в інших місцях), і може бути високим через артефакти планування. Поєднуйте з iostat -xz і таймінгами додатку.
5) Чи варто вимикати SMT у продакшні?
Іноді. Якщо ваше навантаження heavy-lock, чутливе до кешу або критичне по p99, SMT може підвищити джиттер. Якщо навантаження орієнтоване на throughput і часто стоїть у застоях — SMT може допомогти. Тестуйте обидва варіанти; не вгадуйте.
6) Чому мій бенчмарк CPU чудовий 30 секунд, а потім гіршає?
Turbo і термальні ліміти. Багато CPU агресивно підвищують частоту, поки не досягнуть тривалих обмежень потужності/тепла. Запускайте досить довго, щоб дійти до steady state, і моніторьте частоти/температури.
7) Чи можу я робити реальне тестування CPU на shared cloud інстансах?
Можете робити workload-тести, але порівняння CPU ризиковане. Steal time, шумні сусіди і варіабельні політики turbo додають дисперсії. Якщо потрібно, використовуйте виділені інстанси або принаймні записуйте %steal і запускайте довше.
8) Що робити, якщо perf обмежений у моєму середовищі?
Тоді більше покладайтеся на зовнішні перцентилі латентності, PSI, run queue і профілювання на рівні додатку. Perf-лічильники чудові, але не обов’язкові для правильного рішення.
9) Як дізнатись, чи я обмежений пропускною пам’яті, а не обчисленнями?
Шукайте низький IPC і високі backend stalls під навантаженням, а також чутливість до pinning NUMA і швидкості пам’яті. Якщо продуктивність різко покращується при покращенні локальності — це не просто «CPU».
10) Який реалістичний критерій успіху для зміни CPU?
Визначте його як збільшення ємності при фіксованому SLO: «При p99 < X ms ми можемо обробити Y% більше трафіку.» Це число, на якому можна будувати бізнес-рішення.
Висновок: наступні кроки, які дійсно працюють
Реальне тестування CPU — це не про перемогу в суперечках. Це про покупку (або налаштування) продуктивності, яку ви зможете підтримати о 3:00 ночі, коли кеш холодний, трафік вибуховий, і чийсь cron робить щось «корисне».
Зробіть наступне:
- Запишіть один SLO-орієнтований сценарій (офероване навантаження + p99-ціль) і один сценарій «поганого дня».
- Побудуйте повторюваний стенд: розігрів, вимірювання, запис середовища, збір латентності + PSI + run queue + тротлінг.
- Запустіть тричі на поточному CPU і встановіть базу, якій довіряєте.
- Змініть одну змінну (модель CPU, SMT, квота) і повторіть. Якщо ви зміните п’ять речей, ви нічого не дізнаєтесь.
- Приймайте рішення за рівним болем користувача: виберіть CPU (або налаштування), що відповідає p99 з найменшим оперативним ризиком.
Якщо ви запам’ятаєте лише одне: вимірюйте навантаження, а не залізо. Залізо — лише сцена. Ваше програмне забезпечення — вистава. А продакшн — рецензент, який ніколи не спить.