GPU против CPU и современные процессоры
Финальный урок сравнивает две философии вычислителей — CPU и GPU — и обозревает устройство современных процессоров.
CPU оптимизирован под низкую задержку отдельной задачи (мало мощных ядер). GPU оптимизирован под высокую пропускную способность (тысячи простых ядер для массовых параллельных вычислений).
Две разные цели
CPU и GPU решают разные задачи. CPU должен быстро выполнить сложную последовательную программу с ветвлениями (операционная система, логика приложения) — ему нужны мощные ядра с большими кэшами, предсказанием переходов и внеочередным исполнением. GPU должен выполнить одну и ту же простую операцию над миллионами пикселей или чисел — ему нужны тысячи простых ядер, работающих в едином ритме.
Сравнение архитектур
| Свойство | CPU | GPU |
| Число ядер | единицы–десятки, мощные | тысячи, простые |
| Оптимизирован под | низкую задержку | высокую пропускную способность |
| Кэш на ядро | большой | маленький |
| Управление потоком | сложное (OoO, предсказание) | простое (SIMT) |
| Лучшее применение | последовательная логика, ОС | графика, ML, массовые данные |
CPU: несколько больших ядер GPU: тысячи крошечных ядер ┌────┐ ┌────┐ ░░░░░░░░░░░░░░░░░░ │ЯДРО│ │ЯДРО│ + большой кэш ░░░░░░░░░░░░░░░░░░ └────┘ └────┘ ░░░░░░░░░░░░░░░░░░ ┌────┐ ┌────┐ ░░░░░░░░░░░░░░░░░░ │ЯДРО│ │ЯДРО│ (каждое ░ - простое ядро, └────┘ └────┘ все делают одно и то же) быстро для 1 задачи быстро для 10000 задач сразу
Как работает под капотом: где GPU выигрывает, а где нет
GPU блистает на «embarrassingly parallel» задачах — независимых одинаковых вычислениях. Но на последовательной задаче с ветвлениями он медленнее CPU. Промоделируем «время» для двух типов нагрузки:
def cpu_time(tasks, per_task, cores=8):
# мощные ядра, но их мало
import math
return math.ceil(tasks / cores) * per_task
def gpu_time(tasks, per_task, cores=2048, slowdown=4):
# ядер много, но каждое медленнее и любит одинаковую работу
import math
return math.ceil(tasks / cores) * per_task * slowdown
for tasks in (8, 100000):
c = cpu_time(tasks, per_task=1)
g = gpu_time(tasks, per_task=1)
winner = "GPU" if g < c else "CPU"
print(f"{tasks:>6} независимых задач: CPU={c}, GPU={g} -> быстрее {winner}")Вывод:
8 независимых задач: CPU=1, GPU=4 -> быстрее CPU 100000 независимых задач: CPU=12500, GPU=196 -> быстрее GPU
На 8 задачах быстрее CPU (мощные ядра, нет накладных GPU), а на 100 000 параллельных задач GPU обгоняет в десятки раз. Отсюда вывод: их не противопоставляют, а сочетают.
Гетерогенные и современные процессоры
Современные системы гетерогенны: CPU дирижирует, а тяжёлый параллелизм отдаёт GPU или специализированным ускорителям (NPU для нейросетей). Современные чипы (например, мобильные SoC) объединяют на одном кристалле: «большие» и «малые» ядра (big.LITTLE — производительные и энергоэффективные), GPU, NPU, контроллеры памяти и ввода-вывода. Тренды: чиплеты (несколько кристаллов в одном корпусе), специализация под ИИ и борьба за энергоэффективность, а не только за частоту.
| Тренд | Суть |
| Гетерогенность | CPU + GPU + NPU + ускорители на одном чипе |
| big.LITTLE | мощные и экономичные ядра вместе |
| Чиплеты | чип из нескольких кристаллов — дешевле и гибче |
| Спец-ускорители | отдельные блоки под ИИ, видео, шифрование |
Глубже в тему
Различие CPU и GPU лучше всего схватывается через одну ось: оптимизация под задержку против оптимизации под пропускную способность. CPU спроектирован, чтобы как можно быстрее завершить одну сложную последовательную задачу — операционную систему, логику приложения, код с обилием ветвлений и зависимостей. Ради этого он вкладывает транзисторы в то, что ускоряет одиночный поток: большие кэши, внеочередное исполнение, мощные предсказатели переходов, глубокую спекуляцию. Таких «умных» ядер мало — единицы или десятки. GPU решает противоположную задачу: выполнить одну и ту же простую операцию над миллионами независимых элементов (пикселей, вершин, чисел матрицы). Ему не нужны хитрые предсказатели и огромные кэши на ядро — нужны тысячи простых ядер, работающих в едином ритме. Это два разных ответа на вопрос «куда потратить транзисторный бюджет», и ни один не «лучше» — они заточены под разные классы задач.
Ключ к пониманию GPU — модель SIMT (Single Instruction, Multiple Threads) и то, как GPU прячет задержку памяти иначе, чем CPU. Сотни потоков на GPU объединены в группы (warp в терминах NVIDIA), которые исполняют одну и ту же команду синхронно, каждый над своими данными. Когда warp натыкается на медленное обращение к памяти, GPU не пытается, как CPU, спекулятивно забегать вперёд внутри потока, — вместо этого он мгновенно переключается на другой готовый warp и продолжает работу, пряча задержку за счёт обилия параллелизма. CPU прячет задержку «вглубь» (внеочередное исполнение внутри одного потока), GPU — «вширь» (переключением между тысячами потоков). Отсюда и слабость GPU: ветвления. Если внутри warp разные потоки идут по разным веткам if, GPU вынужден исполнять обе ветки последовательно, отключая «лишние» потоки на каждой, — так называемая дивергенция ветвлений, резко роняющая эффективность. Вот почему ветвистая логика — родная стихия CPU, а не GPU.
Расчёт из урока чётко очерчивает границу применимости каждого вычислителя. На восьми независимых задачах быстрее CPU: его мощные ядра справляются мгновенно, а накладные расходы на запуск работы и пересылку данных на GPU не окупаются. Но на ста тысячах одинаковых независимых задач GPU обгоняет CPU в десятки раз: его тысячи ядер перемалывают такую «embarrassingly parallel» нагрузку играючи. Отсюда главный практический вывод: CPU и GPU не противопоставляют, а сочетают. CPU дирижирует — запускает программу, исполняет последовательную логику, управляет вводом-выводом, — а массивно-параллельные куски (рендеринг кадра, умножение больших матриц при обучении нейросети) выгружает на GPU. Стоит помнить и о скрытой цене: перенос данных между памятью CPU и памятью GPU через шину PCIe сам по себе небыстр, поэтому выгрузка на GPU окупается только если вычислений достаточно много, чтобы перекрыть стоимость пересылки.
Современные процессоры доводят эту идею «правильный вычислитель для каждой задачи» до предела — это эпоха гетерогенности, и она логично завершает весь курс. Мобильный SoC (system-on-chip) объединяет на одном кристалле производительные и энергоэффективные ядра (схема big.LITTLE — мощные для пиковой нагрузки, экономичные для фона), GPU для графики, NPU для нейросетевых вычислений, контроллеры памяти и ввода-вывода. Серверные и десктопные чипы переходят на чиплеты — несколько отдельных кристаллов в одном корпусе, что дешевле и гибче, чем один огромный монолит. Сквозной тренд индустрии сменился: если раньше гнались за частотой, то теперь — за энергоэффективностью (производительность на ватт) и за специализацией под конкретные нагрузки, в первую очередь под искусственный интеллект. Так замыкается дуга всего курса: мы прошли путь от одного простого процессора фон Неймана до системы из разнородных специализированных вычислителей, каждый из которых — ответ на свой компромисс между задержкой, пропускной способностью, энергией и универсальностью. Понимание этих компромиссов и есть суть компьютерной архитектуры.
Частые ошибки
- Считать GPU «быстрее CPU». Он быстрее только на массово-параллельных задачах; на последовательной логике CPU выигрывает.
- Думать, что больше ядер всегда лучше. Тысячи ядер GPU бесполезны, если задача не распараллеливается (см. закон Амдала).
- Игнорировать энергоэффективность. Современная гонка идёт за производительность на ватт, а не только за абсолютную скорость.
Итог
- CPU — мало мощных ядер под низкую задержку; GPU — тысячи простых ядер под пропускную способность.
- GPU выигрывает на массово-параллельных задачах, CPU — на последовательной логике.
- Современные системы гетерогенны: CPU + GPU + NPU, big.LITTLE, чиплеты, упор на энергоэффективность.