2011-10-12 26 views
6

Dưới đây là kết quả của Tính trực quan Profiler cho kernel của tôi trên GT 440:Cải thiện hiệu suất hạt nhân bằng cách tăng số người dùng?

  • Kernel chi tiết: kích thước lưới: [100 1 1], Khối kích thước: [256 1 1]
  • Đăng ký Ratio: 0,84375 (27648/32768) [35 thanh ghi cho mỗi thread]
  • Shared Memory Ratio: 0,336914 (16560/49152) [5520 byte cho mỗi Khối]
  • Blocks hoạt động cho mỗi SM: 3 (khối hoạt động tối đa cho mỗi SM: 8)
  • đề hoạt động cho mỗi SM: 768 (đề tối đa hoạt động cho mỗi SM: 1536)
  • tiềm năng lấp đầy: 0,5 (24/48)
  • Occupancy yếu tố hạn chế: Thanh ghi

Xin vui lòng, chú ý của mình vào đạn được in đậm. Thời gian thực hiện hạt nhân là 121195 us.

Tôi đã giảm một số thanh ghi trên mỗi luồng bằng cách di chuyển một số biến cục bộ sang bộ nhớ dùng chung. Compute trực quan Profiler đầu ra trở thành:

  • Kernel chi tiết: kích thước lưới: [100 1 1], Khối kích thước: [256 1 1]
  • Đăng ký Ratio: 1 (32768/32768) [30 thanh ghi mỗi thread]
  • Shared Memory Ratio: 0,451823 (22208/49152) [5552 byte cho mỗi khối]
  • Blocks hoạt động cho mỗi SM: 4 (khối hoạt động tối đa cho mỗi SM: 8)
  • đề hoạt động cho mỗi SM: 1024 (Chủ đề hoạt động tối đa cho mỗi SM: 153 6)
  • tiềm năng Occupancy: 0,666667 (32/48)
  • Occupancy yếu tố hạn chế: Thanh ghi

Do đó, bây giờ 4 khối đồng thời thực hiện trên một SM duy nhất so với 3 khối trong các phiên bản trước. Tuy nhiên, thời gian thực hiện là 115756 us, gần như giống nhau! Tại sao? Không phải là các khối hoàn toàn độc lập được thực hiện trên các lõi CUDA khác nhau?

Trả lời

14

Bạn hoàn toàn giả định rằng tỷ lệ lấp đầy cao hơn sẽ tự động chuyển thành hiệu suất cao hơn. Đó là thường không phải là trường hợp.

Kiến trúc NVIDIA cần một số lượng warp hoạt động nhất định trên mỗi MP để ẩn độ trễ của đường dẫn hướng dẫn của GPU. Trên thẻ dựa trên Fermi của bạn, yêu cầu đó chuyển thành mức tối thiểu khoảng 30%. Nhắm đến những người có công suất cao hơn mức tối thiểu đó sẽ không nhất thiết dẫn đến thông lượng cao hơn, vì nút cổ chai trễ có thể đã chuyển sang một phần khác của GPU. GPU cấp nhập của bạn không có nhiều băng thông bộ nhớ và có thể 3 khối trên mỗi MP đủ để làm cho bạn giới hạn băng thông bộ nhớ mã, trong trường hợp này, số lượng khối sẽ không ảnh hưởng đến hiệu suất (nó thậm chí có thể đi xuống vì sự gia tăng bộ nhớ điều khiển bộ nhớ và bộ nhớ cache nhớ). Hơn nữa, bạn nói rằng bạn đã làm biến các biến thành bộ nhớ dùng chung để giảm số lượng chân đăng ký của hạt nhân.Trên Fermi, bộ nhớ chia sẻ chỉ có khoảng 1000 Gb/s băng thông, so với khoảng 8000 Gb/s cho các thanh ghi (xem liên kết bên dưới để biết kết quả của microbenchmark). Vì vậy, bạn đã chuyển các biến sang bộ nhớ chậm hơn, điều này cũng có thể có tác động tiêu cực đến hiệu suất, bù lại bất kỳ lợi ích nào có khả năng chiếm dụng cao.

Nếu bạn chưa từng xem, tôi khuyên bạn nên giới thiệu bài trình bày của Vasily Volkov từ GTC 2010 "Hiệu suất tốt hơn ở phòng thấp hơn" (pdf). Dưới đây là nó cho thấy cách khai thác song song mức độ hướng dẫn có thể tăng thông lượng GPU đến mức rất cao ở mức rất thấp, chiếm dụng.

+1

Câu trả lời hay. Sự chiếm đóng chỉ là một mối quan tâm nghiêm trọng để ẩn độ trễ truy cập bộ nhớ toàn cầu; đối với các luồng tính toán bị ràng buộc, một vài chuỗi hoạt động trên mỗi SP là đủ. Đó có phải là sự hiểu biết của bạn không? – Patrick87

+0

