برمجة GPU مع C ++

Gpu Programming With C



في هذا الدليل ، سوف نستكشف قوة برمجة وحدة معالجة الرسومات باستخدام C ++. يمكن للمطورين توقع أداء مذهل مع C ++ ، ويمكن أن يؤدي الوصول إلى القوة الهائلة لوحدة معالجة الرسومات بلغة منخفضة المستوى إلى الحصول على بعض من أسرع العمليات الحسابية المتاحة حاليًا.

متطلبات

على الرغم من أن أي جهاز قادر على تشغيل إصدار حديث من Linux يمكنه دعم مترجم C ++ ، فستحتاج إلى وحدة معالجة الرسومات (GPU) المستندة إلى NVIDIA لمتابعة هذا التمرين. إذا لم يكن لديك GPU ، فيمكنك تشغيل مثيل مدعوم من GPU في Amazon Web Services أو مزود سحابي آخر من اختيارك.







إذا اخترت جهازًا فعليًا ، فيرجى التأكد من تثبيت برامج التشغيل الخاصة بـ NVIDIA. يمكنك العثور على التعليمات الخاصة بهذا هنا: https://linuxhint.com/install-nvidia-drivers-linux/



بالإضافة إلى برنامج التشغيل ، ستحتاج إلى مجموعة أدوات CUDA. في هذا المثال ، سنستخدم Ubuntu 16.04 LTS ، ولكن هناك تنزيلات متاحة لمعظم التوزيعات الرئيسية على عنوان URL التالي: https://developer.nvidia.com/cuda-downloads



بالنسبة إلى Ubuntu ، يمكنك اختيار التنزيل المستند إلى .deb. لن يكون الملف الذي تم تنزيله بامتداد .deb افتراضيًا ، لذلك أوصي بإعادة تسميته ليكون له امتداد .deb في النهاية. بعد ذلك ، يمكنك التثبيت باستخدام:





سودو dpkg -أنااسم الحزمة

من المحتمل أن تتم مطالبتك بتثبيت مفتاح GPG ، وإذا كان الأمر كذلك ، فاتبع الإرشادات المقدمة للقيام بذلك.

بمجرد القيام بذلك ، قم بتحديث مستودعاتك:



سودو تحديث apt-get
سودو تثبيت apt-getالمعجزات

بمجرد الانتهاء من ذلك ، أوصي بإعادة التشغيل لضمان تحميل كل شيء بشكل صحيح.

فوائد تطوير GPU

تتعامل وحدات المعالجة المركزية (CPU) مع العديد من المدخلات والمخرجات المختلفة وتحتوي على مجموعة كبيرة من الوظائف ليس فقط للتعامل مع مجموعة متنوعة من احتياجات البرامج ولكن أيضًا لإدارة تكوينات الأجهزة المختلفة. كما أنها تتعامل مع الذاكرة والتخزين المؤقت ونقل النظام والتجزئة ووظائف الإدخال / الإخراج ، مما يجعلها مقبسًا لجميع الصفقات.

إن وحدات معالجة الرسومات هي عكس ذلك - فهي تحتوي على العديد من المعالجات الفردية التي تركز على وظائف رياضية بسيطة للغاية. وبسبب هذا ، فإنهم يعالجون المهام أسرع بكثير من وحدات المعالجة المركزية (CPU). من خلال التخصص في الوظائف العددية (وظيفة تأخذ مدخلاً واحدًا أو أكثر ولكنها ترجع مخرجات واحدة فقط) ، فإنها تحقق أداءً فائقًا على حساب التخصص الشديد.

رمز المثال

في رمز المثال ، نضيف المتجهات معًا. لقد أضفت إصدارًا من الكود الخاص بوحدة المعالجة المركزية ووحدة معالجة الرسومات لمقارنة السرعة.
gpu-example.cpp المحتويات أدناه:

# تضمين 'cuda_runtime.h'
#يشمل
#يشمل
#يشمل
#يشمل
#يشمل

typedefساعات::كرونو::high_resolution_clockساعة؛

#define ITER 65535

// نسخة وحدة المعالجة المركزية من المتجه إضافة وظيفة
فارغvector_add_cpu(int *إلى،int *ب،int *ج ،intن) {
intأنا؛

// أضف عناصر المتجه a و b إلى المتجه c
ل (أنا= 0؛أنا<ن؛ ++أنا) {
ج[أنا] =إلى[أنا] +ب[أنا]؛
}
}

// إصدار GPU من وظيفة إضافة المتجه
__عالمي__فارغvector_add_gpu(int *gpu_a ،int *gpu_b ،int *gpu_c ،intن) {
intأنا=مؤشر ترابط.x؛
// لا حاجة إلى حلقة لأن وقت تشغيل CUDA
// سيخيط مرات ITER هذه
gpu_c[أنا] =gpu_a[أنا] +gpu_b[أنا]؛
}

