33. Архитектура современных CPU. Chiplet, CCX, CCD, Ring Bus, Mesh

Конец эпохи монолитов

В добрые старые времена закон Мура казался непоколебимым законом вселенной: каждые полтора-два года плотность транзисторов на кремниевой пластине удваивалась, и инженеры просто упаковывали всё больше вычислительных блоков на один кремниевый кристалл. Такой традиционный процессор в микроэлектронике называют монолитным (Monolithic Die) — в нем ядра, все уровни кэш-памяти, контроллеры оперативной памяти и шины ввода-вывода вытравлены на едином монолитном куске сверхчистого кремния.

Однако законы квантовой физики и экономики расставили безжалостные ловушки:

  1. Экономика брака и выход годных кристаллов (Yields): Кремниевые пластины (Wafers) диаметром 300 мм никогда не бывают абсолютно идеальными — на них неизбежно оседают микроскопические дефекты решетки и пылинки. Если кристалл монолитного процессора имеет огромную площадь (например, 700–800 мм² для серверного гиганта на 56 или 64 ядра), вероятность попадания пылинки в критический узел стремится к 100%. Выход годных кристаллов (Yields) резко падает (конкретные цифры зависят от техпроцесса и зрелости производства), а стоимость каждого уцелевшего процессора улетает в стратосферу.
  2. Проблема теплоотвода и «темного кремния» (Dark Silicon): Рассеивать 300–400 Вт тепла из крошечной монолитной точки кристалла становится физически невозможно без экзотического жидкостного охлаждения.

Инженерный прорыв, перевернувший мировую полупроводниковую индустрию (и триумфально популяризированный компанией AMD в микроархитектуре AMD Zen), получил название Чиплеты (Chiplets).

Вместо того чтобы печатать огромный рискованный суперчип, инженеры разделили процессор на несколько маленьких, компактных кристаллов и начали собирать процессор на подложке из текстолита как конструктор Lego. Дешевые в производстве маленькие кристаллы обладают выходом годных более 90%, а дефектный чиплет можно просто выбросить, не теряя весь процессор.

Для нас, бэкенд-разработчиков и системных инженеров Go, эта революция принесла фундаментальный тектонический сдвиг: внутри одного физического сокета ядра CPU больше не равноправны. Одинаковые с виду ядра находятся на разном расстоянии друг от друга, а скорость обмена данными между горутинами зависит от кремниевой топологии.


Архитектура AMD: CCX, CCD и Infinity Fabric

Чтобы осознанно писать и профилировать высоконагруженные Go-сервисы на современных серверах (таких как AMD EPYC Rome, Milan, Genoa или десктопных Ryzen), необходимо понимать их анатомическое устройство.

Архитектура современного чиплетного процессора строится из четких строительных блоков:

  1. CCX (Core Complex): Базовый кремниевый кластер. Это группа ядер (в ранних версиях 4, в современных — ровно 8), которые делят между собой общий физический блок кэша третьего уровня (L3 Cache). Если две ваши горутины исполняются на ядрах внутри одного и того же CCX, они синхронизируют данные через общий L3-кэш на фантастической скорости (~10–12 наносекунд).
  2. CCD (Core Chiplet Die): Физический миниатюрный кристалл кремния (чиплет), изготовленный по самому передовому техпроцессу (например, 5 нм или 4 нм). В современных чипах (Zen 3, Zen 4, Zen 5) один CCD содержит один монолитный CCX на 8 ядер и 32 МБ (или 96 МБ с 3D V-Cache) общего L3-кэша.
  3. cIOD (I/O Die): Большой вспомогательный кристалл, расположенный в геометрическом центре процессора. Он выполнен по более старому, дешевому и проверенному техпроцессу (например, 12 нм или 6 нм). На cIOD нет вычислительных ядер: здесь сосредоточены контроллеры оперативной памяти DDR4/DDR5, контроллеры шины PCIe/CXL и центральный коммуникационный хаб.
  4. Infinity Fabric (IF): Внутрипроцессорная высокоскоростная сеть с пакетной передачей данных, соединяющая каждый чиплет CCD с центральным кристаллом cIOD.
