LINUX.ORG.RU

История изменений

Исправление Crocodoom, (текущая версия) :

У тебя на 3080 есть 8700 ядер и 8700 кб L1 кэша. То есть, по килобайту на ядро
...
Сравни это с типичным современным десктопным процессором, у которого 1.5 МБ когерентного кэша на ядро.

Схема архитектур следующая. Несколько упрощённо, как я её себе представляю, буду рад замечаниям.

Nvidia RTX 3080 [1]

P0: 16 float / cycle = half-warp
P1: 2 datapaths = warp
P2: 4 processing blocks = Streaming Multiprocessor (SM)
// P0-2: 128 Cuda Cores
P3: 68 SM = GPU

L1: 128 Кб на 1 SM
L2: 6 Мб общий для всех SM

Intel i9-9990XE [2]

P0: 16 float / cycle = SIMD
P1: 2 threads = HyperThreading
P2: 14 cores = CPU

L1: 64 Kb на 1 core
L2: 1 Mb на 1 core
L3: 1.375 Mb на 1 core // удивился, что даже к L3 у каждого ядра свой канал (?)

Теперь переходим собственно к сравнению. L1 на GPU и CPU примерно равны по размерам, я также предлагаю считать SM и core эквивалентыми сущностями. Тогда получается, что на CPU 14 <ядер>, а на GPU 68 <ядер>, и у каждого <ядра> есть небольшой, но очень быстрый, когерентный L1. Суммарно L1 у GPU существенно больше CPU, так надо обслуживать большое количество FPU. Но принципиальных отличий не вижу.

Что касается других кэшей, то Intel L2+L3 (сюда же Intel Smart Cache) действительно выглядит превосходящим GPU L2. Пригодился ли бы более развитый L2 на GPU? Конечно. Но пока работаем с тем, что есть.

Далее, те «ядра», коих byko3y насчитал 8700 — это Cuda Cores, их как отдельных сущностей на чипе не существует. Лучше говорить о 8704 FP / cycle, перемножив все уровни параллельности P#. Для CPU получается 448 FP / cycle. А чему там соответствует кэш какого уровня — надо аккуратно следить. Подробнее про уровни параллельности писал в [4].

По сравнению с L2/L3 кэшем. Любая оперативная память медленная. Здесь под
скоростью я имею в виду именно задержку доступа, а не ширину канала — так-то
ширина у GDDR6 составляет сотни гигабайт в секунду. Речь идет, грубо говоря,
про сотню циклов на доступ, и чем больше подходов чтения нужно поочередно
выполнить, тем меньше будет производительность твоего кода.

Волга впадает в Каспийское море. Лошади кушают овес и сено (с)

В простых случаях кэширование «просто работает». Да, производительность кода м.б. не идеальной... но вполне достаточной. Сильно зависит от задачи. Но конечно, если очень хочется, то можно и нужно писать cache-aware алгоритмы [next lvl: cache-oblivious]

неэффективность косвенного доступа и виртуальных методов.

Здесь +

Основной примитив синхронизации на ЦП — это атомарный compare-and-swap, на
котором реализовываются как мутексы, так и lock-free алгоритмы. Всё это не
работает на видеокарте, потому что на ней нет когерентного кэша, который бы
являлся арбитром при выполнении compare-and-swap. Вместо этого основной
механизм синхронизации на GPGPU — барьеры плюс ручное согласование кэша.

В CUDA тоже есть atomicCAS(), бумс. Но я делал синхронизацию даже без атомиков, в порядке эксперимента [5]. Там volatile главное проставить на мьютексе и на данных.

Также есть официальное API для кооперативной работы SM-ов (и не только их) [3]


[1] https://images.nvidia.com/aem-dam/en-zz/Solutions/geforce/ampere/pdf/NVIDIA-a...

[2] https://en.wikichip.org/wiki/intel/core_i9/i9-9990xe

[3] https://developer.nvidia.com/blog/cooperative-groups/

[4] https://link.springer.com/chapter/10.1007/978-3-030-92864-3_29

[5] https://link.springer.com/chapter/10.1007/978-3-030-36592-9_5

