อาจเป็นการเพิ่มเติมทางเทคนิคเพิ่มเติมสำหรับการตอบกลับก่อนหน้านี้: CUDA (เช่น Nvidia) GPU สามารถอธิบายได้ว่าเป็นชุดของโปรเซสเซอร์ที่ทำงานแบบอัตโนมัติในแต่ละ 32 เธรด เธรดในโปรเซสเซอร์แต่ละตัวทำงานในขั้นตอนการล็อก (คิดว่า SIMD กับเวกเตอร์ที่มีความยาว 32)
แม้ว่าวิธีที่ดึงดูดใจมากที่สุดในการทำงานกับ GPU คือการแกล้งทำเป็นว่าทุกอย่างทำงานในขั้นตอนล็อค แต่นี่ไม่ใช่วิธีที่มีประสิทธิภาพที่สุดในการทำสิ่งต่าง ๆ เสมอไป
หากโค้ดของคุณไม่ได้ขนานกัน / โดยอัตโนมัติเป็นร้อย / พันของเธรดคุณอาจสามารถแยกมันออกเป็นงานอะซิงโครนัสแต่ละตัวที่ทำงานแบบขนานได้ดี CUDA จัดเตรียมชุดของคำสั่งอะตอมมิกซึ่งทำให้สามารถนำmutexesไปใช้ซึ่งทำให้โปรเซสเซอร์สามารถซิงโครไนซ์กันเองและประมวลผลรายการงานในกระบวนทัศน์พูลเธรด รหัสของคุณจะทำงานในลักษณะเดียวกับที่ทำในระบบมัลติคอร์เพียงจำไว้ว่าแต่ละคอร์นั้นมี 32 เธรดของตัวเอง
นี่คือตัวอย่างเล็ก ๆ โดยใช้ CUDA ของวิธีการทำงาน
/* 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. */
}
จากนั้นคุณต้องเรียกเคอร์เนลด้วยmain<<<N,32>>>(tasks,nr_tasks)
เพื่อให้แน่ใจว่าแต่ละบล็อกมีเพียง 32 เธรดและเหมาะกับการแปรปรวนเดี่ยว ในตัวอย่างนี้ฉันยังสันนิษฐานว่าเพื่อความเรียบง่ายว่างานไม่มีการอ้างอิงใด ๆ (เช่นงานหนึ่งขึ้นอยู่กับผลลัพธ์ของงานอื่น) หรือความขัดแย้ง (เช่นทำงานในหน่วยความจำระดับโลกเดียวกัน) หากเป็นกรณีนี้การเลือกงานจะซับซ้อนขึ้นเล็กน้อย แต่โครงสร้างนั้นเหมือนกัน
แน่นอนว่านี่ซับซ้อนกว่าการทำทุกอย่างในเซลล์จำนวนมาก แต่จะขยายประเภทของปัญหาที่สามารถใช้ GPU ได้อย่างมีนัยสำคัญ