HyperAIHyperAI

Command Palette

Search for a command to run...

بدء تعلم CUDA المبسط: دليل تحديثي للمبرمجين المبتدئين

ملخص بسيط ومحدث لمنصة CUDA للحوسبة المتوازية المؤلف: [غير محدد] تاريخ النشر: 25 يناير 2017 (محدث) منصة CUDA هي تقنية من شركة NVIDIA تتيح للمطورين إنشاء تطبيقات متوازية باستخدام برمجة GPU. هذا المقال يقدم مقدمة بسيطة ومحدثة لمنصة CUDA، مع التركيز على برمجة CUDA C++. البداية البسيطة سنبدأ بمثال بسيط لإضافة عناصر مصفوفتين كل منهما يحتوي على مليون عنصر. الخطوة الأولى هي إنشاء برنامج C++ بسيط وإجراء الإضافة على CPU: ```cpp include void add(int a, int b, int* c, int n) { for (int i = 0; i < n; ++i) { c[i] = a[i] + b[i]; } } int main() { const int N = 1000000; int a = new int[N]; int b = new int[N]; int* c = new int[N]; // Initialize a and b arrays for (int i = 0; i < N; ++i) { a[i] = -i; b[i] = i * i; } add(a, b, c, N); // Check for errors for (int i = 0; i < N; ++i) { if (c[i] != i * i - i) { std::cout << "Error in summation" << std::endl; return 1; } } std::cout << "No error in summation" << std::endl; delete[] a; delete[] b; delete[] c; return 0; } ``` بعد تجميع وتفيذ البرنامج، سيقوم بإضافة العناصر بشكل صحيح وطباعة رسالة عدم وجود خطأ. تحويل الوظيفة إلى كيرنل CUDA للقيام بالإضافة على GPU، نحتاج إلى تحويل الوظيفة add إلى كيرنل CUDA. هذا يتم بإضافة المحدد __global__ إلى الوظيفة: cpp __global__ void add(int* a, int* b, int* c, int n) { for (int i = 0; i < n; ++i) { c[i] = a[i] + b[i]; } } كما سنحتاج إلى تخصيص الذاكرة في الذاكرة الموحدة باستخدام cudaMallocManaged() وتحريرها باستخدام cudaFree(): cpp int* a; cudaMallocManaged(&a, N * sizeof(int)); int* b; cudaMallocManaged(&b, N * sizeof(int)); int* c; cudaMallocManaged(&c, N * sizeof(int)); وأخيراً، سنقوم بإطلاق الكيرنل باستخدام بناء الجملة <<< >>>: cpp add<<<1, 1>>>(a, b, c, N); cudaDeviceSynchronize(); زيادة السرعة باستخدام الخيوط المتوازية لزيادة السرعة، نحتاج إلى استخدام خيوط متوازية. يمكننا تغيير عدد الخيوط في الكتلة (block) إلى 256 خيطاً: cpp const int blockSize = 256; add<<<1, blockSize>>>(a, b, c, N); ثم نعدل الكيرنل لاستخدام فهرسة الخيوط: cpp __global__ void add(int* a, int* b, int* c, int n) { int index = threadIdx.x; int stride = blockDim.x; for (int i = index; i < n; i += stride) { c[i] = a[i] + b[i]; } } بعد تطبيق هذه التعديلات، سيؤدي البرنامج إلى سرعة كبيرة (من 75 ميلي ثانية إلى 4 ميلي ثانية). استغلال جميع الكتل المتوازية GPU لديها العديد من المعالجات المتوازية المجمعة في مجموعات تسمى Streaming Multiprocessors (SMs). يمكن لكل SM تشغيل عدة كتل خيوط متوازية، لكن كل كتلة تعمل على SM واحد. للاستفادة من جميع هذه الخيوط، يجب إطلاق الكيرنل باستخدام عدة كتل: cpp const int blockSize = 256; const int gridSize = (N + blockSize - 1) / blockSize; add<<<gridSize, blockSize>>>(a, b, c, N); وعدل الكيرنل ليعتبر جميع الكتل: cpp __global__ void add(int* a, int* b, int* c, int n) { int index = blockIdx.x * blockDim.x + threadIdx.x; int stride = blockDim.x * gridDim.x; for (int i = index; i < n; i += stride) { c[i] = a[i] + b[i]; } } معالجة زجاجة العنق الذاكرة عند تشغيل البرنامج، قد لا نلاحظ زيادة في السرعة بسبب زجاجة العنق في الذاكرة. هذا يحدث لأن البيانات تبدأ في ذاكرة CPU وتنتقل إلى ذاكرة GPU عند حدوث الأخطاء الافتراضية. لحل هذه المشكلة، يمكننا استخدام الوظيفة cudaMemPrefetchAsync() لتحميل البيانات قبل تشغيل الكيرنل: ```cpp cudaMemPrefetchAsync(a, N * sizeof(int), 0); cudaMemPrefetchAsync(b, N * sizeof(int), 0); cudaMemPrefetchAsync(c, N * sizeof(int), 0); add<<>>(a, b, c, N); cudaDeviceSynchronize(); ``` بعد تطبيق هذا التعديل، ستزداد سرعة الكيرنل بشكل كبير (تحت 50 ميكرو ثانية). ملخص الأداء | الإصدار | الوقت (نانوثانية) | زيادة السرعة مقابل الخيط الواحد | النقل (ميجابت/ثانية) | |----------|------------------|-----------------------------------|---------------------| | خيط واحد | 91,811,206 | 1x | 137 ميجابت/ثانية | | كتلة واحدة (256 خيط) | 2,049,034 | 45x | 6 جيجابت/ثانية | | كتل متعددة | 47,520 | 1932x | 265 جيجابت/ثانية | تمارين تصفح وثائق CUDA Toolkit. قم بتثبيت CUDA إذا لم تقم بذلك بعد، ثم تصفح دليل البرمجة ودليل أفضل الممارسات. جرب استخدام printf() داخل الكيرنل. هل يتم طباعة قيم threadIdx.x و blockIdx.x بطريقة تسلسلية؟ لماذا أو لماذا لا؟ طبّق قيم threadIdx.y أو threadIdx.z (أو blockIdx.y) داخل الكيرنل. لماذا توجد هذه القيم؟ كيف يمكنك جعلها تأخذ قيماً غير صفرية؟ ماذا بعد؟ إذا كنت مهتماً بتعلم المزيد عن CUDA، هناك العديد من الموارد المتاحة: مقدمة CUDA C++: سلسلة مقالات قديمة يمكنك الاستمرار بها. CUDA Fortran: سلسلة مقالات مماثلة تركز على برمجة CUDA Fortran. مدونة المطورين NVIDIA: تحتوي على محتوى غني حول CUDA C++ وغيرها من موضوعات الحوسبة GPU. دورات CUDA البرمجية: تقدم NVIDIA دورات برمجية مكثفة عبر DLI. البرمجة المتقدمة: يمكن العثور على مواد أكثر تقدماً في كتالوج DLI الذاتي الوتيرة. تقييم الحدث من قبل المختصين يعد هذا المقال مرجعًا ممتازًا للمطورين الجدد الذين يرغبون في تعلم CUDA. يوفر شرحًا واضحًا ومفصلًا للأفكار الأساسية ويقدم أمثلة عملية يمكن للمبتدئين تجربتها بسهولة. كما أنه يسلط الضوء على بعض التقنيات المتقدمة مثل تحميل البيانات مسبقًا (prefetching) لتجنب زجاجة العنق في الذاكرة، مما يجعله موردًا شاملًا ومفيدًا. نبذة تعريفية عن NVIDIA NVIDIA هي شركة رائدة في مجال تصميم أشباه الموصلات وتقنيات الحوسبة المرئية. تشتهر بتطوير بطاقات الرسومات (GPUs) وأجهزة الحوسبة المتوازية مثل منصة CUDA. تقدم NVIDIA حلولًا مبتكرة في مجالات مثل الذكاء الاصطناعي، الحوسبة السحابية، والمحاكاة الفيزيائية، مما يجعلها واحدة من الشركات الأكثر تأثيرًا في الصناعة التقنية الحديثة.

الروابط ذات الصلة

بدء تعلم CUDA المبسط: دليل تحديثي للمبرمجين المبتدئين | القصص الشائعة | HyperAI