Tôi không thực sự nghĩ như vậy, Patrick. Điều đó không đúng đối với tất cả các loại hạt nhân. Đối với hạt nhân tính toán, khả năng chứa cao hơn có thể vẫn tăng hiệu suất. Làm thế nào nhiều warps hoạt động cần thiết để ẩn độ trễ số học không phải là đơn giản để nói. Nó phụ thuộc vào loại hoạt động và cách chúng xen kẽ nhau. – Zk1001

2

talonmies đã trả lời câu hỏi của bạn, vì vậy tôi chỉ muốn chia sẻ mã lấy cảm hứng từ phần đầu tiên của bài thuyết trình của V. Volkov được đề cập trong câu trả lời ở trên.

Đây là mã:

#include<stdio.h> 

#define N_ITERATIONS 8192 

//#define DEBUG 

/********************/ 
/* CUDA ERROR CHECK */ 
/********************/ 
#define gpuErrchk(ans) { gpuAssert((ans), __FILE__, __LINE__); } 
inline void gpuAssert(cudaError_t code, char *file, int line, bool abort=true) 
{ 
    if (code != cudaSuccess) 
    { 
     fprintf(stderr,"GPUassert: %s %s %d\n", cudaGetErrorString(code), file, line); 
     if (abort) exit(code); 
    } 
} 

/********************************************************/ 
/* KERNEL0 - NO INSTRUCTION LEVEL PARALLELISM (ILP = 0) */ 
/********************************************************/ 
__global__ void kernel0(int *d_a, int *d_b, int *d_c, unsigned int N) { 

    const int tid = threadIdx.x + blockIdx.x * blockDim.x ; 

    if (tid < N) { 

     int a = d_a[tid]; 
     int b = d_b[tid]; 
     int c = d_c[tid]; 

     for(unsigned int i = 0; i < N_ITERATIONS; i++) { 
      a = a * b + c; 
     } 

     d_a[tid] = a; 
    } 

} 

/*****************************************************/ 
/* KERNEL1 - INSTRUCTION LEVEL PARALLELISM (ILP = 2) */ 
/*****************************************************/ 
__global__ void kernel1(int *d_a, int *d_b, int *d_c, unsigned int N) { 

    const int tid = threadIdx.x + blockIdx.x * blockDim.x; 

    if (tid < N/2) { 

     int a1 = d_a[tid]; 
     int b1 = d_b[tid]; 
     int c1 = d_c[tid]; 

     int a2 = d_a[tid+N/2]; 
     int b2 = d_b[tid+N/2]; 
     int c2 = d_c[tid+N/2]; 

     for(unsigned int i = 0; i < N_ITERATIONS; i++) { 
      a1 = a1 * b1 + c1; 
      a2 = a2 * b2 + c2; 
     } 

     d_a[tid]  = a1; 
     d_a[tid+N/2] = a2; 
    } 

} 

/*****************************************************/ 
/* KERNEL2 - INSTRUCTION LEVEL PARALLELISM (ILP = 4) */ 
/*****************************************************/ 
__global__ void kernel2(int *d_a, int *d_b, int *d_c, unsigned int N) { 

    const int tid = threadIdx.x + blockIdx.x * blockDim.x; 

    if (tid < N/4) { 

     int a1 = d_a[tid]; 
     int b1 = d_b[tid]; 
     int c1 = d_c[tid]; 

     int a2 = d_a[tid+N/4]; 
     int b2 = d_b[tid+N/4]; 
     int c2 = d_c[tid+N/4]; 

     int a3 = d_a[tid+N/2]; 
     int b3 = d_b[tid+N/2]; 
     int c3 = d_c[tid+N/2]; 

     int a4 = d_a[tid+3*N/4]; 
     int b4 = d_b[tid+3*N/4]; 
     int c4 = d_c[tid+3*N/4]; 

     for(unsigned int i = 0; i < N_ITERATIONS; i++) { 
      a1 = a1 * b1 + c1; 
      a2 = a2 * b2 + c2; 
      a3 = a3 * b3 + c3; 
      a4 = a4 * b4 + c4; 
     } 

     d_a[tid]  = a1; 
     d_a[tid+N/4] = a2; 
     d_a[tid+N/2] = a3; 
     d_a[tid+3*N/4] = a4; 
    } 

} 

