Mungkin tambahan yang lebih teknis untuk balasan sebelumnya: CUDA (yaitu Nvidia) GPU dapat digambarkan sebagai satu set prosesor yang bekerja secara otonom di masing-masing 32 utas. Utas di setiap prosesor berfungsi dalam langkah-kunci (pikirkan SIMD dengan vektor panjang 32).
Meskipun cara yang paling menggoda untuk bekerja dengan GPU adalah dengan berpura-pura bahwa semuanya benar-benar berjalan secara terkunci, ini tidak selalu merupakan cara yang paling efisien untuk melakukan sesuatu.
Jika kode Anda tidak memparalelkan dengan baik / otomatis ke ratusan / ribuan utas, Anda mungkin dapat memecahnya menjadi tugas-tugas asinkron individu yang melakukan paralelisasi dengan baik, dan menjalankannya dengan hanya 32 utas yang berjalan dalam langkah-kunci. CUDA menyediakan serangkaian instruksi atom yang memungkinkan untuk mengimplementasikan mutex yang pada gilirannya memungkinkan prosesor untuk melakukan sinkronisasi di antara mereka sendiri dan memproses daftar tugas dalam paradigma thread pool . Kode Anda kemudian akan bekerja banyak dengan cara yang sama seperti pada sistem multi-inti, hanya perlu diingat bahwa setiap inti kemudian memiliki 32 utas sendiri.
Berikut adalah contoh kecil, menggunakan CUDA, tentang cara kerjanya
/* Global index of the next available task, assume this has been set to
zero before spawning the kernel. */
__device__ int next_task;
/* We will use this value as our mutex variable. Assume it has been set to
zero before spawning the kernel. */
__device__ int tasks_mutex;
/* Mutex routines using atomic compare-and-set. */
__device__ inline void cuda_mutex_lock ( int *m ) {
while ( atomicCAS( m , 0 , 1 ) != 0 );
}
__device__ inline void cuda_mutex_unlock ( int *m ) {
atomicExch( m , 0 );
}
__device__ void task_do ( struct task *t ) {
/* Do whatever needs to be done for the task t using the 32 threads of
a single warp. */
}
__global__ void main ( struct task *tasks , int nr_tasks ) {
__shared__ task_id;
/* Main task loop... */
while ( next_task < nr_tasks ) {
/* The first thread in this block is responsible for picking-up a task. */
if ( threadIdx.x == 0 ) {
/* Get a hold of the task mutex. */
cuda_mutex_lock( &tasks_mutex );
/* Store the next task in the shared task_id variable so that all
threads in this warp can see it. */
task_id = next_task;
/* Increase the task counter. */
next_tast += 1;
/* Make sure those last two writes to local and global memory can
be seen by everybody. */
__threadfence();
/* Unlock the task mutex. */
cuda_mutex_unlock( &tasks_mutex );
}
/* As of here, all threads in this warp are back in sync, so if we
got a valid task, perform it. */
if ( task_id < nr_tasks )
task_do( &tasks[ task_id ] );
} /* main loop. */
}
Anda kemudian harus memanggil kernel dengan main<<<N,32>>>(tasks,nr_tasks)
untuk memastikan bahwa setiap blok hanya berisi 32 utas dan dengan demikian cocok dalam satu warp. Dalam contoh ini saya juga mengasumsikan, untuk kesederhanaan, bahwa tugas tidak memiliki dependensi (misalnya satu tugas tergantung pada hasil yang lain) atau konflik (misalnya bekerja pada memori global yang sama). Jika ini masalahnya, maka pemilihan tugas menjadi sedikit lebih rumit, tetapi struktur dasarnya sama.
Ini, tentu saja, lebih rumit daripada hanya melakukan segalanya pada satu batch besar sel, tetapi secara signifikan memperluas jenis masalah yang dapat digunakan GPU.