Перенаправление printf CUDA в поток C++

Постановка задачи

Я работаю над большим проектом, который использует логгер для отладки. Поскольку мне нравится следить за тем, что происходит в некоторых ядрах CUDA, я попытался найти способ перенаправить printf моих ядер CUDA в stringstream (или любой поток), который затем может быть перенаправлен в регистратор.

Возможное решение

Мне удалось сделать это с помощью следующего кода:

#include <cuda.h>
#include <stdio.h>
#include <unistd.h> // dup

#include <iostream>
#include <sstream> // stringstream
#include <fstream> // ofstream

char* output_file = "printf_redirect.log";

__global__ void printf_redirect(int* src, int* res)
{
    res[threadIdx.x] = threadIdx.x;
    printf("  %i: Hello World!\n", res[threadIdx.x]);
}

int main()
{
    using namespace std;

    const uint N = 2;

    // Note: dummy arrays are not actually used, but this should prevent the
    //       compiler from discarding the printf in the kernel.

    int *d_A, *d_B, *h_A, *h_B;
    size_t size = N * sizeof (int);
    cudaMalloc (&d_A, size);
    cudaMalloc (&d_B, size);
    h_A = (int*) malloc (size);
    h_B = (int*) malloc (size);
    cudaMemcpy (d_A, h_A, size, cudaMemcpyHostToDevice);

    std::cout << "std::cout - start" << std::endl;
    printf ("stdout - start\n");

    /// REGULAR PRINT
    // Print to regular stdout
    std::cout << "Output to stdout:" << std::endl;
    printf_redirect<<<1,1>>> (d_A, d_B);
    cudaDeviceSynchronize ();

    /// REDIRECTION TO STRINGSTREAM
    std::stringstream ss;
    // Redirect std::cout to a stringstream
    std::streambuf* backup_cout = std::cout.rdbuf ();
    std::cout.rdbuf (ss.rdbuf ());
    // Redirect stdout to a buffer
    char buf[1024] = "";
    int backup_stdout = dup (fileno (stdout));
    freopen ("/dev/null", "w", stdout);
    setbuf (stdout, buf);

    std::cout << "Redirected output:" << std::endl;
    printf_redirect<<<1,N>>> (d_A, d_B);
    cudaDeviceSynchronize ();

    // Add CUDA buffer to a stringstream
    ss << buf;

    // Write stringstream to file
    std::ofstream outFile;
    outFile.open (output_file);
    outFile << ss.str ();
    outFile.close ();

    /// RESET REDIRECTION
    // Redirect back to initial stdout
    fflush (stdout);
    setbuf (stdout, NULL);
    fclose (stdout);
    FILE *fp = fdopen (backup_stdout, "w");
    fclose (stdout);
    *stdout = *fp;
    // Redirect back to initial std::cout
    std::cout.rdbuf (backup_cout);

    std::cout << "std::cout - end" << std::endl;
    printf ("stdout - end\n");

    cudaMemcpy(h_B, d_B, size, cudaMemcpyDeviceToHost);

    cudaFree(d_A);
    cudaFree(d_B);
    free (h_A);
    free (h_B);
}

Для этого я использовал следующие вопросы:

Запустив программу, мы попадаем в консоль:

std::cout - start
stdout - start
Output to stdout:
  0: Hello World!
std::cout - end
stdout - end

И в printf_redirect.log:

Redirected output:
  0: Hello World!
  1: Hello World!

Вопрос

Есть ли более простой способ добиться этого? (например, скрытая опция CUDA или хитрый трюк C/C++)

Обратите внимание, что окончательное решение окажется в служебном классе, чтобы сделать его менее подробным в реальном коде.

1 ответ

Сторона устройства printf() вызывает неявную сериализацию печатаемых потоков, поэтому вы, вероятно, не захотите использовать ее в производственном коде.

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

Вы можете просто реализовать собственное устройство printf(), Его производительность, вероятно, будет не хуже, чем встроенная. Единственным недостатком является то, что вам нужно будет передать кольцевой буфер ядру и добавить вызов для его обработки после того, как ядро ​​вернется.

Реализация будет примерно такой:

  • Создать буфер с комнатой для фиксированного числа printf() форматирование строк и связанных с ними 32-битных или 64-битных параметров.

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

  • Передайте кольцевой буфер ядру, которое затем передает его функции устройства вместе с параметрами печати.

  • Создайте функцию хоста, которая принимает кольцевой буфер, запускает строки форматирования и параметры на стороне хоста sprintf() и передает результаты в регистратор.

Другие вопросы по тегам