C++ ile GPU Programlama

Gpu Programming With C



Bu kılavuzda, C++ ile GPU programlamanın gücünü keşfedeceğiz. Geliştiriciler, C++ ile inanılmaz bir performans bekleyebilir ve GPU'nun olağanüstü gücüne düşük seviyeli bir dille erişmek, şu anda mevcut olan en hızlı hesaplamalardan bazılarını sağlayabilir.

Gereksinimler

Linux'un modern bir sürümünü çalıştırabilen herhangi bir makine bir C++ derleyicisini destekleyebilirken, bu alıştırmayı takip etmek için NVIDIA tabanlı bir GPU'ya ihtiyacınız olacak. GPU'nuz yoksa, Amazon Web Services'de veya seçtiğiniz başka bir bulut sağlayıcısında GPU ile çalışan bir bulut sunucusu başlatabilirsiniz.







Fiziksel bir makine seçerseniz, lütfen NVIDIA'ya özel sürücülerin kurulu olduğundan emin olun. Bunun için talimatları burada bulabilirsiniz: https://linuxhint.com/install-nvidia-drivers-linux/



Sürücüye ek olarak CUDA araç setine de ihtiyacınız olacak. Bu örnekte Ubuntu 16.04 LTS kullanacağız, ancak çoğu büyük dağıtım için aşağıdaki URL'de indirmeler mevcuttur: https://developer.nvidia.com/cuda-downloads



Ubuntu için .deb tabanlı indirmeyi seçersiniz. İndirilen dosyanın varsayılan olarak bir .deb uzantısı olmaz, bu yüzden sonunda .deb olacak şekilde yeniden adlandırmanızı öneririm. Ardından, şununla yükleyebilirsiniz:





sudo dpkg -benpaket-adı.deb

Muhtemelen bir GPG anahtarı yüklemeniz istenecektir ve eğer öyleyse, bunu yapmak için verilen talimatları izleyin.

Bunu yaptıktan sonra depolarınızı güncelleyin:



sudo apt-get güncellemesi
sudo apt-get installmucizeler-ve

Tamamlandığında, her şeyin düzgün şekilde yüklendiğinden emin olmak için yeniden başlatmayı öneririm.

GPU Geliştirmenin Faydaları

CPU'lar birçok farklı giriş ve çıkışı yönetir ve yalnızca çok çeşitli program gereksinimleriyle ilgilenmek için değil, aynı zamanda değişen donanım yapılandırmalarını yönetmek için de çok çeşitli işlevler içerir. Ayrıca bellek, önbelleğe alma, sistem veriyolu, bölümleme ve IO işlevselliğini de ele alarak onları her türlü işlemin krikosu haline getirirler.

GPU'lar ise tam tersidir - çok basit matematiksel işlevlere odaklanan birçok bireysel işlemci içerirler. Bu nedenle, görevleri CPU'lardan çok daha hızlı işlerler. Skaler işlevlerde uzmanlaşarak (bir veya daha fazla girdi alan ancak yalnızca tek bir çıktı döndüren bir işlev), aşırı uzmanlaşma pahasına olağanüstü performans elde ederler.

Örnek Kod

Örnek kodda vektörleri birlikte ekliyoruz. Hız karşılaştırması için kodun CPU ve GPU sürümünü ekledim.
gpu-example.cpp aşağıdaki içerikler:

#include 'cuda_runtime.h'
#Dahil etmek
#Dahil etmek
#Dahil etmek
#Dahil etmek
#Dahil etmek

typedefsaat::krono::yüksek çözünürlüklü_saatSaat;

#define ITER 65535

// Vektör ekleme fonksiyonunun CPU versiyonu
geçersizvector_add_cpu(int *ile,int *B,int *C,intn) {
intben;

// a ve b vektör öğelerini c vektörüne ekleyin
için (ben= 0;ben<n; ++ben) {
C[ben] =ile[ben] +B[ben];
}
}