flowchart TD
    classDef process  fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
    classDef success  fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
    classDef error    fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
    classDef warning  fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
    classDef entry    fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
    classDef aux      fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
    classDef system   fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
    classDef data     fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
    classDef network  fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
    classDef accent   fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
    subgraph CPU ["Процессор - Единый сокет (Single Socket EPYC / Ryzen)"]
        subgraph CCD1 ["CCD 1 (Вычислительный чиплет 5 нм)"]
            subgraph CCX1 ["CCX 1 (Core Complex)"]
                C1["Ядра 0-7 (Cores 0-7)"]
                L3_1["Общий L3 Cache 32MB"]:::data
                C1 <--> L3_1
            end
        end

        subgraph CCD2 ["CCD 2 (Вычислительный чиплет 5 нм)"]
            subgraph CCX2 ["CCX 2 (Core Complex)"]
                C2["Ядра 8-15 (Cores 8-15)"]
                L3_2["Общий L3 Cache 32MB"]:::data
                C2 <--> L3_2
            end
        end

        IOD["cIOD (Central I/O Die)<br>Контроллеры памяти DDR5 и контроллеры PCIe"]

        CCD1 <-->|"Шина Infinity Fabric (IF)"| IOD
        CCD2 <-->|"Шина Infinity Fabric (IF)"| IOD
        IOD <-->|"Каналы памяти (Memory Channels)"| RAM["Оперативная память (DRAM)"]
    end
  

Архитектура Intel: Ring Bus и Mesh

Компания Intel исторически сохраняла верность монолитному дизайну намного дольше своего конкурента (вплоть до появления архитектур Sapphire Rapids и Meteor Lake). Однако даже на одном огромном монолитном кристалле кремния инженеры столкнулись с той же проблемой: как связать между собой десятки ядер, сегменты L3-кэша и контроллеры памяти?

Ring Bus (Кольцевая шина)

В потребительских и десктопных процессорах Intel (линейки Core i5, i7, i9) применяется топология Ring Bus. Все ядра процессора, распределенные блоки общего L3-кэша, встроенная графика и системный агент нанизаны на двунаправленное скоростное кольцо из проводников.

  • Плюсы: Сверхнизкая задержка (Latency < 5–7 наносекунд) и гигантская пропускная способность при небольшом количестве ядер (до 8–10 ядер).
  • Минусы: Катастрофически плохая масштабируемость. Если попытаться замкнуть в кольцо 28 или 32 ядра, пакету данных придется совершать десятки тактов в пути от ядра №0 до ядра №16 на противоположной стороне кольца.
TEXT
       +----> [ Core 0 ] ----> [ Core 1 ] ----> [ Core 2 ] ---->+
       |         |                 |                 |          |
       |         v                 v                 v          |
       |     [ L3 Block ]      [ L3 Block ]      [ L3 Block ]   |  Ring Bus
       |         ^                 ^                 ^          |
       |         |                 |                 |          |
       +<---- [ Core 5 ] <---- [ Core 4 ] <---- [ Core 3 ] <----+

Mesh Architecture (Сетка)

Когда компании Intel потребовалось создавать серверные процессоры Xeon Scalable на 28, 40 и более ядер на кристалл, инженеры демонтировали кольцевую шину и внедрили топологию Mesh (Двумерная сетка).

Ядра, кэши и контроллеры шины расположены в виде матричной сетки (наподобие улиц и авеню на Манхэттене). На каждом пересечении установлен аппаратный маршрутизатор (Switch/Router). Пакеты с кэш-линиями перемещаются по координатной сетке алгоритмами X-Y routing (сначала по горизонтали, затем по вертикали).

  • Плюсы: Высокая совокупная пропускная способность шины, простота добавления новых ядер и предсказуемая задержка даже на 64+ ядрах.
  • Минусы: Базовая задержка между двумя физически соседними ядрами выше, чем на коротком Ring Bus (около 15–20 нс).

Mechanical Sympathy: Micro-NUMA внутри одного сокета

В лекции 31. NUMA. Non Uniform Memory Access мы подробно исследовали задержки доступа в многосокетных серверах, где путь к памяти соседа проходит через межсокетные шины (UPI / Infinity Fabric).

Но внедрение чиплетного дизайна привело к поразительному явлению: сегодня даже один физический процессор в одном сокете является скрытой NUMA-системой (Micro-NUMA)!

Рассмотрим хрестоматийный паттерн любого Go-бэкенда — пул рабочих горутин (worker pool). У нас есть буферизованный канал, в который одна горутина-продюсер отправляет входящие сетевые запросы, а 10 горутин-воркеров непрерывно вычитывают данные. Вспомним, что канал под капотом рантайма Go — это обычная структура памяти с внутренним мьютексом рантайма (runtime.mutex, а не sync.Mutex из стандартной библиотеки) и циклическим буфером (см. 21. False Sharing и Cache Line Contention).

Что происходит в кремнии чиплетного процессора AMD EPYC?

