Có lẽ là một bổ sung kỹ thuật hơn cho các câu trả lời trước: GPU CUDA (tức là Nvidia) có thể được mô tả như một bộ vi xử lý hoạt động tự động trên 32 luồng mỗi luồng. Các luồng trong mỗi bộ xử lý hoạt động theo bước khóa (nghĩ SIMD với các vectơ có độ dài 32).
Mặc dù cách hấp dẫn nhất để làm việc với GPU là giả vờ rằng mọi thứ hoàn toàn chạy theo bước khóa, đây không phải luôn là cách làm việc hiệu quả nhất.
Nếu mã của bạn không parallelize độc đáo / tự động đến hàng trăm / hàng ngàn chủ đề, bạn có thể phá vỡ nó xuống thành nhiệm vụ không đồng bộ cá nhân mà làm parallelize tốt, và thực hiện những chỉ với 32 đề chạy trong khóa bước. CUDA cung cấp một tập hợp các hướng dẫn nguyên tử cho phép thực hiện các mutexes , từ đó cho phép các bộ xử lý tự đồng bộ hóa với nhau và xử lý một danh sách các tác vụ trong mô hình nhóm luồng . Mã của bạn sau đó sẽ hoạt động theo cách tương tự như trên hệ thống đa lõi, chỉ cần lưu ý rằng mỗi lõi sau đó có 32 luồng của riêng nó.
Đây là một ví dụ nhỏ, sử dụng CUDA, về cách thức hoạt động của nó
/* 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. */
}
Sau đó, bạn phải gọi kernel với main<<<N,32>>>(tasks,nr_tasks)
để đảm bảo rằng mỗi khối chỉ chứa 32 luồng và do đó khớp với một sợi dọc. Trong ví dụ này tôi cũng giả sử, để đơn giản, các tác vụ không có bất kỳ sự phụ thuộc nào (ví dụ: một tác vụ phụ thuộc vào kết quả của một tác vụ khác) hoặc xung đột (ví dụ: hoạt động trên cùng một bộ nhớ chung). Nếu đây là trường hợp, thì việc lựa chọn nhiệm vụ trở nên phức tạp hơn một chút, nhưng cấu trúc về cơ bản là giống nhau.
Tất nhiên, điều này phức tạp hơn so với việc chỉ làm mọi thứ trên một lô lớn, nhưng mở rộng đáng kể loại vấn đề mà GPU có thể được sử dụng.