cuda學習筆記(3)


上篇筆記中記錄了cuda共享內存及線程同步的方法。本篇將介 cuda 常量內存( __ constant __ ) 的特點及使用方法。

1. cuda 常量內存

cuda編程中,如果核函數要用到的數據從開始到結束一直不需要變化,我們可以把這些數據放到cuda常量內存中,以提升讀取數據的速度(有些程序運行速度的瓶頸在內存讀取與交換上)。常量內存有以下特點:

  • 對常量內存的單次讀操作可以廣播到其他的“鄰近(nearby)”線程,這將節約15次讀取操作;
  • 常量內存的數據將緩存起來,因此對於相同地址的連續操作將不會產生額外的內存通信量。
  • 常量內存大小是64k

下面進行解釋:
第一條:首先,我們需要來看看到底什麼是 線程束 ( warp ),在CUDA架構中,線程束是指一個包含32個線程的集合,這個線程集合被“編織在一起”並且以“步調一致(Lockstep)”的形式執行。在程序中的每一行,線程束中的每個線程都將在不同的數據上執行相同的指令。

當處理常量內存時,NVIDIA硬件將把單次內存讀取操作廣播到每個半線程束(Half-Warp)。在半線程束中包含16個線程,即線程束中線程數量的一半。如果在半線程束中的每個線程從常量內存的相同地址上讀取數據,那麼GPU只會產生一次讀取請求並在隨後將數據廣播到每個線程。如果從常量內存中讀取大量數據,那麼這種方式產生的內存流量只是使用全局內存時的1/16。

第二條:不止第一條所述節省讀取數據的內存帶寬。還由於這塊內存的內容是不發生變化的,因此硬件將主動把這個常量數據緩存在GPU上。在第一次從常量內存的某個地址上讀取後,當其他半線程束請求同一個地址時,那麼將命中緩存,這同樣減少了額外的內存流量。

不過常量內存也不能亂用。使用不當可能會對性能產生負面的影響。半線程束廣播功能實際上是一把雙刃劍。雖然當所有16個線程都讀取相同地址時,這個功能可以極大提升性能,但當所有16個線程分別讀取不同的地址時,它實際上會降低性能。因爲這16次不同的讀取操作會被串行化,從而需要16倍的時間來發出請求。但如果從全局內存中讀取,那麼這些請求會同時發出。

2. 常量內存的聲明與使用

定義普通變量時,需要先聲明一個指針,然後通過cudaMalloc()來爲指針分配GPU內存。而當我們聲明常量內存時,需要靜態地指定空間大小:

__constant__ int s[100];

在編譯時要提交固定的大小(如一個數組,提供固定的數組大小)。爲常量賦值時不是再使用cudaMemcpy(),而是使用 cudaMemcpyToSymbol()把數據從主機拷貝到設備GPU中。並且由於沒使用cudaMalloc,所以不需要使用cudaFree()釋放。

下面通過 ray tracing 示例來展示 常量內存的使用:

#include "cuda.h"
#include "../common/book.h"
#include "../common/cpu_bitmap.h"

#define DIM 1024

#define rnd( x ) (x * rand() / RAND_MAX)
#define INF     2e10f

struct Sphere {
    float   r,b,g;
    float   radius;
    float   x,y,z;
    __device__ float hit( float ox, float oy, float *n ) {
        float dx = ox - x;
        float dy = oy - y;
        if (dx*dx + dy*dy < radius*radius) {
            float dz = sqrtf( radius*radius - dx*dx - dy*dy );
            *n = dz / sqrtf( radius * radius );
            return dz + z;
        }
        return -INF;
    }
};
#define SPHERES 20

// !!!!!!!!!!!!!!!
__constant__ Sphere s[SPHERES];

__global__ void kernel( unsigned char *ptr ) {
    // map from threadIdx/BlockIdx to pixel position
    int x = threadIdx.x + blockIdx.x * blockDim.x;
    int y = threadIdx.y + blockIdx.y * blockDim.y;
    int offset = x + y * blockDim.x * gridDim.x;
    float   ox = (x - DIM/2);
    float   oy = (y - DIM/2);

    float   r=0, g=0, b=0;
    float   maxz = -INF;
    for(int i=0; i<SPHERES; i++) {
        float   n;
        float   t = s[i].hit( ox, oy, &n );
        if (t > maxz) {
            float fscale = n;
            r = s[i].r * fscale;
            g = s[i].g * fscale;
            b = s[i].b * fscale;
            maxz = t;
        }
    } 

    ptr[offset*4 + 0] = (int)(r * 255);
    ptr[offset*4 + 1] = (int)(g * 255);
    ptr[offset*4 + 2] = (int)(b * 255);
    ptr[offset*4 + 3] = 255;
}

// globals needed by the update routine
struct DataBlock {
    unsigned char   *dev_bitmap;
};

int main( void ) {
    DataBlock   data;
    // capture the start time
    cudaEvent_t     start, stop;
    HANDLE_ERROR( cudaEventCreate( &start ) );
    HANDLE_ERROR( cudaEventCreate( &stop ) );
    HANDLE_ERROR( cudaEventRecord( start, 0 ) );

    CPUBitmap bitmap( DIM, DIM, &data );
    unsigned char   *dev_bitmap;

    // allocate memory on the GPU for the output bitmap
    HANDLE_ERROR( cudaMalloc( (void**)&dev_bitmap,
                              bitmap.image_size() ) );

    // allocate temp memory, initialize it, copy to constant
    // memory on the GPU, then free our temp memory
    Sphere *temp_s = (Sphere*)malloc( sizeof(Sphere) * SPHERES );
    for (int i=0; i<SPHERES; i++) {
        temp_s[i].r = rnd( 1.0f );
        temp_s[i].g = rnd( 1.0f );
        temp_s[i].b = rnd( 1.0f );
        temp_s[i].x = rnd( 1000.0f ) - 500;
        temp_s[i].y = rnd( 1000.0f ) - 500;
        temp_s[i].z = rnd( 1000.0f ) - 500;
        temp_s[i].radius = rnd( 100.0f ) + 20;
    }
	//!!!!!!!!!!!!!!!!!!!!
    HANDLE_ERROR( cudaMemcpyToSymbol( s, temp_s, 
                                sizeof(Sphere) * SPHERES) );
    free( temp_s );

    // generate a bitmap from our sphere data
    dim3    grids(DIM/16,DIM/16);
    dim3    threads(16,16);
    kernel<<<grids,threads>>>( dev_bitmap );

    // copy our bitmap back from the GPU for display
    HANDLE_ERROR( cudaMemcpy( bitmap.get_ptr(), dev_bitmap,
                              bitmap.image_size(),
                              cudaMemcpyDeviceToHost ) );

    // get stop time, and display the timing results
    HANDLE_ERROR( cudaEventRecord( stop, 0 ) );
    HANDLE_ERROR( cudaEventSynchronize( stop ) );
    float   elapsedTime;
    HANDLE_ERROR( cudaEventElapsedTime( &elapsedTime,
                                        start, stop ) );
    printf( "Time to generate:  %3.1f ms\n", elapsedTime );

    HANDLE_ERROR( cudaEventDestroy( start ) );
    HANDLE_ERROR( cudaEventDestroy( stop ) );

    HANDLE_ERROR( cudaFree( dev_bitmap ) );

    // display
    bitmap.display_and_exit();
}
發佈了27 篇原創文章 · 獲贊 2 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章