i have been studying about cuda.
In CUDA Programming guide, shared memory access time is faster than Global memory time.
so, i made code that perform matrix multiplication.
here is my code. version1 used global memory, version2 used shared memory
my gpu is tesla c2070
cuda sdk version 4.2
main code
#define Matrix_Width 9216
#define Matrix_Divide 4
#define Tile_Width 32
#define Ce_Size 4096
#if Matrix_Width == 9216
#define Matrix_Size 9216*9216
#elif Matrix_Width == 12800
#define Matrix_Size 12800*12800
#elif Matrix_Width == 15872
#define Matrix_Size 15872*15872
#elif Matrix_Width == 18432
#define Matrix_Size 18432*18432
#endif
float* H_Input1 = (float*)malloc( sizeof(float) * Matrix_Size );
float* H_Input2 = (float*)malloc( sizeof(float) * Matrix_Size );
float* H_Output = (float*)malloc( sizeof(float) * Matrix_Size );
for( int i=0 ; i < Matrix_Size ; i++ ){
H_Input1[i] = 1.0f;
H_Input2[i] = 1.0f;
}
memset( H_Output, 0 , sizeof(float) * Matrix_Size );
float* D_Input1;
float* D_Input2;
float* D_Output;
cudaMalloc( (void**)&D_Input1, sizeof(float) * Matrix_Size );
cudaMalloc( (void**)&D_Input2, sizeof(float) * Matrix_Size );
cudaMalloc( (void**)&D_Output, sizeof(float) * Matrix_Size );
cudaMemcpy( D_Input1, H_Input1, sizeof(float) * Matrix_Size, cudaMemcpyHostToDevice );
cudaMemcpy( D_Input2, H_Input2, sizeof(float) * Matrix_Size, cudaMemcpyHostToDevice );
cudaMemcpy( D_Output, H_Output, sizeof(float) * Matrix_Size, cudaMemcpyHostToDevice );
event_pair Event;
start_timer( &Event );
dim3 dimGrid( Matrix_Width/Matrix_Divide/Tile_Width, Matrix_Width/Matrix_Divide/Tile_Width, 1 );
dim3 dimBlock( Tile_Width, Tile_Width, 1 );
kernel_global<< dimGrid, dimBlock>>>( D_Input1, D_Input2, D_Output );
stop_timer( &Event, "1GB mMemory Test\n" );
cudaMemcpy( H_Output, D_Output, sizeof(float) * Matrix_Size, cudaMemcpyDeviceToHost );
kernel version1
__global__ void kernel_global( float* Input1, float* Input2, float* Output ){
for( int i = 0 ; i < Matrix_Divide ; i++ ){
for( int j = 0 ; j < Matrix_Divide ; j++ ){
float Sum = 0;
int Row = (i * (Matrix_Width/Matrix_Divide)) + (blockIdx.y * Tile_Width) + threadIdx.y;
int Col = (j * (Matrix_Width/Matrix_Divide)) + (blockIdx.x * Tile_Width) + threadIdx.x;
for( int k = 0 ; k < Matrix_Width ; k++ ){
Sum += Input1[ Row * Matrix_Width + k ] * Input2[ k * Matrix_Width + Col ];
}
Output[ Row*Matrix_Width+Col] = Sum;
}
}
}
kernel version2
__global__ void kernel_shared( float* Input1, float* Input2, float* Output ){
__shared__ float Input1_s[Tile_Width][Tile_Width];
__shared__ float Input2_s[Tile_Width][Tile_Width];
int Bx = blockIdx.x;
int By = blockIdx.y;
int Tx = threadIdx.x;
int Ty = threadIdx.y;
for( int i = 0 ; i < Matrix_Divide ; i++ ){
for( int j = 0 ; j < Matrix_Divide ; j++ ){
float Sum = 0;
int Row = (i * (Matrix_Width/Matrix_Divide)) + (By * Tile_Width) + Ty;
int Col = (j * (Matrix_Width/Matrix_Divide)) + (Bx * Tile_Width) + Tx;
for( int m = 0 ; m < Matrix_Width/Tile_Width ; m++ ){
Input1_s[Ty][Tx] = Input1[ Row * Matrix_Width + ( m * Tile_Width + Tx ) ];
Input2_s[Ty][Tx] = Input2[ ( m * Tile_Width + Ty ) * Matrix_Width + Col ];
__syncthreads();
for( int k = 0 ; k < Tile_Width; k++ ){
Sum += Input1_s[Ty][k] * Input2_s[k][Tx];
}
__syncthreads();
}
Output[ Row*Matrix_Width+Col] = Sum;
}
}
}
this code made matrix that Width = 9216
it can`t calculate at a time. because max count of block is 65535 and threads 1024
so i divided matrix width using 4 so matrix is divide 16 chunks .
1 chunk can calculate at a time.
so i used loop that is loopcount is 16 ( i * j = 16 )
and a chunk is divided block and thread.. ( tile_width = 32 )
test result is so strange.
version 1 took 90sec
version 2 took 130sec
i can`t understand this result
i think that shared memory element is resued in tile…
why version1 faster than version2?
best regards!!
The Tesla C2070 is a compute capability 2.0 device that caches global memory accesses. So in both cases the inner loop operates (apart from the first iteration) in on-chip memory.
The only difference is that the additional code in the second version to load data into shared memory costs extra time, while in the first version this work is done in hardware by the cache logic.
Manually caching data in shared memory is only worthwhile on compute capability 1.x devices, or if you have a better idea of which data is getting reused than the LRU (least recently used) logic of the hardware cache.