संभवतः पिछले उत्तरों के लिए एक अधिक तकनीकी जोड़: CUDA (यानी एनवीडिया) जीपीयू को उन प्रोसेसरों के एक सेट के रूप में वर्णित किया जा सकता है जो 32 थ्रेड्स पर स्वायत्त रूप से काम करते हैं। प्रत्येक प्रोसेसर में थ्रेड लॉक-स्टेप (लंबाई 32 के वैक्टर के साथ SIMD) पर काम करते हैं।
हालांकि GPUs के साथ काम करने का सबसे लुभावना तरीका यह है कि लॉक-स्टेप में सब कुछ चलता है, लेकिन यह हमेशा चीजों को करने का सबसे कारगर तरीका नहीं है।
यदि आपका कोड अच्छी तरह से / स्वचालित रूप से सैकड़ों / हजारों थ्रेड्स के साथ समानांतर नहीं होता है , तो आप इसे अलग-अलग अतुल्यकालिक कार्यों में तोड़ने में सक्षम हो सकते हैं जो अच्छी तरह से समानांतर करते हैं, और लॉक-स्टेप में चलने वाले केवल 32 थ्रेड्स के साथ निष्पादित करते हैं। सीयूडीए परमाणु निर्देशों का एक सेट प्रदान करता है जो म्यूटेक्स को लागू करना संभव बनाता है जो बदले में प्रोसेसर को आपस में सिंक्रनाइज़ करने और थ्रेड पूल प्रतिमान में कार्यों की सूची संसाधित करने की अनुमति देता है । आपका कोड फिर उसी तरह से बहुत काम करेगा, जैसा कि वह मल्टी-कोर सिस्टम पर करता है, बस ध्यान रखें कि प्रत्येक कोर में 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 धागे हैं और इस तरह एक ही ताना में फिट बैठता है। इस उदाहरण में मैंने यह भी माना कि, सादगी के लिए, कि कार्यों में कोई निर्भरता नहीं है (उदाहरण के लिए, एक कार्य दूसरे के परिणामों पर निर्भर करता है) या संघर्ष (जैसे एक ही वैश्विक मेमोरी पर काम)। यदि यह मामला है, तो कार्य चयन थोड़ा अधिक जटिल हो जाता है, लेकिन संरचना अनिवार्य रूप से समान है।
यह निश्चित रूप से, कोशिकाओं के एक बड़े बैच पर सब कुछ करने की तुलना में अधिक जटिल है, लेकिन महत्वपूर्ण रूप से उन समस्याओं के प्रकार को व्यापक करता है जिनके लिए जीपीयू का उपयोग किया जा सकता है।