Debugging & Profiling

Debugging

Compilarea pentru debugging

Compilarea pentru debugging se face utilizand flagurile “-g -G”. De asemenea este recomandat sa nu folositi flag-uri de optimizare cand compilati pentru debugging. Exemplu:

$ nvcc -g -G foo.cu -o foo

Despre debugger

Debugger-ul CUDA reprezintă o extensie a GDB. Prin urmare, se pot folosi toate comenzile GDB obișnuite de lucru împreună cu comenzi specifice CUDA, de exemplu pentru selecția thread-ului GPU.

Rularea debugger-ului:

$ cuda-gdb ./gpu_program  
Cateva comenzi utile pentru cuda-gdb
@@break mykernel_main@@ – seteaza un breakpoint in kernel
@@thread «<(BX,BY),(TX,TY,TZ)»> @@ – selecteaza un bloc si un thread individual
@@thread «<(0),(1)»> @@ – selecteaza thread-ul 1 in blocul 0
@@info cuda state @@ – self expl.

Interfata grafica pentru debugger

cuda-gdb este un debugger in mod text. Fiind o extensie a gdb, orice interfata grafica ce suporta gdb poate fi folosita. De exemplu, DDD:

  ddd --debugger cuda-gdb ./gpu_program </code>

Daca se foloseste o conexiune ssh, este nevoie de folosirea flagului –X pentru X11 forwarding:

  ssh -X user@machine </code>

'Nota:' pentru masinile cu mai multe placi video, pentru rularea codului sub debugger, s-ar putea sa fie necesara utilizarea placii video cu device_id 0. Deci apelurile pentru SelectDevice() </code> trebuie sa aiba drept parametru gpu device cu id 0.

Emulation mode

Pentru a facilita debuggingul programelor si/sau rularea codului pe o masina ce nu suporta CUDA, exista posibilitatea compilarii codului in modul emulare. Codul kernel, ce ar trebui sa ruleze pe device, va rula de fapt pe host, CPU-only. Se pot folosi astfel toate uneltele uzuale pentru inspectia si debuggingul codului (valgrind, gdb etc.), detectia deadlockurilor provocate de syncthreads() etc. Flagul nvcc </code> pentru setarea emulation mode este –deviceemu </code>.

Se poate folosi macro-ul de preprocesor DEVICE_EMULATION </code> pentru a determina codul ce va fi executat doar in acest mod. Un exemplu de caz de utilizare este adaugarea de printf-uri in device code.

Fiecare device thread va fi emulat printr-un thread obisnuit, pe host.

Probleme posibile: threadurile vor fi rulate secvential, deci se vor obtine rezultate diferite daca au loc accese simultane la aceeasi zona de memorie. Dereferentierea pointerilor spre host-memory din device code va produce rezultate corecte in mod emulare, insa va provoca erori in timpul rularii pe GPU. Calculele in virgula mobila pot produce rezultate diferite, din cauza diferentelor de implementare hardware a FPU.

Exemple de utilizare:

#ifdef __DEVICE_EMULATION__
	printf("\t [!]  Using CUDA emulation\n");
#else
	printf("\t [!]  Using CUDA on GPU\n");
#endif

Functiile non-CUDA (deci si printf) nu pot fi apelate din device context. Insa ele sunt utile pentru debugging. Pentru a le putea folosi si a
nu provoca erori de compilare, se poate utiliza un cod similar cu urmatorul:

#ifdef __DEVICE_EMULATION__
	#define cudaPrint(fmt, args...) fprintf(stdout, fmt, ##args)
#else
	#define cudaPrint(fmt, args...) 
#endif

Profiling

SDK-ul nVidia are in componenta sa un profiler, pentru a ajuta in detectarea bottleneck-urilor din cod. Din pacate, nu permite examinarea parametrilor de rulare la un nivel foarte scazut iar rezultatele sale nu sunt foarte precise, fiind utilizat mai mult pentru a comparatii relative intre mai multe versiuni ale aceluiasi cod (de ex: optimizat fata de neoptimizat).

Rularea profiler-ului

Profilerul are doua moduri de operare: text/cli sau grafic. Pentru modul grafic, interfata se ruleaza cu cudaprof </code> si se aleg parametrii de rulare pentru o noua sesiune. Profilerul poate primi doar 4 semnale per rulare, deci pentru a obtine toti parametrii, se va rula programul de mai multe ori.

Parametri returnati (performance counters)

  • operatii cu memoria globala:
    • gld_request - numarul de cereri de load din memoria globala
    • gst_request - numarul de cereri de store in memoria globala

Pentru chipurile din generatia veche, G 80, vor fi prezenti parametri cu sufixul _incoherent si _coherent. Acestia se refera la numarul de operatii cu memoria globala ce nu au un pattern de acces coerent.
Accesul incoerent la memorie a fost rezolvat in generatia GT 200, parametrii acestia nemaifiind deci utili.

  • operatii cu memoria locala:
    • local_load - numarul de cereri de store in memoria locala
    • local_store - numarul de cereri de store in memoria locala
    • atentie: se refera la memoria locala, nu la cea shared!
  • parametri referitori la branching:
    • branch - numarul de evenimente de branching aparute intr-un warp
    • divergent_branching - numarul de branch-uri divergente aparute intr-un warp
  • instructiuni - numarul de instructiuni executate
  • warp_serialize - numarul de threaduri aflate intr-un eveniment de warp serialization

Drept regula de baza pentru cresterea performantei, valoarea urmatorilor parametri trebuie sa fie minima, preferabil zero:

  • divergent_branching - cele doua ramuri ale branchigului se executa serial → scaderea paralelismului → scaderea performantei
  • warp_serialize - patternul accesului la memorie este gresit; threadurile asteapta unul dupa altul accesul la aceeasi adresa de memorie
  • gld/gst_request, local_load/_store - accesul la memoria globala este foarte lent; este preferat mecanismul de caching in memoria shared pentru minimizarea accesului la memoria globala/locala

Interpretarea valorilor returnate

Valorile returnate de catre profiler nu corespund activitatii individuale ale unui thread. Ci reprezinta de fapt evenimente ce au loc intr-nu thread warp (pentru GTX260, un thread warp are 32 de threaduri). De exemplu, o ramura divergenta intr-un thread warp va incrementa contorul divergent_branch cu 1. Deci valoarea finala a contorului stocheaza informatia pentru toate ramurile divergente din toate warpurile. In plus, profilerul poate colecta parametri doar unui singur multiprocesor din GPU, deci valorile finale nu vor reflecta numarul total de warpuri lansate pentru un kernel. Din acest motiv, pentru a avea valori relevante, multiprocesorul (SM) trebuie sa primeasca un procent mare din munca totala. Un numar de 100 de blocuri este de obicei suficient.

asc/cellcookbook/debugging.txt · Last modified: 2014/05/09 07:41 by dan.dragomir
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0