ในคู่มือนี้ เราจะสำรวจพลังของการเขียนโปรแกรม GPU ด้วย C++ นักพัฒนาสามารถคาดหวังประสิทธิภาพที่น่าทึ่งด้วย C ++ และการเข้าถึงพลังมหัศจรรย์ของ GPU ด้วยภาษาระดับต่ำสามารถให้การคำนวณที่เร็วที่สุดที่มีอยู่ในปัจจุบัน
ความต้องการ
แม้ว่าเครื่องใดก็ตามที่สามารถใช้ 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 ต่อท้าย จากนั้น คุณสามารถติดตั้งด้วย:
sudo dpkg -ผมpackage-name.deb
คุณอาจจะได้รับแจ้งให้ติดตั้งคีย์ GPG และหากเป็นเช่นนั้น ให้ปฏิบัติตามคำแนะนำที่ให้ไว้เพื่อดำเนินการดังกล่าว
เมื่อคุณทำเสร็จแล้ว ให้อัปเดตที่เก็บของคุณ:
sudo apt-get update
sudo apt-get installปาฏิหาริย์-และ
เมื่อเสร็จแล้ว ฉันแนะนำให้รีบูตเพื่อให้แน่ใจว่าทุกอย่างโหลดอย่างถูกต้อง
ประโยชน์ของการพัฒนา GPU
ซีพียูรองรับอินพุตและเอาต์พุตที่แตกต่างกันมากมาย และมีฟังก์ชั่นมากมาย ไม่เพียงแต่จัดการกับความต้องการของโปรแกรมที่หลากหลาย แต่ยังสำหรับการจัดการการกำหนดค่าฮาร์ดแวร์ที่แตกต่างกันด้วย พวกเขายังจัดการหน่วยความจำ การแคช บัสระบบ การแบ่งเซกเมนต์ และฟังก์ชัน IO ทำให้เป็นแจ็คของการซื้อขายทั้งหมด
GPU ตรงกันข้าม – พวกเขามีโปรเซสเซอร์หลายตัวที่เน้นฟังก์ชันทางคณิตศาสตร์ที่ง่ายมาก ด้วยเหตุนี้ พวกเขาจึงประมวลผลงานได้เร็วกว่า CPU หลายเท่า โดยเชี่ยวชาญในฟังก์ชันสเกลาร์ (ฟังก์ชันที่รับอินพุตตั้งแต่หนึ่งอินพุตขึ้นไป แต่ส่งคืนเอาต์พุตเพียงเอาต์พุตเดียว) ฟังก์ชันเหล่านี้จะบรรลุประสิทธิภาพสูงสุดด้วยต้นทุนของความเชี่ยวชาญขั้นสูงสุด
ตัวอย่างโค้ด
ในโค้ดตัวอย่าง เราบวกเวกเตอร์เข้าด้วยกัน ฉันได้เพิ่มรหัสเวอร์ชัน CPU และ GPU เพื่อเปรียบเทียบความเร็ว
gpu-example.cpp เนื้อหาด้านล่าง:
#รวม
#รวม
#รวม
#รวม
#รวม
typedefชั่วโมง::โครโน::ความละเอียดสูง_นาฬิกานาฬิกา;
#define ITER 65535
// เวอร์ชัน CPU ของฟังก์ชันเพิ่มเวกเตอร์
โมฆะvector_add_cpu(int *ถึง,int *NS,int *ค,intNS) {
intผม;
// เพิ่มองค์ประกอบเวกเตอร์ a และ b ให้กับเวกเตอร์ c
สำหรับ (ผม= 0;ผม<NS; ++ผม) {
ค[ผม] =ถึง[ผม] +NS[ผม];
}
}
// เวอร์ชัน GPU ของฟังก์ชันเพิ่มเวกเตอร์
__ทั่วโลก__โมฆะvector_add_gpu(int *gpu_a,int *gpu_b,int *gpu_c,intNS) {
intผม=เธรด Idx.NS;
// ไม่จำเป็นต้องวนซ้ำเพราะรันไทม์ CUDA
// จะเธรด ITER นี้ครั้ง
gpu_c[ผม] =gpu_a[ผม] +gpu_b[ผม];
}
intหลัก() {
int *ถึง,*NS,*ค;
int *gpu_a,*gpu_b,*gpu_c;
ถึง= (int *)malloc(ITER* ขนาดของ(int));
NS= (int *)malloc(ITER* ขนาดของ(int));
ค= (int *)malloc(ITER* ขนาดของ(int));
// เราต้องการตัวแปรที่ GPU เข้าถึงได้
// ดังนั้น cudaMallocManaged จึงจัดเตรียมสิ่งเหล่านี้
cudaMallocManaged(&gpu_a, ITER* ขนาดของ(int));
cudaMallocManaged(&gpu_b, ITER* ขนาดของ(int));
cudaMallocManaged(&gpu_c, ITER* ขนาดของ(int));
สำหรับ (intผม= 0;ผม<ITER; ++ผม) {
ถึง[ผม] =ผม;
NS[ผม] =ผม;
ค[ผม] =ผม;
}
// เรียกใช้ฟังก์ชัน CPU และตั้งเวลา
รถยนต์cpu_start=นาฬิกา::ตอนนี้();
vector_add_cpu(a, b, c, ITER);
รถยนต์cpu_end=นาฬิกา::ตอนนี้();
ชั่วโมง::ค่าใช้จ่าย << 'vector_add_cpu: '
<<ชั่วโมง::โครโน::ระยะเวลา_cast<ชั่วโมง::โครโน::นาโนวินาที>(cpu_end-cpu_start).นับ()
<< ' นาโนวินาทีNS';
// เรียกใช้ฟังก์ชัน GPU และตั้งเวลา
// เบรกสามมุมเป็นส่วนขยายรันไทม์ CUDA ที่อนุญาต
// พารามิเตอร์ของการเรียกเคอร์เนล CUDA ที่จะส่งผ่าน
// ในตัวอย่างนี้ เรากำลังส่งบล็อกเธรดหนึ่งบล็อกด้วยเธรด ITER
รถยนต์gpu_start=นาฬิกา::ตอนนี้();
vector_add_gpu<<<1, ITER>>> (gpu_a, gpu_b, gpu_c, ITER);
cudaDeviceSynchronize();
รถยนต์gpu_end=นาฬิกา::ตอนนี้();
ชั่วโมง::ค่าใช้จ่าย << 'vector_add_gpu: '
<<ชั่วโมง::โครโน::ระยะเวลา_cast<ชั่วโมง::โครโน::นาโนวินาที>(gpu_end-gpu_start).นับ()
<< ' นาโนวินาทีNS';
// ฟรีการจัดสรรหน่วยความจำตามฟังก์ชัน GPU
cudaFree(ถึง);
cudaFree(NS);
cudaFree(ค);
// ฟรีการจัดสรรหน่วยความจำตามฟังก์ชัน CPU
ฟรี(ถึง);
ฟรี(NS);
ฟรี(ค);
กลับ 0;
}
Makefile เนื้อหาด้านล่าง:
INC=-I/usr/ท้องถิ่น/ปาฏิหาริย์/รวมNVCC=/usr/ท้องถิ่น/ปาฏิหาริย์/เป็น/nvcc
NVCC_OPT=-std=c++สิบเอ็ด
ทั้งหมด:
$(NVCC)$(NVCC_OPT)gpu-example.cpp-หรือตัวอย่าง gpu
ทำความสะอาด:
-rm -NSตัวอย่าง gpu
ในการรันตัวอย่าง ให้คอมไพล์:
ทำจากนั้นรันโปรแกรม:
./ตัวอย่าง gpuอย่างที่คุณเห็น เวอร์ชัน CPU (vector_add_cpu) ทำงานช้ากว่าเวอร์ชัน GPU (vector_add_gpu) มาก
หากไม่เป็นเช่นนั้น คุณอาจต้องปรับการกำหนด ITER ใน gpu-example.cu เป็นตัวเลขที่สูงขึ้น เนื่องจากเวลาในการตั้งค่า GPU นั้นยาวนานกว่าลูปที่ใช้ CPU จำนวนมากที่มีขนาดเล็กกว่า ฉันพบว่า 65535 ทำงานได้ดีบนเครื่องของฉัน แต่ระยะของคุณอาจแตกต่างกันไป อย่างไรก็ตาม เมื่อคุณล้างขีดจำกัดนี้ GPU จะเร็วกว่า CPU อย่างมาก
บทสรุป
ฉันหวังว่าคุณจะได้เรียนรู้มากมายจากการแนะนำการเขียนโปรแกรม GPU ด้วย C++ ตัวอย่างข้างต้นไม่ได้ผลมากนัก แต่แนวคิดที่แสดงไว้มีกรอบงานที่คุณสามารถใช้เพื่อรวมแนวคิดของคุณเพื่อปลดปล่อยพลังของ GPU ของคุณ