AMD Bulldozer: сміливий дизайн, що не виправдав очікувань

Було корисно?

Ви не запам’ятовуєте CPU за маркетинговими слайдами. Ви пам’ятаєте його за звуком пейджера: 99-й процентиль розм’якшується,
рахунок за електрику підскакує, і незручна нарада, де хтось каже: «Але в нього ж вісім ядер».

Ера Bulldozer від AMD була саме таким уроком. Це не був тупий дизайн. Це був сміливий дизайн, який вимагав, щоб програмне
забезпечення та навантаження зустріли його посередині. У продакшені «зустрінь мене посередині» зазвичай означає «вранці о 2-й ти сам».

Що Bulldozer намагався зробити (і чому це звучало розумно)

Bulldozer з’явився з одним припущенням: світ іде вшир. Потоки скрізь. Веб-сервери, віртуальні машини, бекграундні задачі, пакетні
конвеєри — більше runnable-роботи, ніж можна було б кинути на традиційне «товсте ядро» без марнотратства кремнію.

Відповіддю AMD став модуль. Кожен модуль містив два цілочислові ядра, які ділили деякі дорогі front-end та плаваючі точки
(FP) ресурси. Ідея: для сильно багатопотокових навантажень ви отримуєте майже в 2× продуктивність за меншу, ніж 2×, площу
і потужність. Це як дві студії, що ділять кухню. Ефективно — поки обидва мешканці не вирішать готувати різдвяний обід одночасно.

Дизайн не був повною новинкою. Він відповідав тренду: дата-центри дбали про пропускну здатність на стійку, а не лише про
однопотокову максимальну продуктивність. І AMD мала реальну проблему: вони не могли продовжувати масштабувати традиційні
монолітні ядра так само швидко, як домінантні на той час мікроархітектури Intel.

Де ставка зігнулася: програмне забезпечення не було «модульно-обізнане» за замовчуванням. Багато реальних навантажень не були
такими паралельними, як припускав роадмап. А спільні частини — особливо front-end і FP — стали пляшковими горлечками саме в
тих місцях, де їх не хотілося бачити: у системах, чутливих до затримки, у змішаних навантаженнях і в усьому, що нагадує «кілька гарячих потоків, що виконують реальну роботу».

Надійна операційна рамка: Bulldozer не був «поганим CPU». Це був CPU, який вимагав розуміння, де насправді знаходиться ваш вузький
місце. Якщо ви помилилися в здогадці, ви втрачали не 5%. Ви втрачали квартал.

Факти й контекст, які варто запам’ятати

Історична дрібниця корисна лише якщо вона змінює ваші рішення. Ось пункти, що справді мають значення.

  1. Bulldozer дебютував у 2011 році (серія FX для десктопів, Opteron 6200 для серверів), замінюючи ядра стилю K10/Phenom II модульним підходом.
  2. «Вісім’ядерні» моделі FX зазвичай мали чотири модулі: вісім цілочислових ядер, але не вісім повністю незалежних front-end і FP-блоків.
  3. Кожен модуль ділив один комплекс плаваючої точки (дві 128-бітні FMAC, які можуть комбінуватися в 256-бітний AVX), тому FP-інтенсивні двопотокові навантаження в модулі могли конфліктувати.
  4. Існувала реальна історія зі schedулером Linux: на початку планувальники могли погано пакувати потоки, фактично спричиняючи уникнуту внутрішньомодульну контенцію.
  5. Планування у Windows також мало значення: розміщення потоків впливало на продуктивність у способи, до яких користувачі не звикли на звичних ядрах.
  6. Bulldozer привніс підтримку AVX для AMD, але реалізація й поведінка конвеєра навколо неї не завжди давали реальні виграші.
  7. Поведінка живлення та турбо була центральною: рекламовані частоти виглядали добре в специфікаціях, але стійкі тактові частоти під навантаженням могли бути іншою, теплішою реальністю.
  8. Це не було одноразовим: Piledriver та пізніші ітерації покращували окремі частини (частоти, деякий IPC, управління потужністю), але фундаментальна ставка на модулі залишалася.

Сухий жарт для розвантаження: маркетинг Bulldozer навчив важливого уроку — «up to» це одиниця вимірювання для надії, а не для пропускної здатності.

Реальність модуля: куди йшли цикли

1) Спільний front-end: отримання й декодування інструкцій платне

Сучасні ядра живуть і помирають залежно від того, наскільки добре вони підгодовують виконувальну частину. Модуль Bulldozer ділив
важливі front-end механізми між двома цілочисловими ядрами. Це означає, що коли ви запускаєте два завантажені потоки в одному модулі,
вони можуть зіткнутися ще на стадії «підготувати інструкції», перш ніж ви почнете сперечатися про execution ports.

