在GPU执行之外的CUDA内核启动中的大量开销

时间:2016-01-13 02:13:28

标签: linux cuda

我正在测量内核的运行时间,从CPU线程看,测量从启动内核到cudaDeviceSynchronize之后的间隔(使用gettimeofday)。在开始记录间隔之前,我有一个cudaDeviceSynchronize。我还检测内核在内核开始时通过从块(0,0,0)到块(占用-1)的每个块的线程(0,0,0)来记录GPU上的时间戳(使用clock64)。 0,0)到一个大小等于SM数量的数组。内核代码末尾的每个线程都将时间戳更新到另一个数组(大小相同),索引等于它运行的SM的索引。

从两个阵列计算的间隔是从CPU线程测量的间隔的60-70%。

例如,在K40上,当gettimeofday给出140ms的间隔时,根据GPU时间戳计算的间隔平均值仅为100ms。我已经尝试了许多网格大小(15个块到6K块)但到目前为止已经发现了类似的行为。

__global__ void some_kernel(long long *d_start, long long *d_end){
     if(threadIdx.x==0){
        d_start[blockIdx.x] = clock64();
     }
     //some_kernel code
     d_end[blockIdx.x] = clock64();
}

这对专家来说是否可行?

1 个答案:

答案 0 :(得分:1)

  

这对专家来说是否可行?

我认为对于您尚未展示的代码,任何事情都是可能的。毕竟,你可能只是在你的任何计算算术中都有一个愚蠢的错误。但是,如果问题是"在内核启动时应该有40ms的未计入时间开销,对于需要大约140ms执行的内核,这是明智的吗?"我会说不。

我相信我在评论中概述的方法相当准确。从网格中的任何线程获取最小clock64()时间戳(但请参阅下面有关SM限制的注释)。将其与网格中任何线程的最大时间戳进行比较。根据我的测试,差异将与gettimeofday()报告的执行时间相差2%以内。

这是我的测试用例:

$ cat t1040.cu
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#define LS_MAX 2000000000U
#define MAX_SM 64

#define cudaCheckErrors(msg) \
    do { \
        cudaError_t __err = cudaGetLastError(); \
        if (__err != cudaSuccess) { \
            fprintf(stderr, "Fatal error: %s (%s at %s:%d)\n", \
                msg, cudaGetErrorString(__err), \
                __FILE__, __LINE__); \
            fprintf(stderr, "*** FAILED - ABORTING\n"); \
            exit(1); \
        } \
    } while (0)


#include <time.h>
#include <sys/time.h>
#define USECPSEC 1000000ULL

__device__ int result;
__device__ unsigned long long t_start[MAX_SM];
__device__ unsigned long long t_end[MAX_SM];

unsigned long long dtime_usec(unsigned long long start){

  timeval tv;
  gettimeofday(&tv, 0);
  return ((tv.tv_sec*USECPSEC)+tv.tv_usec)-start;
}

__device__ __inline__ uint32_t __mysmid(){
  uint32_t smid;
  asm volatile("mov.u32 %0, %%smid;" : "=r"(smid));
  return smid;}

__global__ void kernel(unsigned ls){

  unsigned long long int ts = clock64();
  unsigned my_sm = __mysmid();
  atomicMin(t_start+my_sm, ts);
  // junk code to waste time
  int tv = ts&0x1F;
  for (unsigned i = 0; i < ls; i++){
    tv &= (ts+i);}
  result = tv;
  // end of junk code
  ts = clock64();
  atomicMax(t_end+my_sm, ts);

}

