2012-12-07 9 views
6

BS_x * BS_Y konuları tarafından paylaşılan bellekte içeriklerin taşınmasını (BS_X + 1) * (BS_Y + 1) global bellek konumlarını okumak istiyorum ve aşağıdaki kodu geliştirdim. Benim anlayışındaCUDA çekirdeğimin bellek erişim birleştirmeyi analiz etme

int i  = threadIdx.x; 
int j  = threadIdx.y; 
int idx  = blockIdx.x*BLOCK_SIZE_X + threadIdx.x; 
int idy  = blockIdx.y*BLOCK_SIZE_Y + threadIdx.y; 

int index1 = j*BLOCK_SIZE_Y+i; 

int i1  = (index1)%(BLOCK_SIZE_X+1); 
int j1  = (index1)/(BLOCK_SIZE_Y+1); 

int i2  = (BLOCK_SIZE_X*BLOCK_SIZE_Y+index1)%(BLOCK_SIZE_X+1); 
int j2  = (BLOCK_SIZE_X*BLOCK_SIZE_Y+index1)/(BLOCK_SIZE_Y+1); 

__shared__ double Ezx_h_shared_ext[BLOCK_SIZE_X+1][BLOCK_SIZE_Y+1];  

Ezx_h_shared_ext[i1][j1]=Ezx_h[(blockIdx.y*BLOCK_SIZE_Y+j1)*xdim+(blockIdx.x*BLOCK_SIZE_X+i1)]; 

if ((i2<(BLOCK_SIZE_X+1))&&(j2<(BLOCK_SIZE_Y+1))) 
Ezx_h_shared_ext[i2][j2]=Ezx_h[(blockIdx.y*BLOCK_SIZE_Y+j2)*xdim+(blockIdx.x*BLOCK_SIZE_X+i2)]; 

, birleştirici ardışık bellek paralel eşdeğer sıralı işleme okur olduğunu. Küresel bellek erişimleri birleştirilirse şimdi nasıl tespit edebilirim? (I1, j1) 'den (i2, j2)' ye bir indeks atlamasının olduğunu belirtiyorum. Şimdiden teşekkürler.

cevap

5

Kodunuzun bellek erişimlerini elle yazılmış bir birleştirici analizörle değerlendirdim. Değerlendirme, kodun birleştirmeyi daha az istismar ettiğini göstermektedir. Burada yararlı olabilecek kaynaştırma analizörü:

#include <stdio.h> 
#include <malloc.h> 

typedef struct dim3_t{ 
    int x; 
    int y; 
} dim3; 


// KERNEL LAUNCH PARAMETERS 
#define GRIDDIMX 4 
#define GRIDDIMY 4 
#define BLOCKDIMX 16 
#define BLOCKDIMY 16 


// ARCHITECTURE DEPENDENT 
// number of threads aggregated for coalescing 
#define COALESCINGWIDTH 32 
// number of bytes in one coalesced transaction 
#define CACHEBLOCKSIZE 128 
#define CACHE_BLOCK_ADDR(addr,size) (addr*size)&(~(CACHEBLOCKSIZE-1)) 