Практично: два цілочислові потоки, що виглядають “легкими” у відсотках CPU, все одно можуть сильно навантажувати front-end:
багато розгалужень, великий кодовий відбиток, часті промахи в кеші інструкцій, велика потреба в декодуванні. Моніторинг каже
«CPU добре», але затримка каже інакше.

2) Спільний FP: прихований податок для змішаних навантажень

Ресурси плаваючої точки у Bulldozer ділилися в межах модуля. Якщо у вас був один FP-важкий потік і один здебільшого цілочисловий,
ви могли бути в нормі. Якщо два FP-важкі потоки були прив’язані (або заплановані) в той самий модуль, продуктивність могла стати такою,
ніби хтось замінив ваш CPU ввічливою комісією.

Це особливо важливо для:

  • конвеєрів стиснення/розпакування
  • крипто-стеків, що використовують векторні обчислення
  • обробки медіа
  • наукового коду
  • деяких поведінок JVM і .NET під JIT із векторними шляхами

3) IPC: незручний розрив

Великий публічний заголовок для Bulldozer — розчарування в IPC (інструкцій за такт) порівняно з contemporaries Intel.
Можна говорити про глибину конвеєра, поведінку гілок і ефекти кешу довго. Оператори сприймають це як:
«Чому ця коробка на 3.6 ГГц відчувається повільнішою за ту на 3.2 ГГц?»

IPC — це композитний симптом. Він проявляється коли front-end не здатний підгодовувати back-end, коли спекуляція не окупається,
коли латентність пам’яті погано ховається, і коли ваше навантаження не відповідає припущенням CPU. Припущення Bulldozer сильно
ґрунтувалися на великій кількості runnable-потоків і менше — на «одне гаряче ядро має бути швидким».

4) Модуль — одиниця планування, подобається вам чи ні

Якщо ви трактуєте 8-цілочислове Bulldozer CPU як «8 симетричних ядер», ви робитимете погані рішення. Межа модуля має значення
для контенції. Це не філософія — це вимірювано.

Коли планувальники обізнані про модулі, вони намагаються розподіляти гарячі потоки по модулях спочатку, а потім заповнювати другий
потік у кожному модулі. Коли ні — ви можете опинитися з двома важкими потоками в одному модулі, в той час як інший модуль простає —
класична ситуація «CPU 50%, а сервіс вмирає».

Одна цитата варта того, щоб повісити на стіну, бо вона описує, як вижити з такими архітектурами:
Надія — не стратегія. — General Gordon R. Sullivan

Bulldozer карав планування надії. Він винагороджував вимірювання, pinning і реалістичні бенчмарки.

Підходящі навантаження: де Bulldozer сильний, де провалюється

Добрі варіанти (з умовами)

Bulldozer міг виглядати гідно у навантаженнях, які були:

  • сильно багатопотоковими і з низькою чутливістю до затримки на потік
  • цілочислово-важкими і терпимими до front-end контенції
  • орієнтованими на пропускну здатність пакетні роботи, де можна перепідписуватися й утримувати машину завантаженою
  • сервісами, що масштабуються майже лінійно з додатковими runnable-потоками (деякі веб-навантаження, деякі ферми збірки)

В таких випадках модульний підхід може забезпечити гідну роботу за долар — особливо якщо ви купували його в потрібний момент і не порівнювали з фантазією.

Погані варіанти (ті, через які людей звільняли)

Bulldozer зазвичай розчаровував у:

  • заточених на затримку одно-потокових вузьких місць (гарячі блокування, паузи GC, однопотокові обробники запитів, точки серіалізації)
  • FP/вектор-важких завданнях коли потоки конфліктують в модулі
  • іграх та інтерактивних навантаженнях де кілька потоків домінують у часі кадру
  • змішаній оренді коли «шумні сусіди» непередбачувано конкурують за спільні ресурси модуля

Переклад бізнесовою мовою: якщо ваш бізнес — «відповідати швидко», Bulldozer вимагав більше уваги. Якщо бізнес — «закінчити колись», Bulldozer
міг підійти — поки потужність або терміка не стали наступним обмеженням.

Бізнесова пастка: «ядра» як вигадка закупівель

Підприємства люблять числа, які можна вклеїти в таблиці. «Ядра» — одне з таких чисел. Bulldozer скористався цією слабкістю,
іноді ненавмисно, іноді ні.

