Pengaturcaraan GPU dengan C ++

Gpu Programming With C



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 'cuda_runtime.h'
#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/merangkumi
NVCC=/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:

membuat

Kemudian jalankan program:

./gpu-contoh

Seperti 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.