int main(){ 
    // fixed dim3 variables 
    // grid and block size 
    dim3 blockDim,gridDim; 
    blockDim.x=BLOCKDIMX; 
    blockDim.y=BLOCKDIMY; 
    gridDim.x=GRIDDIMX; 
    gridDim.y=GRIDDIMY; 

    // counters 
    int unq_accesses=0; 
    int *unq_addr=(int*)malloc(sizeof(int)*COALESCINGWIDTH); 
    int total_unq_accesses=0; 

    // iter over total number of threads 
    // and count the number of memory requests (the coalesced requests) 
    int I, II, III; 
    for(I=0; I<GRIDDIMX*GRIDDIMY; I++){ 
     dim3 blockIdx; 
     blockIdx.x = I%GRIDDIMX; 
     blockIdx.y = I/GRIDDIMX; 
     for(II=0; II<BLOCKDIMX*BLOCKDIMY; II++){ 
      if(II%COALESCINGWIDTH==0){ 
       // new coalescing bunch 
       total_unq_accesses+=unq_accesses; 
       unq_accesses=0; 
      } 
      dim3 threadIdx; 
      threadIdx.x=II%BLOCKDIMX; 
      threadIdx.y=II/BLOCKDIMX; 

      //////////////////////////////////////////////////////// 
      // Change this section to evaluate different accesses // 
      //////////////////////////////////////////////////////// 
      // do your indexing here 
      #define BLOCK_SIZE_X BLOCKDIMX 
      #define BLOCK_SIZE_Y BLOCKDIMY 
      #define xdim 32 
      int i  = threadIdx.x; 
      int j  = threadIdx.y; 
      int idx  = blockIdx.x*BLOCK_SIZE_X + threadIdx.x; 
      int idy  = blockIdx.y*BLOCK_SIZE_Y + threadIdx.y; 

      int index1 = j*BLOCK_SIZE_Y+i; 

      int i1  = (index1)%(BLOCK_SIZE_X+1); 
      int j1  = (index1)/(BLOCK_SIZE_Y+1); 

      int i2  = (BLOCK_SIZE_X*BLOCK_SIZE_Y+index1)%(BLOCK_SIZE_X+1); 
      int j2  = (BLOCK_SIZE_X*BLOCK_SIZE_Y+index1)/(BLOCK_SIZE_Y+1); 
      // calculate the accessed location and offset here 
      // change the line "Ezx_h[(blockIdx.y*BLOCK_SIZE_Y+j1)*xdim+(blockIdx.x*BLOCK_SIZE_X+i1)];" to 
      int addr = (blockIdx.y*BLOCK_SIZE_Y+j1)*xdim+(blockIdx.x*BLOCK_SIZE_X+i1); 
      int size = sizeof(double); 
      ////////////////////////// 
      // End of modifications // 
      ////////////////////////// 

      printf("tid (%d,%d) from blockid (%d,%d) accessing to block %d\n",threadIdx.x,threadIdx.y,blockIdx.x,blockIdx.y,CACHE_BLOCK_ADDR(addr,size)); 
      // check whether it can be merged with existing requests or not 
      short merged=0; 
      for(III=0; III<unq_accesses; III++){ 
       if(CACHE_BLOCK_ADDR(addr,size)==CACHE_BLOCK_ADDR(unq_addr[III],size)){ 
        merged=1; 
        break; 
       } 
      } 
      if(!merged){ 
       // new cache block accessed over this coalescing width 
       unq_addr[unq_accesses]=CACHE_BLOCK_ADDR(addr,size); 
       unq_accesses++; 
      } 
     } 
    } 
    printf("%d threads make %d memory transactions\n",GRIDDIMX*GRIDDIMY*BLOCKDIMX*BLOCKDIMY, total_unq_accesses); 
} 

kod ızgaranın her iş parçacığı için çalışacak ve birleştirici hafıza erişiminin metrik birleşti istekleri, sayısını hesaplar.

Analiz cihazını kullanmak için, kodunuzun indeks hesaplama bölümünü belirtilen bölgede yapıştırın ve bellek erişimini (dizi) 'adres' ve 'boyut' olarak çözün. Zaten indexings olan kodunuz için yaptık:

int i  = threadIdx.x; 
int j  = threadIdx.y; 
int idx  = blockIdx.x*BLOCK_SIZE_X + threadIdx.x; 
int idy  = blockIdx.y*BLOCK_SIZE_Y + threadIdx.y; 

int index1 = j*BLOCK_SIZE_Y+i; 

int i1  = (index1)%(BLOCK_SIZE_X+1); 
int j1  = (index1)/(BLOCK_SIZE_Y+1); 