Якщо ваша ліцензія, ємність або математика SLO припускають, що кожне «ядро» еквівалентне кожному «ядру» іншого вендора — зупиніться.
Перекалібруйтеся навколо виміряної пропускної здатності, а не за кількістю міток. Це вірно і сьогодні з SMT, енергоефективними ядрами та спільними акселераторами.
Bulldozer був лише раннім, гучним нагадуванням.

Погляд SRE: режими відмов, які ви можете спостерігати

В операціях ви не дебажите мікроархітектуру напряму. Ви дебажите симптоми: черги, хвостову затримку, глибину run-queue,
steal time, затримки пам’яті, термальне дроселювання, артефакти планувальника.

Симптоми, типові для Bulldozer

  • Висока затримка при помірному завантаженні CPU через те, що «завантажена» робота конкурує за спільні ресурси модуля.
  • Варіабельність продуктивності на ідентичних хостах через налаштування BIOS, C-states, поведінку turbo і розміщення потоків.
  • Сюрпризи віртуалізації коли топологія vCPU не відповідає топології модулів, спричиняючи уникальну контенцію або NUMA-промахи.
  • Обмеження потужності/терміки які перешкоджають стійкому бусту, роблячи «заявлену частоту» неважливою в довготривалих тестах.

Другий жарт, і на цьому все: трактувати ядра Bulldozer як однакові — це як вважати всі черги FIFO — заспокоює, поки ви не подивитесь на графіки.

Чому інженерам зі зберігання даних варто хвилюватися (так, серйозно)

Стеки зберігання повні CPU-роботи: контрольні суми, стиснення, шифрування, обробка мережі, метадані файлової системи, обробка переривань і координація в юзерленді.
CPU з дивною картиною контенції може перетворити «проблему диска» на проблему планування CPU під чужою маскою.

Якщо ви працюєте з мережевим зберіганням, шлюзами об’єктного зберігання або будь-чим суміжним до ZFS на підлозі Bulldozer, потрібно вміти відокремити
«пристрій повільний» від «CPU погано справляється з цим типом роботи». Виправлення може бути в перемикачі планувальника, а не в новому SSD.

Швидкий план діагностики

Коли хост епохи Bulldozer працює не так, не починайте з аргументів про IPC на форумах. Почніть з локалізації вузького місця у трьох проходах:
планування/топологія, частоти/живлення, потім пам’ять/IO.

Перший крок: перевірте розміщення потоків і вирівнювання топології

  • Підтвердіть, скільки у вас сокетів, NUMA-вузлів і ядер насправді.
  • Перевірте, чи не упаковані гарячі потоки в той самий модуль.
  • У віртуальних машинах підтвердіть, що vCPU-топологія, представлена гостю, відповідає тому, що гіпервізор може забезпечити фізично.

Другий крок: переконайтесь, що ви не програєте через частоти (governor, turbo, терміка)

  • Перевірте частоту CPU під стійким навантаженням, а не в режимі простою.
  • Переконайтесь, що governor налаштований відповідно до серверних навантажень.
  • Шукайте термальне дроселювання, обмеження потужності або надмірно агресивні C-states.

Третій крок: виміряйте stalls і черги (CPU vs пам’ять vs IO)

  • Використовуйте perf, щоб побачити, чи ви stalled по front-end, backend або чекаєте пам’ять.
  • Перевірте глибину run-queue і переключення контексту.
  • Підтвердіть, що переривання диску та мережі не навалюються на якийсь нещасливий CPU.

Правило прийняття рішення: якщо виправлення розміщення і частот покращує затримку на десятки відсотків — зупиніться і стабілізуйте.
Якщо ні — переходьте до глибшого профілювання.

Практичні завдання: 12+ перевірок з командами, виводами та рішеннями

Це не «бенчмарки для блогу». Це перевірки, які ви запускаєте на коробці, що не виконує SLO. Кожне завдання включає:
команду, приклад виводу, що це значить і яке рішення прийняти.

1) Швидко визначити модель CPU і топологію

cr0x@server:~$ lscpu
Architecture:            x86_64
CPU(s):                  16
Thread(s) per core:      1
Core(s) per socket:      8
Socket(s):               2
NUMA node(s):            2
Model name:              AMD Opteron(tm) Processor 6272
NUMA node0 CPU(s):       0-7
NUMA node1 CPU(s):       8-15

Що це значить: Ймовірно Opteron 6200-series (нащадок Bulldozer). Thread(s)-per-core = 1 (немає SMT), але модулі все ще ділять ресурси.

Рішення: Розглядайте планування і pinning як критичні. Також перевірте афінність NUMA для сервісів, що важать пам’яттю.

2) Підтвердити версію ядра та контекст планувальника