// optional command line parameter 1 = kernel duration, parameter 2 = number of blocks, parameter 3 = number of threads per block
int main(int argc, char *argv[]){

 unsigned ls;
 if (argc > 1) ls = atoi(argv[1]);
 else ls = 1000000;
 if (ls > LS_MAX) ls = LS_MAX;
 int num_sms = 0;
 cudaDeviceGetAttribute(&num_sms, cudaDevAttrMultiProcessorCount, 0);
 cudaCheckErrors("cuda get attribute fail");
 int gpu_clk = 0;
 cudaDeviceGetAttribute(&gpu_clk, cudaDevAttrClockRate, 0);
 if ((num_sms < 1) || (num_sms > MAX_SM)) {printf("invalid sm count: %d\n", num_sms); return 1;}
 unsigned blks;
 if (argc > 2) blks = atoi(argv[2]);
 else blks = num_sms;
 if ((blks < 1) || (blks > 0x3FFFFFFF)) {printf("invalid blocks: %d\n", blks); return 1;}
 unsigned ntpb;
 if (argc > 3) ntpb = atoi(argv[3]);
 else ntpb = 256;
 if ((ntpb < 1) || (ntpb > 1024)) {printf("invalid threads: %d\n", ntpb); return 1;}
 kernel<<<1,1>>>(100);  // warm up
 cudaDeviceSynchronize();
 cudaCheckErrors("kernel fail");
 unsigned long long *h_start, *h_end;
 h_start = new unsigned long long[num_sms];
 h_end = new unsigned long long[num_sms];
 for (int i = 0; i < num_sms; i++){
   h_start[i] = 0xFFFFFFFFFFFFFFFFULL;
   h_end[i] = 0;}
 cudaMemcpyToSymbol(t_start, h_start, num_sms*sizeof(unsigned long long));
 cudaMemcpyToSymbol(t_end, h_end, num_sms*sizeof(unsigned long long));
 unsigned long long htime = dtime_usec(0);
 kernel<<<blks,ntpb>>>(ls);
 cudaDeviceSynchronize();
 htime = dtime_usec(htime);
 cudaMemcpyFromSymbol(h_start, t_start, num_sms*sizeof(unsigned long long));
 cudaMemcpyFromSymbol(h_end, t_end, num_sms*sizeof(unsigned long long));
 cudaCheckErrors("some error");
 printf("host elapsed time (ms): %f \n device sm clocks:\n start:", htime/1000.0f);
 unsigned long long max_diff = 0;
 for (int i = 0; i < num_sms; i++) {printf(" %12lu  ", h_start[i]);}
 printf("\n end:  ");
 for (int i = 0; i < num_sms; i++) {printf(" %12lu  ", h_end[i]);}
 for (int i = 0; i < num_sms; i++) if ((h_start[i] != 0xFFFFFFFFFFFFFFFFULL) && (h_end[i] != 0) && ((h_end[i]-h_start[i]) > max_diff)) max_diff=(h_end[i]-h_start[i]);
 printf("\n max diff clks: %lu\nmax diff kernel time (ms): %f\n", max_diff, max_diff/(float)(gpu_clk));
 return 0;
}
$ nvcc -o t1040 t1040.cu -arch=sm_35
$ ./t1040 1000000 1000 128
host elapsed time (ms): 2128.818115
 device sm clocks:
 start:      3484744        3484724
 end:     2219687393     2228431323
 max diff clks: 2224946599
max diff kernel time (ms): 2128.117432
$

注意:

  1. 由于使用了64位atomicMinatomicMax,此代码只能在cc3.5或更高版本的GPU上运行。

  2. 我已经在GT640(极低端cc3.5设备)和K40c(高端)上运行各种网格配置,主机和设备之间的时序结果同意2%(相当长的内核执行时间。如果你传递1作为命令行参数,网格大小非常小,内核执行时间将非常短(纳秒),而主机将看到大约10-20us这个正在测量的内核启动开销。所以2%的数字用于执行时间超过20us的内核。

  3. 它接受3个(可选)命令行参数,第一个参数会改变内核执行的时间。

  4. 我的时间戳是在 per-SM 的基础上完成的,因为clock64()资源是indicated to be一个 per-SM 资源。不保证sm时钟在SM之间同步。

  5. 您可以修改网格尺寸。第二个可选命令行参数指定要启动的块数。第三个可选命令行参数指定每个块的线程数。我在这里展示的时序方法不应该依赖于启动的块数或每个块的线程数。如果您指定的块数少于SM,则代码应忽略&#34;未使用的&#34; SM数据。

相关问题