Prawdopodobnie najlepszym sposobem sprawdzenia błędów w kodzie API środowiska wykonawczego jest zdefiniowanie funkcji obsługi stylu assert i makra opakowania w następujący sposób:
#define gpuErrchk(ans) { gpuAssert((ans), __FILE__, __LINE__); }
inline void gpuAssert(cudaError_t code, const char *file, int line, bool abort=true)
{
if (code != cudaSuccess)
{
fprintf(stderr,"GPUassert: %s %s %d\n", cudaGetErrorString(code), file, line);
if (abort) exit(code);
}
}
Następnie można zawinąć każde wywołanie interfejsu API za pomocą gpuErrchk
makra, które przetworzy status powrotu zawiniętego wywołania interfejsu API, na przykład:
gpuErrchk( cudaMalloc(&a_d, size*sizeof(int)) );
Jeśli wystąpi błąd w wywołaniu, zostanie wysłany komunikat tekstowy opisujący błąd oraz plik i wiersz w kodzie, w którym wystąpił błąd, stderr
i aplikacja zostanie zamknięta. Można sobie wyobrazić modyfikację, gpuAssert
aby zgłosić wyjątek, zamiast wywoływać exit()
bardziej wyrafinowane aplikacje, jeśli byłyby one wymagane.
Drugim powiązanym pytaniem jest, jak sprawdzić błędy w uruchomieniach jądra, których nie można bezpośrednio zawrzeć w wywołaniu makra, takim jak standardowe wywołania API środowiska wykonawczego. W przypadku jąder coś takiego:
kernel<<<1,1>>>(a);
gpuErrchk( cudaPeekAtLastError() );
gpuErrchk( cudaDeviceSynchronize() );
najpierw sprawdzi, czy argument uruchamiania jest nieprawidłowy, a następnie zmusi host do czekania, aż jądro się zatrzyma i sprawdzi błąd wykonania. Synchronizację można wyeliminować, jeśli wystąpi kolejne blokujące wywołanie interfejsu API:
kernel<<<1,1>>>(a_d);
gpuErrchk( cudaPeekAtLastError() );
gpuErrchk( cudaMemcpy(a_h, a_d, size * sizeof(int), cudaMemcpyDeviceToHost) );
w takim przypadku cudaMemcpy
wywołanie może zwrócić albo błędy, które wystąpiły podczas wykonywania jądra, albo błędy z samej kopii pamięci. Może to być mylące dla początkujących i zaleciłbym użycie jawnej synchronizacji po uruchomieniu jądra podczas debugowania, aby łatwiej zrozumieć, gdzie mogą wystąpić problemy.
Należy pamiętać, że podczas korzystania z równoległego dynamicznego interfejsu CUDA bardzo podobna metodologia może i powinna być stosowana do każdego użycia interfejsu API środowiska wykonawczego CUDA w jądrach urządzeń, a także po uruchomieniu dowolnego jądra urządzenia:
#include <assert.h>
#define cdpErrchk(ans) { cdpAssert((ans), __FILE__, __LINE__); }
__device__ void cdpAssert(cudaError_t code, const char *file, int line, bool abort=true)
{
if (code != cudaSuccess)
{
printf("GPU kernel assert: %s %s %d\n", cudaGetErrorString(code), file, line);
if (abort) assert(0);
}
}
getLastCudaError
icheckCudaErrors
które wykonują prawie wszystko, co opisano w zaakceptowanej odpowiedzi . Zobacz próbki do demonstracji. Wystarczy zainstalować próbki wraz z zestawem narzędzi, a będziesz go mieć.