cr0x@server:~$ uname -r
5.15.0-94-generic

Що це значить: Сучасне ядро, загалом краще обізнане про топологію, ніж ранні 3.x. Але: ваш гіпервізор, BIOS і навантаження можуть це обійти.

Рішення: Не вважайте, що «нове ядро вирішило проблему». Вимірюйте поведінку розміщення справжніми картами потоків.

3) Подивитись завантаження по CPU, щоб знайти упакування

cr0x@server:~$ mpstat -P ALL 1 3
Linux 5.15.0-94-generic (server)  01/21/2026  _x86_64_ (16 CPU)

12:00:01 AM  CPU   %usr  %sys  %iowait  %irq  %soft  %idle
12:00:02 AM  all   42.10  6.20   0.10   0.20  0.50  50.90
12:00:02 AM    0   88.00  8.00   0.00   0.00  0.00   4.00
12:00:02 AM    1   86.00 10.00   0.00   0.00  0.00   4.00
12:00:02 AM    2    5.00  2.00   0.00   0.00  0.00  93.00
...

Що це значить: CPU0 і CPU1 завалені, а CPU2 простає. Запахне упакуванням потоків, проблемами IRQ-афінності або прив’язаним процесом.

Рішення: Знайдіть процес і його CPU-афінність; виправте pinning або відрегулюйте планувальник/розподіл IRQ.

4) Знайти гарячий процес і його розклад потоків

cr0x@server:~$ ps -eo pid,comm,%cpu --sort=-%cpu | head
  PID COMMAND         %CPU
 8421 java            175.3
 2310 ksoftirqd/0      35.0
 1023 nginx            22.4

Що це значить: JVM використовує ~1.75 ядра; ksoftirqd/0 теж завантажений на CPU0. Імовірна контенція.

Рішення: Перевірте розміщення потоків та розподіл переривань перед тим, як чіпати JVM-флаги.

5) Переглянути відображення потоків на CPU для гарячого PID

cr0x@server:~$ pid=8421; ps -L -p $pid -o pid,tid,psr,pcpu,comm --sort=-pcpu | head
 PID   TID  PSR  %CPU COMMAND
8421  8421    0  98.5 java
8421  8434    1  77.2 java
8421  8435    0  15.0 java
8421  8436    1  12.1 java

Що це значить: Потоки застрягли на CPU0/CPU1. На Bulldozer це може означати «два потоки борються в одному модулі» залежно від нумерації і топології.

Рішення: Приберіть надто жорстку афінність; розподіліть по ядрах/модулях; або явне pinning критичних потоків по модулях.

6) Перевірити маску афінності CPU процесу

cr0x@server:~$ taskset -pc 8421
pid 8421's current affinity list: 0-1

Що це значить: Хтось прив’язав JVM до CPU 0–1. Це «ми його оптимізували» фатальне рішення.

Рішення: Розширте до розумного набору (наприклад, розподіл по модулях або NUMA-вузлу), потім повторно виміряйте затримку.

7) Застосувати безпечнішу афінність (приклад) і перевірити

cr0x@server:~$ sudo taskset -pc 0-7 8421
pid 8421's current affinity list: 0-7

Що це значить: Процес тепер може виконуватися на CPU 0–7 (один NUMA-вузол у цьому прикладі).

Рішення: Якщо навантаження локальне по пам’яті — тримайте в межах NUMA-вузла. Якщо CPU-bound і багатопотокове — обережно використовуйте обидва вузли.

8) Перевірити governor частоти CPU і поточну політику

cr0x@server:~$ cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
powersave

Що це значить: «powersave» на сервері під навантаженням може бути катастрофічним для затримки.

Рішення: Переключіть на «performance» для заточених на затримку сервісів, потім перевірте терміку і запас потужності.

9) Тимчасово переключити governor на performance і перевірити

cr0x@server:~$ sudo cpupower frequency-set -g performance
Setting cpu: 0
Setting cpu: 1
Setting cpu: 2
Setting cpu: 3
Setting cpu: 4
Setting cpu: 5
Setting cpu: 6
Setting cpu: 7
Setting cpu: 8
Setting cpu: 9
Setting cpu: 10
Setting cpu: 11
Setting cpu: 12
Setting cpu: 13
Setting cpu: 14
Setting cpu: 15

Що це значить: Усі CPU тепер прагнуть максимальної частоти під навантаженням.

Рішення: Перезапустіть навантажувальний тест сервісу; якщо хвостова затримка знизилась — зробіть зміну постійною через CM.

10) Спостерігати реальні частоти під навантаженням

