Pemrograman GPU dengan C++

Gpu Programming With C

Dalam panduan ini, kita akan mengeksplorasi kekuatan pemrograman GPU dengan C++. Pengembang dapat mengharapkan kinerja yang luar biasa dengan C++, dan mengakses kekuatan fenomenal dari GPU dengan bahasa tingkat rendah dapat menghasilkan beberapa komputasi tercepat yang tersedia saat ini.

Persyaratan

Meskipun mesin apa pun yang mampu menjalankan versi Linux modern dapat mendukung kompiler C++, Anda memerlukan GPU berbasis NVIDIA untuk mengikuti latihan ini. Jika Anda tidak memiliki GPU, Anda dapat menjalankan instans bertenaga GPU di Amazon Web Services atau penyedia cloud lain pilihan Anda.



Jika Anda memilih mesin fisik, pastikan Anda telah menginstal driver berpemilik NVIDIA. Anda dapat menemukan petunjuk untuk ini di sini: https://linuxhint.com/install-nvidia-drivers-linux/



Selain driver, Anda memerlukan toolkit CUDA. Dalam contoh ini, kami akan menggunakan Ubuntu 16.04 LTS, tetapi ada unduhan yang tersedia untuk sebagian besar distribusi utama di URL berikut: https://developer.nvidia.com/cuda-downloads



Untuk Ubuntu, Anda akan memilih unduhan berbasis .deb. File yang diunduh tidak akan memiliki ekstensi .deb secara default, jadi saya sarankan mengganti namanya menjadi .deb di akhir. Kemudian, Anda dapat menginstal dengan:

sudo dpkg -Sayanama-paket.deb

Anda mungkin akan diminta untuk memasang kunci GPG, dan jika demikian, ikuti petunjuk yang diberikan untuk melakukannya.

Setelah Anda selesai melakukannya, perbarui repositori Anda:



sudo pembaruan apt-get
sudo apt-get installkeajaiban-dan

Setelah selesai, saya sarankan untuk me-reboot untuk memastikan semuanya dimuat dengan benar.

Manfaat Pengembangan GPU

CPU menangani banyak input dan output yang berbeda dan berisi berbagai macam fungsi untuk tidak hanya menangani berbagai macam kebutuhan program tetapi juga untuk mengelola berbagai konfigurasi perangkat keras. Mereka juga menangani memori, caching, bus sistem, segmentasi, dan fungsionalitas IO, menjadikannya jack of all trades.

GPU adalah kebalikannya – mereka mengandung banyak prosesor individu yang berfokus pada fungsi matematika yang sangat sederhana. Karena itu, mereka memproses tugas berkali-kali lebih cepat daripada CPU. Dengan berspesialisasi dalam fungsi skalar (fungsi yang mengambil satu atau lebih input tetapi hanya mengembalikan satu output), mereka mencapai kinerja ekstrem dengan biaya spesialisasi ekstrem.

Contoh Kode

Dalam kode contoh, kami menambahkan vektor bersama-sama. Saya telah menambahkan kode versi CPU dan GPU untuk perbandingan kecepatan.
gpu-contoh.cpp isi di bawah ini:

#sertakan 'cuda_runtime.h'
#termasuk
#termasuk
#termasuk
#termasuk
#termasuk

typedefjam::krono::jam_resolusi_tinggiJam;

#menentukan ITER 65535

// Versi CPU dari fungsi penambahan vektor
ruang kosongvector_add_cpu(ke dalam *ke,ke dalam *B,ke dalam *C,ke dalamn) {
ke dalamSaya;

// Tambahkan elemen vektor a dan b ke vektor c
untuk (Saya= 0;Saya<n; ++Saya) {
C[Saya] =ke[Saya] +B[Saya];
}
}

// Versi GPU dari fungsi penambahan vektor
__global__ruang kosongvector_add_gpu(ke dalam *gpu_a,ke dalam *gpu_b,ke dalam *gpu_c,ke dalamn) {
ke dalamSaya=threadIdx.x;
// Tidak diperlukan perulangan karena CUDA runtime
// akan meng-thread iTER ini kali
gpu_c[Saya] =gpu_a[Saya] +gpu_b[Saya];
}