// Vektör ekleme fonksiyonunun GPU versiyonu
__küresel__geçersizvector_add_gpu(int *gpu_a,int *gpu_b,int *gpu_c,intn) {
intben=threadIdx.x;
// CUDA çalışma zamanı nedeniyle döngü için gerekli değil
// bu ITER kez işlenecek
gpu_c[ben] =gpu_a[ben] +gpu_b[ben];
}

intana() {

int *ile,*B,*C;
int *gpu_a,*gpu_b,*gpu_c;

ile= (int *)malloc(ÖĞRENCİ* boyutu(int));
B= (int *)malloc(ÖĞRENCİ* boyutu(int));
C= (int *)malloc(ÖĞRENCİ* boyutu(int));

// GPU tarafından erişilebilir değişkenlere ihtiyacımız var,
// yani cudaMallocManaged bunları sağlar
cudaMallocYönetilen(&gpu_a, ITER* boyutu(int));
cudaMallocYönetilen(&gpu_b, ITER* boyutu(int));
cudaMallocYönetilen(&gpu_c, ITER* boyutu(int));

için (intben= 0;ben<ÖĞRENCİ; ++ben) {
ile[ben] =ben;
B[ben] =ben;
C[ben] =ben;
}

// CPU işlevini çağırın ve zamanlayın
Otoişlemci_başlangıcı=Saat::şimdi();
vector_add_cpu(a, b, c, ÖĞRENCİ);
Otocpu_end=Saat::şimdi();
saat::maliyet << 'vector_add_cpu:'
<<saat::krono::süre_yayın<saat::krono::nanosaniye>(cpu_end-işlemci_başlangıcı).saymak()
<< ' nanosaniye. ';

// GPU işlevini çağırın ve zamanlayın
// Üç açılı braketler, izin veren bir CUDA çalışma zamanı uzantısıdır.
// geçirilecek bir CUDA çekirdek çağrısının parametreleri.
// Bu örnekte, ITER evreli bir evre bloğunu geçiyoruz.
Otogpu_start=Saat::şimdi();
vector_add_gpu<<<1, ÖĞRENCİ>>> (gpu_a, gpu_b, gpu_c, ITER);
cudaDeviceSynchronize();
Otogpu_end=Saat::şimdi();
saat::maliyet << 'vector_add_gpu:'
<<saat::krono::süre_yayın<saat::krono::nanosaniye>(gpu_end-gpu_start).saymak()
<< ' nanosaniye. ';

// GPU işlevine dayalı bellek ayırmalarını boşaltın
cudaÜcretsiz(ile);
cudaÜcretsiz(B);
cudaÜcretsiz(C);

// CPU işlevine dayalı bellek ayırmalarını boşaltın
Bedava(ile);
Bedava(B);
Bedava(C);

dönüş 0;
}

makefile aşağıdaki içerikler:

INC=-I/usr/yerel/mucizeler/Dahil etmek
NVCC=/usr/yerel/mucizeler/NS/nvcc
NVCC_OPT=-std=c++on bir

herşey:
$(NVCC)$(NVCC_OPT)gpu-example.cpp-veyagpu örneği

temiz:
-rm -Fgpu örneği

Örneği çalıştırmak için derleyin:

Yapmak

Ardından programı çalıştırın:

./gpu örneği

Gördüğünüz gibi, CPU sürümü (vector_add_cpu), GPU sürümünden (vector_add_gpu) oldukça yavaş çalışıyor.

Değilse, gpu-example.cu içindeki ITER tanımını daha yüksek bir sayıya ayarlamanız gerekebilir. Bunun nedeni, GPU kurulum süresinin bazı küçük CPU yoğun döngülerden daha uzun olmasıdır. 65535'in makinemde iyi çalıştığını buldum, ancak kilometreniz değişebilir. Ancak, bu eşiği bir kez temizlediğinizde GPU, CPU'dan çok daha hızlıdır.

Çözüm

Umarım C++ ile GPU programlamaya girişimizden çok şey öğrenmişsinizdir. Yukarıdaki örnek pek bir şey sağlamaz, ancak gösterilen kavramlar, GPU'nuzun gücünü açığa çıkarmak için fikirlerinizi birleştirmek için kullanabileceğiniz bir çerçeve sağlar.