cr0x@server:~$ sudo turbostat --Summary --interval 2
turbostat: Snapshot every 2.0 sec
Avg_MHz   Busy%   Bzy_MHz  IRQ     SMI
2875      62.10   4630     1234    0

Що це значить: CPU бустяться (Bzy_MHz вищий за Avg_MHz). Якби Bzy_MHz був низький, підозрюйте дроселювання або політику.

Рішення: Якщо буст колапсує під стійким навантаженням — розслідуйте терміку, ліміти BIOS або обдув корпусу.

11) Перевірити тиск в run-queue і переключення контексту

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
 4  0      0 812340  90212 993220    0    0     1     2 1100 4200 45  6 49  0  0
 9  0      0 811900  90212 993500    0    0     0     0 2100 9800 52  8 40  0  0

Що це значить: Run-queue (r) зростає до 9 на 16-CPU системі; переключення контексту (cs) високі. Може бути перепідписування або контенція замків.

Рішення: Для чутливих до затримки — зменшіть кількість runnable-потоків (розміри пулів), виправте lock-hotspots або переконайтеся, що потоки не конкурують в межах модулів.

12) Інспектувати NUMA-локальність (розміщення пам’яті відносно CPU)

cr0x@server:~$ numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7
node 0 size: 32768 MB
node 0 free: 12000 MB
node 1 cpus: 8 9 10 11 12 13 14 15
node 1 size: 32768 MB
node 1 free: 9000 MB

Що це значить: Два NUMA-вузли. Якщо процес виконується на CPU вузла 0, але алокує пам’ять на вузлі 1 — ви платите за віддалену латентність.

Рішення: Прив’яжіть процес і пам’ять до одного вузла для стабільної затримки (numactl --cpunodebind/--membind), або виправте розміщення VM.

13) Перевірити, чи переривання зосереджені на одному CPU

cr0x@server:~$ cat /proc/interrupts | head -n 10
           CPU0       CPU1       CPU2       CPU3       CPU4       CPU5       CPU6       CPU7
  24:    982312          0          0          0          0          0          0          0   IO-APIC  24-fasteoi   eth0
  25:      1200        980        870        910        940        905        899        920   IO-APIC  25-fasteoi   ahci

Що це значить: Мережеві IRQ приходять майже виключно на CPU0. Це може позбавляти реальної роботи і створювати модульну контенцію.

Рішення: Налаштуйте IRQ-афінність або увімкніть/перевірте irqbalance; для високопродуктивних NIC розподіліть черги по CPU і по NUMA-локальних ядрах.

14) Швидка перевірка затримки зберігання (бо її завжди звинувачують)

cr0x@server:~$ iostat -x 1 3
avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          44.21    0.00    6.44    0.20    0.00   49.15

Device            r/s   w/s  rkB/s  wkB/s  await  svctm  %util
sda              2.0   8.0   64.0  512.0   3.10   0.45   0.90

Що це значить: Диск не є вузьким місцем (await низький, %util низький). Тикет «накопичення повільне» — скоріш за все проблема планування CPU або поведінки додатка.

Рішення: Перестаньте купувати диски. Зосередьтесь на розміщенні CPU, IRQ і контенції замків.

15) Виміряти, куди йде CPU-час (зверху вниз) за допомогою perf

cr0x@server:~$ sudo perf stat -p 8421 -a -- sleep 10
 Performance counter stats for 'system wide':

        32,145.12 msec task-clock                #    3.214 CPUs utilized
       120,331,221      context-switches          #    3.742 M/sec
         2,110,334      cpu-migrations            #   65.655 K/sec
    98,771,234,112      cycles                    #    3.070 GHz
    61,220,110,004      instructions              #    0.62  insn per cycle
     9,112,004,991      branches                  #  283.381 M/sec
       221,004,112      branch-misses             #    2.43% of all branches

Що це значить: IPC ~0.62 низький для багатьох серверних навантажень; міграції і переключення контексту величезні, що натякає на плутанину планувальника.

Рішення: Зменшіть міграції (афінність/NUMA binding), правильно розміркуйте пул потоків і ізолюйте IRQ. Потім заново виміряйте IPC і затримку.

16) Перевірити steal time у віртуалізації (якщо застосовно)

cr0x@server:~$ mpstat 1 3 | tail -n 3
12:01:01 AM  all   40.20  7.10   0.10   0.20  0.50  48.40  3.50
12:01:02 AM  all   41.10  7.30   0.10   0.20  0.40  47.60  3.30
12:01:03 AM  all   39.80  6.90   0.10   0.20  0.50  49.10  3.40