TEXT
СЦЕНАРИЙ А: Все горутины на CCD 1 (внутри одного CCX)
[Горутина 1] <--- Общий L3-кэш (32 МБ) ---> [Горутина 2]
               Задержка синхронизации: порядка 20 нс!

СЦЕНАРИЙ Б: Горутина 1 на CCD 1, а Горутина 2 на CCD 2
[Горутина 1 (CCD 1)] 
         |
         v (Infinity Fabric)
     [ cIOD (Центральный контроллер) ] 
         |
         v (Infinity Fabric)
[Горутина 2 (CCD 2)]
               Задержка синхронизации: порядка 70–100+ нс!
  1. Идеальный сценарий (Intra-CCX): Планировщик операционной системы распределил горутины продюсера и воркеров по ядрам внутри одного CCX (например, ядра с 0 по 7). Передача владения мьютексом канала происходит через разделяемый L3-кэш за десятки тактов. Задержка межъядерной передачи составляет скромные порядка 20 наносекунд (по данным микробенчмарков, например Chips and Cheese; зависит от модели).
  2. Кошмарный сценарий (Inter-CCD): Планировщик Linux раскидал горутины по разным кристаллам: продюсер работает на CCD 1, а воркер — на CCD 2.
    В кэше L3 второго чиплета этих данных нет. Физическое ядро на CCD 2 вынуждено послать пакет запроса через шину Infinity Fabric в кристалл cIOD, оттуда запрос перенаправляется по шине в чиплет CCD 1, инвалидирует строку кэша продюсера по протоколу когерентности, упаковывает 64 байта в сетевой пакет и транспортирует обратно в CCD 2. Задержка подскакивает до 70–100+ наносекунд (по тем же микробенчмаркам)!

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

Как с этим жить бэкендеру на Go?

Планировщик Go (runtime.scheduler) спроектирован очень тщательно. Он использует эвристику локальности кэша и старается удерживать исполняемую горутину (G) на одном и том же логическом процессоре (P) и системном потоке ОС (M), чтобы сохранить прогретыми кэши данных L1/L2 (см. 18. Кэши CPU. L1, L2, L3 и Cache Line).

Однако планировщик Go абсолютно слеп к кремниевой топологии CCX, CCD и шин Infinity Fabric! Он видит плоский список процессоров от 0 до N-1, предоставляемый ядром операционной системы.

В высоконагруженных системах (таких как распределенные базы данных, брокеры сообщений или HFT-шлюзы) системные инженеры берут управление в свои руки:

  1. CPU Pinning (Изоляция ядер через маски сродства): С помощью системных утилит taskset или настроек cgroups контейнера процесс приложения жестко привязывается к диапазону ядер одного чиплета CCX (например, taskset -c 0-7 ./my_go_service). Если приложению требуется 32 ядра, запускают 4 изолированных экземпляра микросервиса, каждый из которых заперт внутри своего 8-ядерного CCD.
  2. Share-Nothing Архитектура (Разделяй и властвуй): Проектирование софта ведется по принципу полного отказа от глобально разделяемой памяти. Воркеры получают изолированные фрагменты данных (партиции/шарды), не используют общие мьютексы и не пишут в централизованные каналы. Каждая горутина работает только со своим локальным состоянием, а объединение результатов происходит пакетно на финальной стадии.

Итоги раздела

Появление чиплетного компонования (CCX, CCD), скоростных кольцевых шин (Ring Bus) и координатных сеток (Mesh) окончательно похоронило наивную иллюзию о том, что «все ядра внутри процессора равноудалены друг от друга».

Современный процессор — это не просто вычислитель, это высокоскоростная локальная вычислительная сеть на кремнии (Network-on-Chip, NoC). Стоимость пересылки даже одного байта между горутинами, оказавшимися на разных чиплетах, в 4–6 раз превышает обмен внутри одного кристалла. Для создания по-настоящему быстрого кода на Go мы обязаны минимизировать разделяемое состояние (shared state) и сочувствовать топологии кремния.

На этом мы триумфально завершаем изучение внутреннего устройства процессора, памяти и кремниевой адресации. Мы изучили транзисторы, конвейеры, суперскалярность, кэши, виртуальные страницы, TLB и чиплеты. Наш процессор разогрет, кэш-линии заполнены, а инструкции готовы к исполнению.

Но как процессор понимает, когда нужно приостановить выполнение пользовательского кода на Go и передать бразды правления ядру операционной системы? Как операционная система Linux перехватывает управление при нажатии клавиши, поступлении сетевого пакета или системной ошибке?

Переходим к четвертому блоку архитектуры компьютера — системному вводу-выводу: 34. Аппаратные прерывания и Системные вызовы.