int i2  = (BLOCK_SIZE_X*BLOCK_SIZE_Y+index1)%(BLOCK_SIZE_X+1); 
int j2  = (BLOCK_SIZE_X*BLOCK_SIZE_Y+index1)/(BLOCK_SIZE_Y+1); 

ve bellek erişimi geçerli:

Ezx_h_shared_ext[i1][j1]=Ezx_h[(blockIdx.y*BLOCK_SIZE_Y+j1)*xdim+(blockIdx.x*BLOCK_SIZE_X+i1)]; 

analizörü 4096 ipler 4064 önbellek bloklarına erişmek bildirir. Gerçek kılavuzunuzun kodunu ve blok boyutunu çalıştırın ve birleştirici davranışı analiz edin.

+0

Oldukça havalı! NVIDIA ayrıca çipteki performans sayaçlarına doğrudan erişim sağlayan bir SDK'ya da sahiptir. https://developer.nvidia.com/nvidia-perfkit –

+0

@RogerDahl Güzel! Bellek birleştirmenin çipte herhangi bir sayacı var mı? – ahmad

+0

Bence birleştirme diğer tezgâhlardan elde edilen şeylerden biridir. Nsight profiler bellek deneyleri hakkında bu kadar bulanıklaşıyor: "Bir çekirdek için bellekle ilgili performans darboğazlarını tanımlamak için bu deneme grubunu seçin. CUDA bellek hiyerarşisinin her bellek alanı için anahtar metrikler toplanıyor, bunlar birleştiriliyor, banka çakışmaları, L1/L2 önbellek isabet oranları ve elde edilen bant genişlikleri. " Perf kit dokümanlarında sayaçları detaylandıran güzel grafikler var. Muhtemelen birleştirmeyi nasıl saymak için kullanılırlar. –

1

visual profiler, işinizi kontrol etmek için mükemmel bir araçtır. İşlevsel olarak doğru bir kod parçası olduktan sonra, görsel profilleyicinin içinden çalıştırın. Örneğin linux üzerinde, bir X oturumunuz olduğunu varsayarak, bir terminal penceresinden nvvp'yi çalıştırın. Daha sonra, uygulama için herhangi bir komut satırı parametresiyle birlikte profil oluşturmanızı isteyen bir sihirbaz verilecektir.

Daha sonra, profiler istatistikleri toplamak için uygulamanızın temel çalıştırmasını yapar. Ayrıca, daha gelişmiş istatistik topluluğunu (addtional running gerektiren) de seçebilirsiniz ve bunlardan biri bellek kullanım istatistikleri olacaktır. Bellek kullanımını zirveye oran olarak rapor edecek ve dikkatinizi çeken düşük kullanım olarak gördükleri şey için uyarıları da işaretleyecektir.

% 50'nin üzerinde bir kullanım numaranız varsa, uygulamanız muhtemelen beklediğiniz gibi çalışır. Düşük bir numaranız varsa, muhtemelen bazı birleştirici ayrıntıları kaçırdınız. Hafıza okumaları ve hafıza yazmaları için ayrı ayrı istatistikleri rapor edecektir. % 100 veya ona yakın olmak için, aynı zamanda, birleştirilmiş okuma ve yazışmaların 128 bayt sınırında yazıldığından emin olmanız gerekir.

Bu durumlarda yaygın bir hata, threadIdx.y tabanlı değişkeni en hızlı değişen dizin olarak kullanmaktır. Bu hatayı yaptığınız görünmüyor. Örneğin. Bu sık sık C olarak düşündüğümüzden beri shared[threadIdx.x][threadIdx.y] yapmak için sık rastlanan bir hatadır. Ancak ipler ilk önce x ekseninde gruplandırılmıştır, bu yüzden shared[threadIdx.y][threadIdx.x] veya benzeri bir şey kullanmak istiyoruz. Bu hatayı yaparsanız, kodunuz hala işlevsel olarak doğru olabilir, ancak profilerdeki düşük yüzde kullanım rakamlarına sahip olacaksınız, örneğin% 12 veya% 3 gibi.