Що це значить: %steal ~3–4% вказує, що VM чекає на фізичний CPU. На Bulldozer-хостах погане вирівнювання vCPU→модуль може посилити проблему.

Рішення: Узгодьте з командою гіпервізора: забезпечте pinning CPU, вирівняний по модулях/NUMA, зменшіть overcommit для заточених на затримку орендарів.

Три корпоративні міні-історії (реалістичні, анонімізовані)

Міні-історія 1: Інцидент через неправильне припущення

Середня SaaS-компанія мігрувала API-рівень з великим числом читань зі старих quad-core на нові «8-ядерні» сервери. У презентації закупівлі
писалося, що вони подвоїли ядра на вузол, тож скоротили кількість вузлів на третину. Роллаут працював добре дві години, потім 99-й процентиль
затримки повільно почав зростати.

Інженер на виклику зробив звичне: перевірив затримку диска (нормально), помилки мережі (немає), завантаження CPU (дивно помірне). Убивчою підказкою стало
розміщення потоків: робочі потоки сервісу були прив’язані в успадкованому systemd unit до CPU 0–3 «для локальності кешу». На цьому залізі ці CPU
відповідали меншій кількості модулів, ніж очікувалось, і гарячі потоки билися за спільний front-end.

Вони спробували знову масштабувати — додати вузли — але бюджет був уже витрачений. Тож зробили нудну роботу: прибрали старе pinning, перевірили розподіл,
дружній до модулів, і підправили числа воркерів, орієнтуючись на реальну пропускну здатність, а не на кількість «ядер». Хвостова затримка впала до прийнятних рівнів.

Постмортем не був про AMD чи Intel. Він був про припущення: що «8 ядер» означає «вдвічі більше паралелізму, ніж 4».
На Bulldozer топологія має таке значення, що це припущення може бути операційно хибним.

Міні-історія 2: Оптимізація, що обернулась проти команди

Команда фінансової аналітики запускала Monte Carlo симуляції вночі. Вони налаштовували все: прапорці компілятора, huge pages, прив’язку потоків,
власні алокатори пам’яті. Хтось помітив, що прогони швидші, коли два воркери прив’язані до тієї ж парі модулів — менше міжмодульної комунікації, вважали вони. Це «працювало» на мікробенчмарку і виглядало як геніальна ідея.

Потім вони оновили математичну бібліотеку, яка стала використовувати ширші векторні інструкції і трохи іншу стратегію потоків. Тепер два потоки в модулі почали навантажувати спільні FP-ресурси. Пропускна здатність впала. Гірше — стала шумною: іноді робота закінчувалась до робочого дня, іноді ні.

Команда відреагувала як зазвичай: підправляли числа потоків і звинувачували бібліотеку. Справжнє виправлення — топологічно-обізнане pinning:
розподіляти FP-важкі потоки по модулях, і парувати потоки в модулі лише коли можна довести, що вони не конкурують за спільний блок у вашому фактичному наборі інструкцій.

Урок: оптимізація, що покладається на «стабільну мікроархітектурну поведінку», крихка. Bulldozer зробив цю крихкість видимою, але та сама пастка
існує й на сучасних CPU з shared кешами, SMT і динамічними частотами.

Міні-історія 3: Сумне, але правильне правило, що врятувало день

Внутрішня платформа мала змішаний флот: деякі вузли Intel, деякі — епохи Bulldozer. У них було непопулярне правило:
кожна зміна ємності вимагала запуску бенчмарка, специфічного для навантаження, і збереженого артефакту (графіки, конфіги та точні версії ядра/BIOS).
Люди нарікали. Це уповільнювало «прості оновлення».

Продуктова команда хотіла запустити фічу, що подвоювала парсинг JSON і додавала навантаження TLS. На Intel-вузлах все було нормально.
На Bulldozer-вузлах завантаження CPU росло, а хвостова затримка почала хитатися під навантаженнями. Оскільки команда платформи мала базові профілі, вони одразу побачили дельту:
більше циклів у крипто та парсингу, вищий тиск на гілки і гірший IPC.

Вони не купили імпульсно нове залізо. Вони розділили флот: Bulldozer-вузли обробляли менш чутливі пакетні роботи; Intel-вузли — інтерактивний рівень.
Також вони налаштували IRQ-афінність і governor на залишкових Bulldozer-боксах, щоб зменшити джиттер. Фіча вийшла вчасно.

Ніхто не отримав трофея за «збережені базові бенчмарки». Але це запобігло інциденту в релізний тиждень. В SRE-термінах — це трофей.

Поширені помилки: симптом → корінь → виправлення

1) Симптом: стрибки затримки при ~50% CPU