/********/ 
/* MAIN */ 
/********/ 
void main() { 

    const int N = 1024; 

    int *h_a    = (int*)malloc(N*sizeof(int)); 
    int *h_a_result_host = (int*)malloc(N*sizeof(int)); 
    int *h_a_result_device = (int*)malloc(N*sizeof(int)); 
    int *h_b    = (int*)malloc(N*sizeof(int)); 
    int *h_c    = (int*)malloc(N*sizeof(int)); 

    for (int i=0; i<N; i++) { 
     h_a[i] = 2; 
     h_b[i] = 1; 
     h_c[i] = 2; 
     h_a_result_host[i] = h_a[i]; 
     for(unsigned int k = 0; k < N_ITERATIONS; k++) { 
      h_a_result_host[i] = h_a_result_host[i] * h_b[i] + h_c[i]; 
     } 
    } 

    int *d_a; gpuErrchk(cudaMalloc((void**)&d_a, N*sizeof(int))); 
    int *d_b; gpuErrchk(cudaMalloc((void**)&d_b, N*sizeof(int))); 
    int *d_c; gpuErrchk(cudaMalloc((void**)&d_c, N*sizeof(int))); 

    gpuErrchk(cudaMemcpy(d_a, h_a, N*sizeof(int), cudaMemcpyHostToDevice)); 
    gpuErrchk(cudaMemcpy(d_b, h_b, N*sizeof(int), cudaMemcpyHostToDevice)); 
    gpuErrchk(cudaMemcpy(d_c, h_c, N*sizeof(int), cudaMemcpyHostToDevice)); 

    // --- Creating events for timing 
    float time; 
    cudaEvent_t start, stop; 
    cudaEventCreate(&start); 
    cudaEventCreate(&stop); 

    /***********/ 
    /* KERNEL0 */ 
    /***********/ 
    cudaEventRecord(start, 0); 
    kernel0<<<1, N>>>(d_a, d_b, d_c, N); 
#ifdef DEBUG 
    gpuErrchk(cudaPeekAtLastError()); 
    gpuErrchk(cudaDeviceSynchronize()); 
#endif 
    cudaEventRecord(stop, 0); 
    cudaEventSynchronize(stop); 
    cudaEventElapsedTime(&time, start, stop); 
    printf("GFlops = %f\n", (1.e-6)*(float)(N*N_ITERATIONS)/time); 
    gpuErrchk(cudaMemcpy(h_a_result_device, d_a, N*sizeof(int), cudaMemcpyDeviceToHost)); 
    for (int i=0; i<N; i++) if(h_a_result_device[i] != h_a_result_host[i]) { printf("Error at i=%i! Host = %i; Device = %i\n", i, h_a_result_host[i], h_a_result_device[i]); return; } 

    /***********/ 
    /* KERNEL1 */ 
    /***********/ 
    gpuErrchk(cudaMemcpy(d_a, h_a, N*sizeof(int), cudaMemcpyHostToDevice)); 
    cudaEventRecord(start, 0); 
    kernel1<<<1, N/2>>>(d_a, d_b, d_c, N); 
#ifdef DEBUG 
    gpuErrchk(cudaPeekAtLastError()); 
    gpuErrchk(cudaDeviceSynchronize()); 
#endif 
    cudaEventRecord(stop, 0); 
    cudaEventSynchronize(stop); 
    cudaEventElapsedTime(&time, start, stop); 
    printf("GFlops = %f\n", (1.e-6)*(float)(N*N_ITERATIONS)/time); 
    gpuErrchk(cudaMemcpy(h_a_result_device, d_a, N*sizeof(int), cudaMemcpyDeviceToHost)); 
    for (int i=0; i<N; i++) if(h_a_result_device[i] != h_a_result_host[i]) { printf("Error at i=%i! Host = %i; Device = %i\n", i, h_a_result_host[i], h_a_result_device[i]); return; } 

    /***********/ 
    /* KERNEL2 */ 
    /***********/ 
    gpuErrchk(cudaMemcpy(d_a, h_a, N*sizeof(int), cudaMemcpyHostToDevice)); 
    cudaEventRecord(start, 0); 
    kernel2<<<1, N/4>>>(d_a, d_b, d_c, N); 
#ifdef DEBUG 
    gpuErrchk(cudaPeekAtLastError()); 
    gpuErrchk(cudaDeviceSynchronize()); 
#endif 
    cudaEventRecord(stop, 0); 
    cudaEventSynchronize(stop); 
    cudaEventElapsedTime(&time, start, stop); 
    printf("GFlops = %f\n", (1.e-6)*(float)(N*N_ITERATIONS)/time); 
    gpuErrchk(cudaMemcpy(h_a_result_device, d_a, N*sizeof(int), cudaMemcpyDeviceToHost)); 
    for (int i=0; i<N; i++) if(h_a_result_device[i] != h_a_result_host[i]) { printf("Error at i=%i! Host = %i; Device = %i\n", i, h_a_result_host[i], h_a_result_device[i]); return; } 

    cudaDeviceReset(); 

} 

On GT540M GeForce của tôi, kết quả là

kernel0 GFlops = 21.069281 Occupancy = 66% 
kernel1 GFlops = 21.183354 Occupancy = 33% 
kernel2 GFlops = 21.224517 Occupancy = 16.7% 

có nghĩa là hạt nhân với công suất thấp hơn vẫn có thể thể hiện hiệu suất cao, nếu Hướng dẫn Cấp Parallelism (ILP) được khai thác.

Các vấn đề liên quan