Разделение CUDA cudaMemcpy на куски - PullRequest
1 голос
/ 25 июля 2011

Мы с коллегой провели мозговой штурм о том, как уменьшить время передачи памяти между хостом и устройством, и выяснилось, что, возможно, может помочь организация вещей для одного мега-переноса (то есть одного единственного вызова). Это привело меня к созданию контрольного примера, в котором я использовал время для передачи нескольких больших порций данных против множества маленьких порций данных. Я получил очень интересные / странные результаты, и мне было интересно, если у кого-нибудь есть объяснение?

Я не буду помещать здесь весь свой код, поскольку он довольно длинный, но я проверил порцию двумя разными способами:

  1. Явно выписывает все cudaMemcpy's, например ::

    cudaEventRecord (начало, 0);
    cudaMemcpy (aD, a, nBytes / 10, cudaMemcpyHostToDevice);
    cudaMemcpy (aD + 1 * nBytes / 10, a + 1 * nBytes / 10, nBytes / 10, cudaMemcpyHostToDevice);
    cudaMemcpy (aD + 2 * nBytes / 10, a + 2 * nBytes / 10, nBytes / 10, cudaMemcpyHostToDevice);
    cudaMemcpy (aD + 3 * nBytes / 10, a + 3 * nBytes / 10, nBytes / 10, cudaMemcpyHostToDevice);
    cudaMemcpy (aD + 4 * nBytes / 10, a + 4 * nBytes / 10, nBytes / 10, cudaMemcpyHostToDevice);
    cudaMemcpy (aD + 5 * nBytes / 10, a + 5 * nBytes / 10, nBytes / 10, cudaMemcpyHostToDevice);
    cudaMemcpy (aD + 6 * nBytes / 10, a + 6 * nBytes / 10, nBytes / 10, cudaMemcpyHostToDevice);
    cudaMemcpy (aD + 7 * nBytes / 10, a + 7 * nBytes / 10, nBytes / 10, cudaMemcpyHostToDevice);
    cudaMemcpy (aD + 8 * nBytes / 10, a + 8 * nBytes / 10, nBytes / 10, cudaMemcpyHostToDevice);
    cudaMemcpy (aD + 9 * nBytes / 10, a + 9 * nBytes / 10, nBytes / 10, cudaMemcpyHostToDevice);
    cudaEventRecord (стоп, 0);
    cudaEventSynchronize (остановка); * * тысяча двадцать-одна cudaEventElapsedTime (& время, начало, остановка);

  2. Помещение cudaMemcpy в цикл for:

    cudaEventRecord (начало, 0);
    for (int i = 0; i {
    cudaMemcpy (aD + i * nBytes / nChunks, a + i * nBytes / nChunks, nBytes / nChunks, cudaMemcpyHostToDevice);
    }
    cudaEventRecord (стоп, 0);
    cudaEventSynchronize (остановка);
    cudaEventElapsedTime (& время, начало, остановка);

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

Я проверил это на всех объемах передачи в диапазоне от 1 МБ до 1 ГБ, где в каждом тестовом примере передавалось одинаковое количество информации независимо от того, как она была распределена. Пример моего вывода такой:

один большой перевод = 0,451616 мс
10 явных передач = 0,198016 мс
100 явных передач = 0,691712 мс
10 зацикленных передач = 0,174848 мс
100 цикловых передач = 0,683744 мс
1000 циклических передач = 6,145792 мс
10000 зацикленных передач = 104,981247 мс
100000 циклических передач = 13097,441406 мс

Что интересно здесь, и чего я не понимаю, так это то, что 10 переводов были ВСЕГДА быстрее на значительную сумму, чем любой другой, даже один большой перевод! И этот результат оставался неизменным независимо от того, насколько большим или маленьким был набор данных (т. Е. 10x100 МБ против 1x1 ГБ или 10x1 МБ против 1x10 МБ по-прежнему приводят к увеличению скорости в 10 раз). Если у кого-то есть понимание того, почему это так или что я могу делать неправильно, чтобы получить эти странные цифры, мне было бы очень интересно услышать, что вы хотите сказать.

Спасибо!

P.S. Я знаю, что cudaMemcpy несет с собой неявную синхронизацию, и поэтому я мог бы использовать таймер ЦП, а cudaEventSynchronize является избыточным, но я решил, что лучше быть на безопасной стороне

ОБНОВЛЕНИЕ: Я написал функцию, чтобы попытаться воспользоваться этим очевидным разрывом в пространственно-временном континууме производительности. Когда я использую эту функцию, которая написана EXACLTY, как в моих тестовых случаях, эффект исчезает, и я вижу то, что ожидаю (один cudaMemcpy самый быстрый). Возможно, это все больше похоже на квантовую физику, чем на теорию относительности, в которой акт наблюдения меняет поведение ...

Ответы [ 3 ]

4 голосов
/ 26 июля 2011

cudaMemcpy () является синхронным - CUDA ждет, пока memcpy не будет выполнен, прежде чем вернуться в ваше приложение.

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

Очень важно, чтобы вы вызывали cudaMemcpyAsync () вместо cudaMemcpy (). Не потому, что вы хотите перекрывать передачу с обработкой на GPU, а потому, что это единственный способ получить параллелизм CPU / GPU.

В экземпляре cg1.4xlarge в Amazon EC2 драйверу требуется ~ 4 микросекунды для запроса графического процессора графического процессора; так что параллелизм CPU / GPU - хороший способ скрыть издержки драйвера.

У меня нет готового объяснения несоответствия, которое вы видите в 10 - главное колено, которое я ожидаю увидеть, это то, где memcpy пересекает размер более 64K. Драйвер вставляет значение memcpy меньше 64 КБ в тот же буфер, который используется для отправки команд.

1 голос
/ 09 ноября 2012

Используйте cudaThreadSynchronize () до и после каждого вызова cuda для получения реального времени передачи памяти, cudaMemcpy () является синхронным, но не с выполнением ЦП, это зависит от вызываемой функции.

Вызовы функции Cudaсинхронны с другими вызовами функций cuda, такими как другие передачи памяти или выполнение ядра, это осуществляется в другом потоке CUDA, невидимом для разработчика CUDA.cudaMemcpyAsync () асинхронен с другими вызовами CUDA, поэтому необходимо, чтобы скопированные сегменты памяти графического процессора не перекрывались с другими одновременными передачами памяти.

Вы уверены, что в этом случае cudaMemcpy () является синхроннымв потоке выполнения CUDA синхронизируется также с потоком ЦП?В зависимости от функции cuda, это может быть или нет, но если вы используете функцию cudaThreadSynchronize при измерении времени, она точно будет синхронизирована с процессором, и появится реальное время каждого шага.

0 голосов
/ 25 июля 2011

Возможно, это какая-то особенность в том, как CUDA измеряет время. Вы измеряете время менее 1 мс, что очень мало. Вы пытались синхронизировать его с таймером на базе процессора и сравнивать результаты?

...