Ошибка сегментации cudaMemcpy

Эта ошибка меня преследовала довольно долго, поэтому я решил опубликовать ее здесь.

Эта ошибка сегментации произошла, когда вызывается cudaMemcpy:

CurrentGrid->cdata[i] = new float[size];
cudaMemcpy(CurrentGrid->cdata[i], Grid_dev->cdata[i], size*sizeof(float),\
                cudaMemcpyDeviceToHost);

CurrentGrid а также Grid_dev являются указателем на grid Объект класса на хосте и устройстве соответственно, и я =0 в этом контексте. Член класса cdata массив указателей с плавающей точкой Для отладки прямо перед этим вызовом cudaMemcpy я распечатал значение каждого элемента Grid_Dev->cdata[i]Адрес CurrentGrid->cdata[i] а также Grid_dev->cdata[i] и значение size, что все выглядит хорошо. Но это все равно заканчивается "Ошибка сегментации (ядро сброшено)", который является единственным сообщением об ошибке. cuda-memcheck только дал "процесс не завершился успешно". Я не могу использовать cuda-gdb в данный момент. Любое предложение о том, куда идти?

ОБНОВЛЕНИЕ: Кажется, теперь я решил эту проблему с помощью cudaMalloc другого плавающего указателя A на устройстве и cudaMemcpy значения Grid_dev->cdata[i] для A, а затем cudaMemcpy A для хоста. Таким образом, фрагмент кода, написанный выше, становится:

float * A;
cudaMalloc((void**)&A, sizeof(float));
...
...
cudaMemcpy(&A, &(Grid_dev->cdata[i]), sizeof(float *), cudaMemcpyDeviceToHost);    
CurrentGrid->cdata[i] = new float[size];
cudaMemcpy(CurrentGrid->cdata[i], A, size*sizeof(float), cudaMemcpyDeviceToHost);            

Я сделал это, потому что Вальгринд выдал "недопустимое чтение размера 8", что я думал, ссылаясь на Grid_dev->cdata[i], Я проверил это снова с помощью GDB, распечатав значение Grid_dev->cdata[i] быть NULL. Так что я думаю, что не могу напрямую разыменовать указатель устройства даже в этом вызове cudaMemcpy. Но почему? Согласно комментарию внизу этого потока, мы должны иметь возможность разыменовать указатель устройства в функции cudaMemcpy.

Кроме того, я не знаю основной механизм работы cudaMalloc и cudaMemcpy, но я думаю, что cudaMalloc - указатель, скажем, здесь, мы фактически назначаем этот указатель, чтобы он указывал на определенный адрес на устройстве. И cudaMemcpy Grid_dev->cdata[i] к A, как в модифицированном коде выше, мы переназначаем указатель A, чтобы указывать на массив. Тогда не потеряем ли мы предыдущий адрес, на который указывал A, когда он является cudaMalloced? Может ли это вызвать утечку памяти или что-то? Если да, то как мне правильно обойти эту ситуацию? Спасибо!

Для справки я поставил код полной функции, в которой эта ошибка произошла ниже.

Большое спасибо!

__global__ void Print(grid *, int);
__global__ void Printcell(grid *, int);
void CopyDataToHost(param_t p, grid * CurrentGrid, grid * Grid_dev){

    cudaMemcpy(CurrentGrid, Grid_dev, sizeof(grid), cudaMemcpyDeviceToHost);
#if DEBUG_DEV
    cudaCheckErrors("cudaMemcpy1 error");
#endif
    printf("\nBefore copy cell data\n");
    Print<<<1,1>>>(Grid_dev, 0);            //Print out some Grid_dev information for 
    cudaDeviceSynchronize();                //debug 
    int NumberOfBaryonFields = CurrentGrid->ReturnNumberOfBaryonFields();
    int size = CurrentGrid->ReturnSize();
    int vsize = CurrentGrid->ReturnVSize();
    CurrentGrid->FieldType = NULL;
    CurrentGrid->FieldType = new int[NumberOfBaryonFields];
    printf("CurrentGrid size is %d\n", size);
    for( int i = 0; i < p.NumberOfFields; i++){
        CurrentGrid->cdata[i] = NULL;
        CurrentGrid->vdata[i] = NULL;
        CurrentGrid->cdata[i] = new float[size];
        CurrentGrid->vdata[i] = new float[vsize];

        Printcell<<<1,1>>>(Grid_dev, i);//Print out element value of Grid_dev->cdata[i]
        cudaDeviceSynchronize();        

        cudaMemcpy(CurrentGrid->cdata[i], Grid_dev->cdata[i], size*sizeof(float),\
                cudaMemcpyDeviceToHost);               //where error occurs
#if DEBUG_DEV
        cudaCheckErrors("cudaMemcpy2 error");
#endif
        printf("\nAfter copy cell data\n");
        Print<<<1,1>>>(Grid_dev, i);
        cudaDeviceSynchronize();
        cudaMemcpy(CurrentGrid->vdata[i], Grid_dev->vdata[i], vsize*sizeof(float),\
                cudaMemcpyDeviceToHost);
#if DEBUG_DEV
        cudaCheckErrors("cudaMemcpy3 error");
#endif
    }
    cudaMemcpy(CurrentGrid->FieldType, Grid_dev->FieldType,\
            NumberOfBaryonFields*sizeof(int), cudaMemcpyDeviceToHost);
#if DEBUG_DEV
    cudaCheckErrors("cudaMemcpy4 error");
#endif
}