Исходная версия Crocodoom, :

У тебя на 3080 есть 8700 ядер и 8700 кб L1 кэша. То есть, по килобайту на ядро
...
Сравни это с типичным современным десктопным процессором, у которого 1.5 МБ когерентного кэша на ядро.

Схема архитектур следующая. Несколько упрощённо, как я её себе представляю, буду рад замечаниям.

Nvidia RTX 3080 [1]

P0: 16 float / cycle = half-warp
P1: 2 datapaths = warp
P2: 4 processing blocks = Streaming Multiprocessor (SM)
// P0-2: 128 Cuda Cores
P3: 68 SM = GPU

L1: 128 Кб на 1 SM
L2: 6 Мб общий для всех SM

Intel i9-9990XE [2]

P0: 16 float / cycle (SIMD)
P1: 2 threads
P2: 14 cores

L1: 64 Kb на 1 core
L2: 1 Mb на 1 core
L3: 1.375 Mb на 1 core // удивился, что даже к L3 у каждого ядра свой канал (?)

Теперь переходим собственно к сравнению. L1 на GPU и CPU примерно равны по размерам, я также предлагаю считать SM и core эквивалентыми сущностями. Тогда получается, что на CPU 14 <ядер>, а на GPU 68 <ядер>, и у каждого <ядра> есть небольшой, но очень быстрый, когерентный L1. Суммарно L1 у GPU существенно больше CPU, так надо обслуживать большое количество FPU. Но принципиальных отличий не вижу.

Что касается других кэшей, то Intel L2+L3 (сюда же Intel Smart Cache) действительно выглядит превосходящим GPU L2. Пригодился ли бы более развитый L2 на GPU? Конечно. Но пока работаем с тем, что есть.

Далее, те «ядра», коих byko3y насчитал 8700 — это Cuda Cores, их как отдельных сущностей на чипе не существует. Лучше говорить о 8704 FP / cycle, перемножив все уровни параллельности P#. Для CPU получается 448 FP / cycle. А чему там соответствует кэш какого уровня — надо аккуратно следить. Подробнее про уровни параллельности писал в [4].

По сравнению с L2/L3 кэшем. Любая оперативная память медленная. Здесь под
скоростью я имею в виду именно задержку доступа, а не ширину канала — так-то
ширина у GDDR6 составляет сотни гигабайт в секунду. Речь идет, грубо говоря,
про сотню циклов на доступ, и чем больше подходов чтения нужно поочередно
выполнить, тем меньше будет производительность твоего кода.

Волга впадает в Каспийское море. Лошади кушают овес и сено (с)

В простых случаях кэширование «просто работает». Да, производительность кода м.б. не идеальной... но вполне достаточной. Сильно зависит от задачи. Но конечно, если очень хочется, то можно и нужно писать cache-aware алгоритмы [next lvl: cache-oblivious]

неэффективность косвенного доступа и виртуальных методов.

Здесь +

Основной примитив синхронизации на ЦП — это атомарный compare-and-swap, на
котором реализовываются как мутексы, так и lock-free алгоритмы. Всё это не
работает на видеокарте, потому что на ней нет когерентного кэша, который бы
являлся арбитром при выполнении compare-and-swap. Вместо этого основной
механизм синхронизации на GPGPU — барьеры плюс ручное согласование кэша.

В CUDA тоже есть atomicCAS(), бумс. Но я делал синхронизацию даже без атомиков, в порядке эксперимента [5]. Там volatile главное проставить на мьютексе и на данных.

Также есть официальное API для кооперативной работы SM-ов (и не только их) [3]


[1] https://images.nvidia.com/aem-dam/en-zz/Solutions/geforce/ampere/pdf/NVIDIA-a...

[2] https://en.wikichip.org/wiki/intel/core_i9/i9-9990xe

[3] https://developer.nvidia.com/blog/cooperative-groups/

[4] https://link.springer.com/chapter/10.1007/978-3-030-92864-3_29

[5] https://link.springer.com/chapter/10.1007/978-3-030-36592-9_5