การเขียนโปรแกรม GPU ด้วย C++

Gpu Programming With C



ในคู่มือนี้ เราจะสำรวจพลังของการเขียนโปรแกรม 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 เนื้อหาด้านล่าง:

#include 'cuda_runtime.h'
#รวม
#รวม
#รวม
#รวม
#รวม

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 ของคุณ