Dalam panduan ini, kami akan meneroka kehebatan pengaturcaraan GPU dengan C ++. Pembangun dapat mengharapkan prestasi luar biasa dengan C ++, dan mengakses kekuatan GPU yang luar biasa dengan bahasa tahap rendah dapat menghasilkan beberapa pengiraan terpantas yang ada saat ini.
Keperluan
Walaupun mana-mana mesin yang mampu menjalankan versi Linux moden dapat menyokong penyusun C ++, anda memerlukan GPU berasaskan NVIDIA untuk mengikuti latihan ini. Sekiranya anda tidak mempunyai GPU, anda boleh mendapatkan contoh yang dikuasakan oleh GPU di Amazon Web Services atau penyedia awan pilihan anda.
Sekiranya anda memilih mesin fizikal, pastikan anda memasang pemandu proprietari NVIDIA. Anda boleh mendapatkan arahan untuk ini di sini: https://linuxhint.com/install-nvidia-drivers-linux/
Sebagai tambahan kepada pemacu, anda memerlukan kit alat CUDA. Dalam contoh ini, kami akan menggunakan Ubuntu 16.04 LTS, tetapi terdapat muat turun yang tersedia untuk sebilangan besar pengedaran utama di URL berikut: https://developer.nvidia.com/cuda-downloads
Untuk Ubuntu, anda akan memilih muat turun berasaskan .deb. Fail yang dimuat turun tidak akan mempunyai pelanjutan .deb secara lalai, jadi saya cadangkan untuk menamakannya menjadi .deb pada akhir. Kemudian, anda boleh memasang dengan:
sudo dpkg -ipakej-nama.deb
Anda mungkin akan diminta untuk memasang kunci GPG, dan jika ya, ikuti arahan yang diberikan untuk melakukannya.
Setelah selesai, kemas kini repositori anda:
sudo apt-get kemas kini
sudo pasang-dapatkankeajaiban-dan
Setelah selesai, saya cadangkan reboot untuk memastikan semuanya dimuat dengan betul.
Kebaikan Pembangunan GPU
CPU menangani banyak input dan output yang berbeza dan mengandung banyak fungsi untuk tidak hanya menangani berbagai macam keperluan program tetapi juga untuk mengatur berbagai konfigurasi perkakasan. Mereka juga menangani memori, caching, bus sistem, segmentasi, dan fungsi IO, menjadikannya jack semua perdagangan.
GPU adalah sebaliknya - ia mengandungi banyak pemproses individu yang tertumpu pada fungsi matematik yang sangat sederhana. Oleh kerana itu, mereka memproses tugas berkali-kali lebih cepat daripada CPU. Dengan mengkhususkan diri dalam fungsi skalar (fungsi yang memerlukan satu atau lebih input tetapi hanya menghasilkan satu output), mereka mencapai prestasi yang ekstrem dengan kos pengkhususan yang ekstrem.
Contoh Kod
Dalam kod contoh, kami menambah vektor bersama-sama. Saya telah menambahkan versi CPU dan GPU kod untuk perbandingan kelajuan.
gpu-contoh.cpp kandungan di bawah:
#sertakan
#sertakan
#sertakan
#sertakan
#sertakan
mesin taipJam::chrono::high_resolution_clockJam;
#tentukan ITER 65535
// Versi CPU fungsi tambah vektor
batalvector_add_cpu(int *ke,int *b,int *c,intn) {
inti;
// Tambahkan elemen vektor a dan b ke vektor c
untuk (i= 0;i<n; ++i) {
c[i] =ke[i] +b[i];
}
}
// Versi GPU fungsi tambah vektor
__ global__batalvektor_add_gpu(int *gpu_a,int *gpu_b,int *gpu_c,intn) {
inti=utasIdx.x;
// Tidak diperlukan gelung kerana masa berjalan CUDA
// akan memuatkan kali ITER ini
gpu_c[i] =gpu_a[i] +gpu_b[i];
}
intutama() {
int *ke,*b,*c;
int *gpu_a,*gpu_b,*gpu_c;
ke= (int *)malloc(ITER* saiz(int));
b= (int *)malloc(ITER* saiz(int));
c= (int *)malloc(ITER* saiz(int));
// Kami memerlukan pemboleh ubah yang dapat diakses oleh GPU,
// jadi cudaMallocManaged menyediakan ini
cudaMallocMenguruskan(&gpu_a, ITER* saiz(int));
cudaMallocMenguruskan(&gpu_b, ITER* saiz(int));
cudaMallocMenguruskan(&gpu_c, ITER* saiz(int));
untuk (inti= 0;i<ITER; ++i) {
ke[i] =i;
b[i] =i;
c[i] =i;
}
// Panggil fungsi CPU dan waktunya
keretacpu_start=Jam::sekarang();
vector_add_cpu(a, b, c, ITER);
keretacpu_end=Jam::sekarang();
Jam::kos << 'vector_add_cpu:'
<<Jam::chrono::tempoh_ siaran<Jam::chrono::nanodetik>(cpu_end-cpu_start).mengira()
<< 'nanodetik. n';
// Panggil fungsi GPU dan buat masa
// Braket sudut tiga adalah lanjutan jangka masa CUDA yang membolehkan
// parameter panggilan kernel CUDA yang akan dilalui.
// Dalam contoh ini, kita melewati satu blok utas dengan utas ITER.
keretagpu_start=Jam::sekarang();
vektor_add_gpu<<<1, ITER>>> (gpu_a, gpu_b, gpu_c, ITER);
cudaDeviceSynchronize();
keretagpu_end=Jam::sekarang();
Jam::kos << 'vector_add_gpu:'
<<Jam::chrono::tempoh_ siaran<Jam::chrono::nanodetik>(gpu_end-gpu_start).mengira()
<< 'nanodetik. n';
// Bebaskan peruntukan memori berdasarkan fungsi GPU
cudaFree(ke);
cudaFree(b);
cudaFree(c);
// Bebaskan peruntukan memori berdasarkan fungsi CPU
percuma(ke);
percuma(b);
percuma(c);
kembali 0;
}
Makefile kandungan di bawah:
INC= -Saya/usr/tempatan/keajaiban/merangkumiNVCC=/usr/tempatan/keajaiban/saya/nvcc
NVCC_OPT= -std = c ++sebelas
semua:
$(NVCC)$(NVCC_OPT)gpu-contoh.cpp-ataugpu-contoh
bersih:
-rm -fgpu-contoh
Untuk menjalankan contohnya, kumpulkan:
membuatKemudian jalankan program:
./gpu-contohSeperti yang anda lihat, versi CPU (vector_add_cpu) berjalan jauh lebih lambat daripada versi GPU (vector_add_gpu).
Sekiranya tidak, anda mungkin perlu menyesuaikan definisi ITER dalam gpu-example.cu ke nombor yang lebih tinggi. Ini disebabkan masa penyediaan GPU lebih lama daripada beberapa gelung intensif CPU yang lebih kecil. Saya mendapati 65535 berfungsi dengan baik pada mesin saya, tetapi jarak tempuh anda mungkin berbeza. Namun, setelah anda membersihkan ambang ini, GPU secara dramatik lebih cepat daripada CPU.
Kesimpulannya
Saya harap anda banyak belajar dari pengenalan kami dalam pengaturcaraan GPU dengan C ++. Contoh di atas tidak mencapai banyak hasil, tetapi konsep yang ditunjukkan memberikan kerangka yang boleh anda gunakan untuk memasukkan idea anda untuk melepaskan kekuatan GPU anda.