Ошибка сегментации 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
и все будет хорошо. Чтобы сделать этот класс пригодным для использования только на устройстве и устройстве, процесс может быть похожим. Я мог бы:
- cudaMalloc указатель на память устройства, которая будет хранить
myclass
- (опционально) скопировать экземпляр объекта
myclass
на хосте указатель устройства с шага 1 с помощью cudaMemcpy - на устройстве, используйте
malloc
или жеnew
выделить память устройства дляmyptr
Вышеприведенная последовательность хороша, если я никогда не хочу получить доступ к хранилищу, выделенному для myptr
на хосте. Но если я хочу, чтобы это хранилище было видно с хоста, мне нужна другая последовательность:
- cudaMalloc указатель на память устройства, которая будет хранить
myclass
давайте назовем этоmydevobj
- (опционально) скопировать экземпляр объекта
myclass
на хосте указатель устройстваmydevobj
из шага 1 с использованием cudaMemcpy - Создайте отдельный указатель int на хост, давайте назовем его
myhostptr
- cudaMalloc
int
хранение на устройстве дляmyhostptr
- 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
):