intالأساسية() {

int *إلى،*ب،*ج؛
int *gpu_a ،*gpu_b ،*gpu_c؛

إلى= (int *)مالوك(ITER* حجم(int))؛
ب= (int *)مالوك(ITER* حجم(int))؛
ج= (int *)مالوك(ITER* حجم(int))؛

// نحن بحاجة إلى متغيرات يمكن الوصول إليها من خلال وحدة معالجة الرسومات ،
// لذا فإن cudaMallocManaged يوفر هذه
cudaMalloc مُدار(&gpu_a ، ITER* حجم(int))؛
cudaMalloc مُدار(&gpu_b ، ITER* حجم(int))؛
cudaMalloc مُدار(&gpu_c ، ITER* حجم(int))؛

ل (intأنا= 0؛أنا<ITER؛ ++أنا) {
إلى[أنا] =أنا؛
ب[أنا] =أنا؛
ج[أنا] =أنا؛
}

// اتصل بوظيفة وحدة المعالجة المركزية ووقتها
تلقاءيcpu_start=ساعة::حاليا()؛
vector_add_cpu(أ ، ب ، ج ، إيتر)؛
تلقاءيcpu_end=ساعة::حاليا()؛
ساعات::كلفة << 'vector_add_cpu:'
<<ساعات::كرونو::المدة_ البث<ساعات::كرونو::نانوثانية>(cpu_end-cpu_start).عدد()
<< نانوثانية.ن'؛

// اتصل بوظيفة GPU ووقتها
// الأقواس ثلاثية الزوايا هي امتداد لوقت تشغيل CUDA يسمح
// معلمات استدعاء نواة CUDA ليتم تمريرها.
// في هذا المثال ، نقوم بتمرير كتلة خيط واحدة مع سلاسل ITER.
تلقاءيgpu_start=ساعة::حاليا()؛
vector_add_gpu<<<1، ITER>>> (gpu_a ، gpu_b ، gpu_c ، ITER)؛
cudaDeviceSynchronize()؛
تلقاءيgpu_end=ساعة::حاليا()؛
ساعات::كلفة << 'vector_add_gpu:'
<<ساعات::كرونو::المدة_ البث<ساعات::كرونو::نانوثانية>(gpu_end-gpu_start).عدد()
<< نانوثانية.ن'؛

// تحرير تخصيصات الذاكرة القائمة على وظيفة وحدة معالجة الرسومات
cudaFree(إلى)؛
cudaFree(ب)؛
cudaFree(ج)؛

// حرر تخصيصات الذاكرة القائمة على وظيفة وحدة المعالجة المركزية
مجانا(إلى)؛
مجانا(ب)؛
مجانا(ج)؛

إرجاع 0؛
}

Makefile المحتويات أدناه:

INC= -أنا/usr/محلي/المعجزات/يشمل
NVCC=/usr/محلي/المعجزات/صباحا/nvcc
NVCC_OPT= -std = c ++أحد عشر

الكل:
$(NVCC)$(NVCC_OPT)gpu-example.cpp-أوgpu- سبيل المثال

ينظف:
-rm -Fgpu- سبيل المثال

لتشغيل المثال ، قم بتجميعه:

صنع

ثم قم بتشغيل البرنامج:

./gpu- سبيل المثال

كما ترى ، فإن إصدار وحدة المعالجة المركزية (vector_add_cpu) يعمل بشكل أبطأ بكثير من إصدار GPU (vector_add_gpu).

إذا لم يكن الأمر كذلك ، فقد تحتاج إلى ضبط تعريف ITER في gpu-example.cu إلى رقم أعلى. ويرجع ذلك إلى أن وقت إعداد وحدة معالجة الرسومات أطول من بعض الحلقات الصغيرة كثيفة الاستخدام لوحدة المعالجة المركزية. لقد وجدت 65535 يعمل بشكل جيد على جهازي ، ولكن قد تختلف المسافة المقطوعة. ومع ذلك ، بمجرد مسح هذا الحد ، تصبح وحدة معالجة الرسومات أسرع بشكل كبير من وحدة المعالجة المركزية.

استنتاج

آمل أن تكون قد تعلمت الكثير من مقدمتنا إلى برمجة GPU باستخدام C ++. لا يحقق المثال أعلاه الكثير ، ولكن المفاهيم الموضحة توفر إطارًا يمكنك استخدامه لدمج أفكارك لإطلاق العنان لقوة وحدة معالجة الرسومات الخاصة بك.