Два последовательных ядра или синхронизация по группам из всей сетки?
Предположим, у меня есть две задачи для запуска на графическом процессоре, вторая из которых основывается на практически всех работах первой. Традиционно мне, по сути, приходилось писать эти задачи как два отдельных ядра и планировать запуск второго в какой-то момент после первого. Но - с CUDA 9 теперь я могу синхронизировать всю сетку, завершая свою работу над первой задачей - используя функцию кооперативных групп, а затем приступить к работе сетки со своей второй задачей.
Мои вопросы:
- Можем ли мы предоставить практическое правило относительно того, когда лучше, с точки зрения производительности, писать два ядра и когда использовать синхронизацию всей сетки?
- Если так, что бы это было?
- Если нет - почему трудно определить, какой вариант предпочтительнее в каких случаях?
1 ответ
Делая это CW ответом, чтобы другие не стеснялись добавлять свои мнения и редактировать.
Функция синхронизации всей сетки в кооперативных группах влечет за собой требование ограничить дополнение потока (размер сетки) до любой пропускной способности графического процессора, на котором вы работаете. Это не является серьезным ограничителем производительности, но требует от вас написания кода, который может гибко использовать различные размеры сетки, в то же время достигая максимальной производительности. петли шага сетки являются типичным компонентом такой стратегии кодирования.
Поэтому функция синхронизации всей сетки часто требует тщательного кодирования и дополнительных накладных расходов кода (например, использование API занятости) для достижения максимальной производительности, особенно по сравнению с простыми или простыми ядрами.
Чтобы компенсировать это возможное снижение производительности программиста, возможны следующие преимущества:
В ситуации, когда издержки запуска составляют значительную часть общего времени выполнения, совместная синхронизация по всей сетке может дать существенную выгоду. В дополнение к объединению двух отдельных ядер, алгоритмы, которые могут вызывать ядра в цикле, например, итерация / релаксация jacobi, или другие алгоритмы моделирования временного шага, могут заметно выиграть, поскольку цикл запуска может эффективно "перемещаться в GPU", замена цикла запуска ядра одним вызовом ядра.
В ситуации, когда существует значительное количество "состояния" на кристалле (например, содержимого регистра, содержимого совместно используемой памяти), которое необходимо загрузить до синхронизации всей сетки и будет использовано после синхронизации всей сетки, тогда кооперативные группы могут быть значительным выигрышем, экономя время в ядре, которое следовало бы за этой синхронизацией всей сетки, которая использовалась бы для повторной загрузки состояния. Это, кажется, было мотивацией здесь (см. Раздел 4.3), например. Я не предполагаю, что они использовали кооперативные группы (они не были). Я полагаю, что они были заинтересованы в поиске синхронизации по всей сетке, используя временные методы, доступные в то время, чтобы исключить как стоимость перезагрузки состояния, так и, возможно, затраты на запуск ядра.