Корінь: Гарячі потоки конкурують в межах модулів (спільний front-end / FP) або прив’язані до вузького набору CPU.

Виправлення: Інспектуйте розміщення потоків (ps -L), приберіть жорстку афінність (taskset -pc), розподіліть гарячі потоки по модулях і зменшіть плутанину планувальника.

2) Симптом: бенчмарки виглядають чудово, а в продакшені гірше

Корінь: Мікробенчмарки вміщаються в кеш, уникають FP-контенції і працюють в ідеальних turbo-умовах.

Виправлення: Використовуйте стійкі тести з репрезентативними наборами даних; вимірюйте частоту в часі (turbostat) і хвостову затримку, а не тільки пропускну здатність.

3) Симптом: продуктивність VM непослідовна між хостами

Корінь: vCPU-топологія не вирівнована з фізичними модулями/NUMA; overcommit і steal time посилюють модульну контенцію.

Виправлення: Використовуйте pinning на хості вирівняний з NUMA; не дозвольте latency-sensitive VM ділити модулі під навантаженням; моніторьте %steal.

4) Симптом: тікети «зберігання повільне», але диски прості

Корінь: Компоненти стеку зберігання CPU-bound (контрольні суми, стиснення, шифрування) або концентрація IRQ на одному CPU.

Виправлення: Підтвердіть за допомогою iostat -x і /proc/interrupts; перерозподіліть IRQ; розгляньте вимкнення CPU-важких фіч на цих хостах або переміщення навантаження.

5) Симптом: продуктивність падає після ініціатив з енергозбереження

Корінь: Governor встановлений у powersave, глибокі C-states або агресивні BIOS-політики знижують стійку частоту.

Виправлення: Встановіть governor у performance для критичних сервісів; аудитуйте BIOS-параметри по всьому флоту; перевірте терміку.

6) Симптом: два «однакові» сервери поводяться інакше

Корінь: Різниці в BIOS (turbo, C-states), мікрокоді, наповненні DIMM впливають на NUMA, або різне маршрутування IRQ.

Виправлення: Стандартизувати BIOS-профілі, підтвердити пакети мікрокоду, порівняти lscpu/numactl --hardware, та диференціювати розподіл переривань.

Контрольні списки / покроковий план

Коли приймаєте Bulldozer-флот (план на перший тиждень)

  1. Інвентаризація топології: збережіть lscpu, numactl --hardware і версії ядра для кожного класу хостів.
  2. Стандартизувати BIOS і політику живлення: підтвердіть C-states, поведінку turbo і будь-які power cap; оберіть консистентний профіль.
  3. Встановити і забезпечити політику governor: «performance» для latency-tier; документувати винятки.
  4. Базові бенчмарки для кожного навантаження: один інтерактивний і один пакетний профіль; зберігати виводи і конфіги.
  5. Аудит pinning CPU: grep-ніть unit-файли, конфіги контейнерів і політики оркестрації на предмет афінності, що припускає симетричні ядра.
  6. Аудит розподілу IRQ: особливо черги NIC; підтверджуйте за /proc/interrupts під навантаженням.
  7. Правила розміщення NUMA: визначте, які сервіси прив’язувати до одного вузла, а які — розподіляти; вбудуйте це в інструменти деплою.

Коли сервіс працює гірше на Bulldozer (60-хвилинний триаж)

  1. Перевірити завантаження по CPU (mpstat -P ALL) на предмет упакування.
  2. Перевірити афінність процесу (taskset -pc) і потоки (ps -L).
  3. Перевірити governor і реальні частоти (sysfs cpufreq + turbostat).
  4. Перевірити run-queue і переключення контексту (vmstat).
  5. Перевірити NUMA-розташування і чи випадково не віддалена пам’ять (numactl --hardware + політика розміщення).
  6. Перевірити точки скупчення IRQ (/proc/interrupts) і розподіл при необхідності.
  7. Лише потім: профілюйте з perf stat, щоб підтвердити CPU-bound проти memory-bound.

Коли плануєте міграцію (що виміряти перед купівлею)

  1. Виміряти пропускну здатність і хвостову затримку на ват при стійкому навантаженні.
  2. Квантифікувати, скільки часу ваше навантаження проводить у FP/векторі, парсингу з великою кількістю гілок або в затримках пам’яті.
  3. Симулювати сценарії «шумних сусідів», якщо ви використовуєте змішані навантаження або віртуалізацію.
  4. Цінувати ліцензування з реалістичною еквівалентністю ядер (або не використовувати ядра зовсім — вимірювати одиниці пропускної здатності).