ke dalamutama() {

ke dalam *ke,*B,*C;
ke dalam *gpu_a,*gpu_b,*gpu_c;

ke= (ke dalam *)malloc(ITU* ukuran dari(ke dalam));
B= (ke dalam *)malloc(ITU* ukuran dari(ke dalam));
C= (ke dalam *)malloc(ITU* ukuran dari(ke dalam));

// Kami membutuhkan variabel yang dapat diakses oleh GPU,
// jadi cudaMallocManaged menyediakan ini
cudaMallocDikelola(&gpu_a, ITER* ukuran dari(ke dalam));
cudaMallocDikelola(&gpu_b, ITER* ukuran dari(ke dalam));
cudaMallocDikelola(&gpu_c, ITER* ukuran dari(ke dalam));

untuk (ke dalamSaya= 0;Saya<ITU; ++Saya) {
ke[Saya] =Saya;
B[Saya] =Saya;
C[Saya] =Saya;
}

// Panggil fungsi CPU dan tentukan waktunya
mobilcpu_start=Jam::sekarang();
vector_add_cpu(a, b, c, ITER);
mobilcpu_end=Jam::sekarang();
jam::biaya << 'vector_add_cpu: '
<<jam::krono::durasi_cast<jam::krono::nanodetik>(cpu_end-cpu_start).menghitung()
<< ' nanodetik. ';

// Panggil fungsi GPU dan tentukan waktunya
// Braket sudut tiga adalah ekstensi runtime CUDA yang memungkinkan
// parameter panggilan kernel CUDA yang akan diteruskan.
// Dalam contoh ini, kita melewati satu blok utas dengan utas ITER.
mobilgpu_start=Jam::sekarang();
vector_add_gpu<<<1, ITU>>> (gpu_a, gpu_b, gpu_c, ITER);
cudaDeviceSinkronisasi();
mobilgpu_end=Jam::sekarang();
jam::biaya << 'vector_add_gpu: '
<<jam::krono::durasi_cast<jam::krono::nanodetik>(gpu_end-gpu_start).menghitung()
<< ' nanodetik. ';

// Bebaskan alokasi memori berbasis fungsi GPU
cudaGratis(ke);
cudaGratis(B);
cudaGratis(C);

// Bebaskan alokasi memori berbasis fungsi CPU
Gratis(ke);
Gratis(B);
Gratis(C);

kembali 0;
}

Makefile isi di bawah ini:

INC=-aku/usr/lokal/keajaiban/termasuk
NVCC=/usr/lokal/keajaiban/NS/nvcc
NVCC_OPT=-std=c++sebelas

semua:
$(NVCC)$(NVCC_OPT)gpu-contoh.cpp-ataucontoh gpu

membersihkan:
-rm -Fcontoh gpu

Untuk menjalankan contoh, kompilasi:

membuat

Kemudian jalankan programnya:

./contoh gpu

Seperti yang Anda lihat, versi CPU (vector_add_cpu) berjalan jauh lebih lambat daripada versi GPU (vector_add_gpu).

Jika tidak, Anda mungkin perlu menyesuaikan definisi ITER di gpu-example.cu ke angka yang lebih tinggi. Ini karena waktu penyiapan GPU lebih lama daripada beberapa loop intensif CPU yang lebih kecil. Saya menemukan 65535 bekerja dengan baik di mesin saya, tetapi jarak tempuh Anda mungkin berbeda. Namun, setelah Anda menghapus ambang batas ini, GPU secara dramatis lebih cepat daripada CPU.

Kesimpulan

Saya harap Anda telah belajar banyak dari pengantar kami tentang pemrograman GPU dengan C++. Contoh di atas tidak menghasilkan banyak hal, tetapi konsep yang ditunjukkan menyediakan kerangka kerja yang dapat Anda gunakan untuk menggabungkan ide-ide Anda untuk melepaskan kekuatan GPU Anda.