Ve daha önce de belirtildiği gibi,% 50'nin üzerinde ve% 100'ün üzerine çıkmak için, yalnızca tüm iş parçacığınızın isteklerinin bitişik olmasını değil, bunların 128B sınırında hizalanmasını sağlamanız gerekir. L1/L2 önbellekleri nedeniyle, bunlar zor ve hızlı kurallar değil, kurallar. Önbellekler, bazı hataları bir dereceye kadar azaltabilir.

+0

"Kullanım" ile ne demek istiyorsun? Tüm önbelleğe alınmış bellek, genel bellekten aktarıldığından tam olarak yararlanır mı? Teşekkürler. – JackOLantern

+0

Doğru. Bir hafıza işlemi örneğin bir okuma talebi tarafından tetiklendiğinde, normal olarak tam 128 bayt bellekten alınır. Eğer warp'im sadece bir 32 bitlik bir miktara ihtiyaç duyuyorsa, o zaman sadece bu 128 byte'lık 4'ü kullanacağım. Eğer tüm okuma aktivitem böyle olsaydı, 4/128 =% 3,125'lik bir kullanım yüzdesi görecektim. Fakat bunun yerine, her çözgüdeki 32 iş parçacığının tümü aynı 128 bayt bloğundan aynı anda bitişik 32 bitlik bir değer istiyorsa (a * birleştirilmiş * erişim) daha sonra benim kullanımım% 100 olacak, bu da ideal. –

2

GPU'lar geliştikçe, birleştirilen erişim sağlama gereksinimleri daha az kısıtlayıcı hale geldi. Birleştirilmiş geçişlerin açıklamanız daha önceki GPU mimarileri için daha yeni olanlardan daha doğrudur. Özellikle, Fermi (hesaplama kapasitesi 2.0) gereksinimleri önemli ölçüde gevşetmiştir. Fermi ve daha sonra, bellek konumlarına art arda erişmek önemli değildir. Bunun yerine, odak, belleğe erişmeye mümkün olduğunca az sayıda bellek işlemi ile kaymıştır. Fermi'de, global bellek işlemleri 128 bayt genişliğindedir. Dolayısıyla, bir warp'teki 32 iş parçacığı bir yük veya mağaza gerçekleştiren bir talimat verdiğinde, çözgüdeki tüm iş parçacıklarına hizmet vermek için 128 baytlık işlemler planlanacaktır. Performans, kaç işlemin gerekli olduğuna bağlıdır. Tüm iş parçacıkları 128 bayt ile hizalanmış 128 baytlık bir alandaki değerlere erişirse, tek bir işlem gereklidir. Tüm iş parçacıkları farklı 128 bayt alanlarda değerlere erişirse, 32 işlem gerekli olacaktır. Bir warp'de tek bir talimat için taleplerin yerine getirilmesi için en kötü durum senaryosu olurdu.

İsteklere hizmet vermek için kaç işlem gerektiğine ilişkin ortalamayı belirlemek için CUDA profilleyicilerinden birini kullanırsınız. Sayı mümkün olduğunca 1'e kadar olmalıdır.Daha yüksek sayılar, çekirdeğinizde bellek erişimini optimize etmek için fırsatlar olup olmadığını görmeniz gerektiği anlamına gelir.

+0

Teşekkürler. Ahmad'ın koduna göre, 4096 iş parçacığı 4064 işlem yapar. Daha sonra kodumun oldukça verimsiz olduğu sonucuna varırdım. Ben haklı mıyım – JackOLantern

+0

@ user1886641 Her 16 parçacığın verisi 128 baytlık olduğundan, her çözgü ideal olarak 2 istek göndermelidir. Kodunuz için ideal durum (4096/32) * 2 = 256 istek göndermektir. – ahmad

İlgili konular