Питання та відповіді

1) Чи був Bulldozer «справжньо вісім ядер»?

У звичних «8-core» частинах FX було вісім цілочислових ядер (чотири модулі), але деякі ключові ресурси були спільними на рівні модуля.
Для багатьох навантажень таке спільне використання робить його відмінним від восьми повністю незалежних ядер.

2) Чому Bulldozer так погано програвав у деяких однопотокових бенчмарках?

Бо дизайн робив акцент на пропускній здатності з розділеними ресурсами, і ефективність front-end і виконання на потік не відповідала конкурентам,
оптимізованим під одиночний IPC того часу.

3) Чи дійсно планувальник ОС мав таке велике значення?

Так. Розміщення потоків, що збирає гарячі потоки в одному модулі, може викликати уникальну контенцію. Краща поведінка планувальника допомагає,
але pinning навантаження, топологія віртуалізації і розподіл IRQ усе ще мають значення.

4) Чи Bulldozer завжди поганий для серверів?

Ні. Для достатньо паралельних, орієнтованих на пропускну здатність, здебільшого цілочислових навантажень він може бути придатним. Ризик у тому,
що багато «серверних навантажень» мають хвостову затримку, яка домінована невеликою кількістю потоків.

5) Який найпростіший операційний виграш, якщо я застряг з Bulldozer?

Виправте політику частот і розміщення: використайте правильний governor, перевірте стійкі частоти, приберіть погане pinning і не дозволяйте IRQ
таборуватися на CPU0.

6) Як зрозуміти, що я страждаю саме від модульної контенції?

Часто ви побачите високу затримку при помірному середньому CPU, нерівномірне завантаження CPU, низький IPC у perf stat і покращення при розподілі потоків
або зменшенні парування в межах модуля.

7) Чи пізніші архітектури AMD тієї ж історії?

Ні. Пізніші дизайни AMD відходили від модульних компромісів Bulldozer у спосіб, який значно покращив продуктивність на ядро
і зменшив несподівані «спільні» вузькі місця. Не узагальнюйте боль Bulldozer на всі AMD CPU.

8) Чи варто вимикати функції енергозбереження на цих системах?

Для tier-ів, чутливих до затримки, часто — так, принаймні відключити найагресивніші політики і використовувати performance governor.
Але перевірте терміку і потужність; не жертвуйте стабільністю раптовим дроселюванням.

9) Чи це важливо, якщо я запускаю контейнери замість VM?

Це може бути ще важливішим. Контейнери полегшують випадкове перепідписування CPU і застосування наївного pinning. Якщо ваш оркестратор не обізнаний про топологію,
ви можете створити контенцію, яка виглядає як «таємне уповільнення».

10) Чи варто купувати вживане залізо Bulldozer для лабораторії?

Для вивчення планування, NUMA і діагностики продуктивності воно — дивовижний викладач. Для ефективного, передбачуваного обчислення на ваті можна знайти кращі варіанти серед новіших поколінь.

Висновок: що робити далі у реальному флоті

Справжня історія Bulldozer не в тому, що «AMD зазнала невдачі». Вона в тому, що сміливі архітектурні ставки мають операційні наслідки.
Спільні ресурси не відображаються в специфікації так само, як кількість ядер, але вони відображаються у вашій хроніці інцидентів.

Якщо ви досі експлуатуєте машини епохи Bulldozer, ставте їх як особливий клас обладнання:
стандартизуйте BIOS і політику частот, перевірте pinning і IRQ, і бенчмаркуйте ті навантаження, які ви дійсно запускаєте. Якщо плануєте ємність — припиніть
використовувати марковані «ядра» як одиницю обчислення. Використовуйте виміряну пропускну здатність і хвостову затримку під стійким навантаженням.

Практичні наступні кроки, у порядку:

  1. Запустіть швидкий план діагностики на одному «поганому» і одному «хорошому» хості поруч.
  2. Приберіть випадкове pinning, розподіліть гарячі потоки і виправте точки скупчення IRQ.
  3. Закріпіть консистентні налаштування живлення/частот відповідно до ваших SLO.
  4. Визначте, які навантаження належать на цю архітектуру — і перемістіть решту.

Bulldozer хотів світу, де все ідеально паралельне. Продакшн — не той світ. Продакшн — безладний, спільний, керований перериваннями і повний дрібних вузьких місць у маскарадних костюмах. Плануйте відповідно.

← Попередня
OpenCL: чому відкриті стандарти не завжди перемагають
Наступна →
PostgreSQL проти SQLite: повнотекстовий пошук — коли SQLite дивує (а коли ні)

Залишити коментар