РЕДАКТИРОВАТЬ: вот информация из Valgrind, из которого я пытаюсь отследить, где произошла утечка памяти.

==19340== Warning: set address range perms: large range [0x800000000, 0xd00000000) (noaccess)
==19340== Warning: set address range perms: large range [0x200000000, 0x400000000) (noaccess)
==19340== Invalid read of size 8
==19340==    at 0x402C79: CopyDataToHost(param_t, grid*, grid*) (CheckDevice.cu:48)
==19340==    by 0x403646: CheckDevice(param_t, grid*, grid*) (CheckDevice.cu:186)
==19340==    by 0x40A6CD: main (Transport.cu:81)
==19340==  Address 0x2003000c0 is not stack'd, malloc'd or (recently) free'd
==19340== 
==19340== 
==19340== Process terminating with default action of signal 11 (SIGSEGV)
==19340==  Bad permissions for mapped region at address 0x2003000C0
==19340==    at 0x402C79: CopyDataToHost(param_t, grid*, grid*) (CheckDevice.cu:48)
==19340==    by 0x403646: CheckDevice(param_t, grid*, grid*) (CheckDevice.cu:186)
==19340==    by 0x40A6CD: main (Transport.cu:81)
==19340== 
==19340== HEAP SUMMARY:
==19340==     in use at exit: 2,611,365 bytes in 5,017 blocks
==19340==   total heap usage: 5,879 allocs, 862 frees, 4,332,278 bytes allocated
==19340== 
==19340== LEAK SUMMARY:
==19340==    definitely lost: 0 bytes in 0 blocks
==19340==    indirectly lost: 0 bytes in 0 blocks
==19340==      possibly lost: 37,416 bytes in 274 blocks
==19340==    still reachable: 2,573,949 bytes in 4,743 blocks
==19340==         suppressed: 0 bytes in 0 blocks
==19340== Rerun with --leak-check=full to see details of leaked memory
==19340== 
==19340== For counts of detected and suppressed errors, rerun with: -v
==19340== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 2 from 2)

1 ответ

Решение

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

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

Предположим, у меня есть такой класс:

class myclass{
  int myval;
  int *myptr;
  }

Я мог бы создать экземпляр класса выше на хосте, а затем malloc массив int и назначить этот указатель myptr и все будет хорошо. Чтобы сделать этот класс пригодным для использования только на устройстве и устройстве, процесс может быть похожим. Я мог бы:

  1. cudaMalloc указатель на память устройства, которая будет хранить myclass
  2. (опционально) скопировать экземпляр объекта myclass на хосте указатель устройства с шага 1 с помощью cudaMemcpy
  3. на устройстве, используйте malloc или же new выделить память устройства для myptr

Вышеприведенная последовательность хороша, если я никогда не хочу получить доступ к хранилищу, выделенному для myptr на хосте. Но если я хочу, чтобы это хранилище было видно с хоста, мне нужна другая последовательность:

  1. cudaMalloc указатель на память устройства, которая будет хранить myclass давайте назовем это mydevobj
  2. (опционально) скопировать экземпляр объекта myclass на хосте указатель устройства mydevobj из шага 1 с использованием cudaMemcpy
  3. Создайте отдельный указатель int на хост, давайте назовем его myhostptr
  4. cudaMalloc int хранение на устройстве для myhostptr
  5. cudaMemcpy значение указателя myhostptr с хоста на указатель устройства &(mydevobj->myptr)

После этого вы можете cudaMemcpy данные, на которые указывает встроенный указатель myptr в регион, выделенный (через cudaMalloc) на myhostptr

Обратите внимание, что на шаге 5, поскольку я беру адрес этого расположения указателя, эта операция cudaMemcpy требует только mydevobj указатель на хост, который действителен в операции cudaMemcpy (только).

Значение указателя устройства myint затем будет правильно настроен для выполнения операций, которые вы пытаетесь выполнить. Если вы затем хотите cudaMemcpy данные в и из myint на хост, вы используете указатель myhostptr в любых вызовах cudaMemcpy, а не mydevobj->myptr, Если бы мы попытались использовать mydevobj->myptr, это потребует разыменования mydevobj а затем использовать его для получения указателя, который хранится в myptr, а затем использовать этот указатель в качестве копии в / из местоположения. Это не приемлемо в коде хоста. Если вы попытаетесь сделать это, вы получите ошибку сегмента. (Обратите внимание, что по аналогии мой mydevobj как твой Grid_dev и мой myptr как твой cdata)

В целом, это концепция, которая требует тщательного обдумывания, когда вы впервые сталкиваетесь с ней, и поэтому подобные вопросы встречаются с некоторой частотой в SO. Возможно, вы захотите изучить некоторые из этих вопросов, чтобы увидеть примеры кода (поскольку вы не предоставили свой код, который настраивает Grid_dev):

  1. пример 1
  2. пример 2
  3. пример 3
Другие вопросы по тегам