cuda編程知識普及


本帖經過多方整理,大多來自各路書籍《GPGPU編程技術》《cuda高性能》
 
1 gridblock都可以用三元向量來表示:
 
  grid的數組元素是block
  block的數組元素是grid
但是1.x計算能力的核心,grid的第三元必須為1.block的X和Y索引最大尺寸為512
 
2 通過__launch_bounds__(maxBlockSize,minBlocksPerMp)來限制每個block中最大的線程數,及每個多處理器上最少被激活的block數
 
3 SM streaming multiprocessor 多流處理器
   SP scalar processor cores 標量處理核心
 
一個Block中的所有線程在一個多處理器上面並發執行。當這個Block的所有線程執行完后,再激活其他等待的Block.一個多處理器上也可以執行多個block。但是一個block卻不能拆分為多個處理器上面執行
 
對於同一個Block里面的線程:
    1 同一個Block里的線程可以被同步
    2 可以共同訪問多處理器里的共享存儲器
 
到2.x為止,多處理器 執行任務時,以32個並行線程為單位,稱為一個wrap。
當以個block到來的時候,會被分成線程號連續的多個wrap,然后多處理器上的SIMT控制器以wrap為單位控制調度線程。所以block中的線程數要是以32的整數倍來設計,就不會出現空閑的SP。組織WARP的時候,從線程號最小的開始
 
4 各個存儲器存儲位置及作用 
 
5 寄存器放在SP中,如果溢出,會被放在設備處理器上面,發生嚴重滯后,影響性能。
1.0   4KB 
2.0   16kb

 

 共享存儲器位於SM中,大約兩個時鍾周期讀寫4B,靜態分配 __shared__ int shared[16];
1.0   16KB 
2.0   48kb
 
6 共享存儲器,是以4個字節為單位的16個存儲器組
 
  bank沖突:半個warp中的多線程訪問的數組元素處於同一個bank時,訪問串行化,發生沖突
  避免沖突:最多的數據類型是int、float等占用4個字節的類型
 
7線程設計
  
float shared=data[base+tid];
base訪問的起始元素下標 tid線程號
  
如果要是char類型,每個元素占1個字節,就會沖突
  
float shared = data[base+4*tid];
 
8 共享存儲器廣播訪問:半個warp線程都訪問一個數據
 
9 補白策略
    shared[tid]=global[tid];
 
    int number = shared[tid*16];
    int nRow = tid/16;
    int nColumn = tid%16;
    shared[nColumn*17+nRow] = global[tid];
 
    int number = shared[17*tid];

  

10 一次性訪問全局存儲器:數據的起始地址應為每個線程訪問數據大小的16倍的整數倍
 
11 主機鎖頁存儲器cudaHostMalloc()分配。
 
  不參與操作系統分頁管理的存儲空間,訪問鎖頁文件不會耗費主機內存分頁管理方面的開銷。不會被操作系統放到硬盤的頁面文件中,因此比訪問普通的主機存儲器更快。
 
 
12 計算能力2.x的GPU上面,每個SM有獨立的一級緩存,有唯一的二級緩存
 
13 異步並發
 
主機上的計算、
設備上的計算、
主機到設備上的傳輸、
設備到主機上的傳輸共同執行
 
14 設備存儲器 類型是DRAM,動態隨機存儲器。使用它最高效的方式就是順序讀取。為了保證順序:
 
__global__ static void sumof(int *pnNumber,int* pnResult,clock_t* pclock_tTime){
    const int tid = threadIdx.x;
    int nSum = 0;
    int i;
    clock_t clock_tStart;
    if(tid == 0) clock_tStart = clock();
 
    for(i = tid;i<DATA_SIZE;i+=THREAD_NUM){
        nSum += pnNumber[i]*pnNumber[i];
}
 
    pnResult[tid] = nSum;
    if(tid == 0)
        *pclock_tTime = clock()-clock_tStart;
}

 

每個block 在1.x的計算能力的GPU下,最多只有512的線程數
 
__global__ static void sumof(int *pnNumber,int* pnResult,clock_t* pclock_tTime){
    const int tid = threadIdx.x;
    const int bid = blockIdx.x;
    int nSum = 0;
    int i;
    clock_t clock_tStart;
    if(tid == 0) pclock_tTime[bid] = clock();
 
    for(i = bid*THREAD_NUM+tid;i<DATA_SIZE;i+=BLOCK_NUM*THREAD_NUM){
        nSum += pnNumber[i]*pnNumber[i];
}
 
    pnResult[bid*THREAD_NUM+tid] = nSum;
 
    if(tid == 0)
        *pclock_tTime[bid+BLOCK_NUM] = clock();
}

 

15 用縮減樹避免bank沖突:
 
  bank沖突指的是,一個warp內的線程同時訪問一個bank列,導致串行讀取數據
 
    noffset = THREAD_NUM/2;
    while(noffset > 0){
        if(tid < offset)
            nshared[tid] += nshared[tid+noffset];
    }
    noffset >>= 1;
 
    __syncthreads();

 

16 CPU有強大的分支預測、程序堆棧、循環優化等針對控制采取的復雜邏輯。
    GPU相對簡單,適合處理順序的,單一的,少循環,少跳轉的語句。
 
17  #progma unroll 5下面的程序循環5次
 
18 cuda中的同步
 
1》__syncthreads()同步
 
  同一個warp內的線程總是被一同激活且一同被分配任務,因此不需要同步。因此最好把需要同步的線程放在同一個warp內,這樣就減少了__syncthreads()的指令
 
2》__threadfence() __threadfence_block()同步
 
  前者針對grid的所有線程,后者針對block內的所有線程。告知線程,全局存儲器或共享存儲器已經被改變
 
3》cudaThreadSynchronize() 主機與設備間的同步
 
  在主機程序里同步線程。該函數以上的設備線程完成后,控制權才交給cpu
 
4》volatile關鍵字
 
  使用這個關鍵字定義數組,設備會知道這個數組隨時都會改變,就會自動重新讀取數組(但是不能保證線程間讀取的數據一致)
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM