Плохая производительность при одновременном вызове cudaMalloc с двумя графическими процессорами

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

Рассмотрим пример изображения (сгенерированного с использованием инструмента профилирования NVIDIA CUDA) для примера интервала обработки графического процессора - здесь приложение использует один графический процессор.

enter image description here

Как вы видите, большая часть времени обработки графического процессора потребляется двумя операциями сортировки, и для этого я использую библиотеку Thrust (thrust:: sort_by_key). Кроме того, похоже, что thrust:: sort_by_key вызывает несколько cudaMallocs под капотом, прежде чем он начнет фактический сортировку.

Теперь рассмотрим тот же интервал обработки, когда приложение распространило нагрузку на обработку на двух графических процессорах:

enter image description here

В идеальном мире вы ожидаете, что интервал обработки 2 GPU будет в полтора раза меньше, чем у одного GPU (поскольку каждый GPU выполняет половину работы). Как вы можете видеть, это не так, частично потому, что cudaMallocs, кажется, занимает больше времени, когда их называют одновременно (иногда в 2-3 раза больше) из-за какой-то вопрос раздора. Я не понимаю, почему это должно быть так, потому что пространство для выделения памяти для 2 графических процессоров полностью независимо, поэтому не должно быть общесистемной блокировки cudaMalloc - блокировка за один GPU была бы более разумной.

Чтобы доказать свою гипотезу, что проблема связана с одновременными вызовами cudaMalloc, я создал смехотворно простую программу с двумя потоками ЦП (для каждого графического процессора), каждый раз вызывающий cudaMalloc несколько раз. Сначала я запускал эту программу, чтобы отдельные потоки не вызывали одновременно cudaMalloc:

enter image description here

Понимаете, для распределения требуется ~ 175 микросекунд. Затем я запускал программу с потоками, вызывающими cudaMalloc одновременно:

enter image description here

Здесь каждый вызов занимал ~ 538 микросекунд или в 3 раза дольше, чем в предыдущем случае! Излишне говорить, что это значительно замедляет мое приложение, и разумно, что проблема будет только ухудшаться с более чем 2 графическими процессорами.

Я заметил это поведение в Linux и Windows. В Linux я использую драйвер Nvidia версии 319.60, а в Windows я использую версию 327.23. Я использую CUDA toolkit 5.5.

Возможная причина: Я использую GTX 690 в этих тестах. Эта карта в основном состоит из 6 680-подобных графических процессоров, размещенных в одном устройстве. Это единственная настройка "multi-GPU", которую я запускал, поэтому, возможно, проблема cudaMalloc связана с некоторой аппаратной зависимостью между 690 2 GPU?

6
05 окт. '13 в 3:00
источник поделиться
2 ответов

Подводя итог проблеме и дадим возможное решение:

Конкуренция cudaMalloc, вероятно, связана с конкуренцией на уровне драйверов (возможно, из-за необходимости переключать контексты устройств, как предполагали подсказки), и можно было бы избежать этой дополнительной задержки в критичных для производительности разделах cudaMalloc-ing и временных буферах заранее.

Похоже, мне, вероятно, нужно реорганизовать мой код, чтобы я не вызывал никакой процедуры сортировки, которая вызывает cudaMalloc под капотом (в моем случае thrust:: sort_by_key). библиотека CUB выглядит перспективным в этом отношении. В качестве бонуса CUB также предоставляет пользователю параметр потока CUDA, что также может повысить производительность.

См. CUB (CUDA UnBound) эквивалент thrust:: gather для некоторых деталей перехода от тяги к CUB.

UPDATE:

Я отменил вызовы toust:: sort_by_key в пользу cub:: DeviceRadixSort:: SortPairs.
Выполнение этих бритых миллисекунд с моего времени обработки в течение интервала. Кроме того, проблема разногласий с несколькими GPU разрешилась сама собой - разгрузка до 2 графических процессоров почти снижает время обработки на 50%, как и ожидалось.

4
06 окт. '13 в 19:00
источник

Я предопределю это с отказом от ответственности: я не привязан к внутренним компонентам драйвера NVIDIA, поэтому это несколько умозрительно.

Замедление, которое вы видите, - это просто конкуренция на уровне драйверов, вызванная конкуренцией со стороны нескольких потоков, вызывающих устройство malloc одновременно. Для распределения памяти устройства требуется ряд системных вызовов ОС, равно как и переключение контекста на уровне драйвера. В обеих операциях существует нетривиальная сумма задержек. Вероятно, дополнительное время, которое вы видите, когда два потока пытаются и выделяют память одновременно, вызвано дополнительной задержкой драйвера от переключения с одного устройства на другое в течение всей последовательности системных вызовов, необходимых для распределения памяти на обоих устройствах.

Я могу придумать несколько способов, с помощью которых вы можете уменьшить это:

  • Вы можете уменьшить накладные расходы системного вызова на распределение памяти опора к нулю, написав собственный собственный распределитель памяти остова для устройство, которое работает с плиты памяти, выделенной во время инициализация. Это избавит вас от всех издержек системного вызова в пределах каждого sort_by_key, но усилие написания собственного пользователя менеджер памяти не является тривиальным. С другой стороны, это оставляет вашего кода тяги.
  • Вы можете переключиться на альтернативную библиотеку сортировки и вернуть управлять распределением временной памяти самостоятельно. Если вы сделаете все распределение на этапе инициализации, стоимость одного времени распределения памяти могут быть амортизированы почти до нуля в течение срока службы каждый поток.

В кодах линейной алгебры с несколькими GPU на основе CUBLAS, которые я написал, я объединил обе идеи и написал автономный диспетчер памяти устройства пользователя, который работает с одноразовым распределенным пулом памяти устройства. Я обнаружил, что удаление всех накладных расходов на распределение памяти промежуточных устройств дало полезную скорость. Ваш вариант использования может выиграть от аналогичной стратегии.

6
05 окт. '13 в 13:22
источник

Посмотрите другие вопросы по меткам или Задайте вопрос