CUDA分支优化

时间:2012-07-08 19:40:48

标签: cuda

我有一个使用一些CUDA内核的程序,这个程序运行50-100ms,而其他运行0-5ms。我希望这与所有分支有关,但我不确定如何减少它。我正在编译计算能力2.1设备。如果有人能指出我正确的方向,那就太好了。

// chosen using occupancy spreadsheet
#define SCORE_THREADS_PER_BLOCK 448

__device__ double ScoringMatrixVal(double *scoring_matrix, size_t pitch, unsigned int row, unsigned int column) {
  return *((double*)((char*) scoring_matrix + row * pitch) + column);
}

__global__ void ScoreBindingSites(char *input_sequence, unsigned long is_length, unsigned int *rvd_sequence, unsigned int rs_len, double cutoff, unsigned int rvd_num, double *scoring_matrix, size_t sm_pitch, unsigned char *results) {

  int block_seq_index = SCORE_THREADS_PER_BLOCK * (blockIdx.y * gridDim.x + blockIdx.x);
  int thread_id = (blockDim.x * threadIdx.y) + threadIdx.x;
  int seq_index = block_seq_index + thread_id;

  if (seq_index < 1 || seq_index >= is_length || seq_index + rs_len >= is_length - 1) return;

  if (input_sequence[seq_index - 1] == 'T' || input_sequence[seq_index - 1] == 't') {

    double thread_result = 0;

    for (int i = 0; i < rs_len; i++) {

      int rvd_index = i;

      int sm_col = 4;

      char base = input_sequence[seq_index + i];

      if (base == 'A' || base == 'a')    
        sm_col = 0;
      if (base == 'C' || base == 'c')
        sm_col = 1;
      if (base == 'G' || base == 'g')
        sm_col = 2;
      if (base == 'T' || base == 't')
        sm_col = 3;

      thread_result += ScoringMatrixVal(scoring_matrix, sm_pitch, rvd_sequence[rvd_index], sm_col);

    }

    results[seq_index] |= (thread_result < cutoff ? 1UL : 0UL) << (2 * rvd_num);

  } 

  if (input_sequence[seq_index + rs_len] == 'A' || input_sequence[seq_index + rs_len] == 'a') {

    double thread_result = 0;

    for (int i = 0; i < rs_len; i++) {

      int rvd_index = rs_len - i - 1;

      int sm_col = 4;

      char base = input_sequence[seq_index + i];

      if (base == 'A' || base == 'a')    
        sm_col = 3;
      if (base == 'C' || base == 'c')
        sm_col = 2;
      if (base == 'G' || base == 'g')
        sm_col = 1;
      if (base == 'T' || base == 't')
        sm_col = 0;

      thread_result += ScoringMatrixVal(scoring_matrix, sm_pitch, rvd_sequence[rvd_index], sm_col);

    }

    results[seq_index] |= (thread_result < cutoff ? 1UL : 0UL) << (2 * rvd_num + 1);

  }

}

ScoreBindingSites以每个块的(32,14)个线程启动,并有足够的块来覆盖输入序列。如果有帮助,可以找到完整的来源here

2 个答案:

答案 0 :(得分:1)

您可以采取一些措施来改进此代码:

  • 如上所述,合并'T''A'的两个循环。这可能是您分支差异的最大来源,因为循环内的if - 语句的小级联很可能被编译为谓词指令(参见NVidia CUDA C Programming guide的第5.4.2节)。

  • 字节大小的全局内存访问是一个糟糕的主意。相反,我建议将input_sequenceresultsbase声明为char4,并在主循环的每次迭代中为{{1}的每个值执行操作},base.xbase.ybase.z

  • 您可能还想仔细查看base.w正在做什么。它只是从记忆中读取价值吗?如果是这样,你可以用恒定内存替换它吗?还是纹理?

<强>更新

根据要求,这是第二点我的意思。但是我还没有对代码进行测试,所以请随意保留您发现的任何错误或错别字。请注意,为简单起见,我假设ScoringMatrixVal是四的倍数。

rs_len

一些注意事项:

  • 我已经正确地重写了你的函数// chosen using occupancy spreadsheet #define SCORE_THREADS_PER_BLOCK 448 __device__ double ScoringMatrixVal(double *scoring_matrix, size_t pitch, unsigned int row, unsigned int column) { return scoring_matrix[ row*pitch/sizeof(double) + column ]; } __global__ void ScoreBindingSites(char4 *input_sequence, unsigned long is_length, unsigned int *rvd_sequence, unsigned int rs_len, double cutoff, unsigned int rvd_num, double *scoring_matrix, size_t sm_pitch, unsigned char *results) { int block_seq_index = SCORE_THREADS_PER_BLOCK * (blockIdx.y * gridDim.x + blockIdx.x); int thread_id = (blockDim.x * threadIdx.y) + threadIdx.x; int seq_index = block_seq_index + thread_id; if (seq_index < 1 || seq_index >= is_length || seq_index + rs_len >= is_length - 1) return; if (input_sequence[seq_index - 1] == 'T' || input_sequence[seq_index - 1] == 't') { double4 thread_result = make_double4( 0 ); for (int i = 0; i < rs_len/4; i++) { int rvd_index = 4*i; int4 sm_col = make_int4( 4 ); char4 base = input_sequence[seq_index + i]; if (base.x == 'A' || base.x == 'a') sm_col.x = 0; else if (base.x == 'C' || base.x == 'c') sm_col.x = 1; else if (base.x == 'G' || base.x == 'g') sm_col.x = 2; else if (base.x == 'T' || base.x == 't') sm_col.x = 3; thread_result.x += ScoringMatrixVal(scoring_matrix, sm_pitch, rvd_sequence[rvd_index + 0], sm_col.x); if (base.y == 'A' || base.y == 'a') sm_col.y = 0; else if (base.y == 'C' || base.y == 'c') sm_col.y = 1; else if (base.y == 'G' || base.y == 'g') sm_col.y = 2; else if (base.y == 'T' || base.y == 't') sm_col.y = 3; thread_result.y += ScoringMatrixVal(scoring_matrix, sm_pitch, rvd_sequence[rvd_index + 1], sm_col.y); if (base.z == 'A' || base.z == 'a') sm_col.z = 0; else if (base.z == 'C' || base.z == 'c') sm_col.z = 1; else if (base.z == 'G' || base.z == 'g') sm_col.z = 2; else if (base.z == 'T' || base.z == 't') sm_col.z = 3; thread_result.z += ScoringMatrixVal(scoring_matrix, sm_pitch, rvd_sequence[rvd_index + 2], sm_col.z); if (base.w == 'A' || base.w == 'a') sm_col.w = 0; else if (base.w == 'C' || base.w == 'c') sm_col.w = 1; else if (base.w == 'G' || base.w == 'g') sm_col.w = 2; else if (base.w == 'T' || base.w == 't') sm_col.w = 3; thread_result.w += ScoringMatrixVal(scoring_matrix, sm_pitch, rvd_sequence[rvd_index + 3], sm_col.w); } double acc_thread_result = thread_result.x + thread_result.y + thead_result.z + thread_result.w; results[seq_index] |= (acc_thread_result < cutoff ? 1UL : 0UL) << (2 * rvd_num); } if (input_sequence[seq_index + rs_len] == 'A' || input_sequence[seq_index + rs_len] == 'a') { ... } } 以使用常规数组访问,因为整个指针算法的混乱可能会导致编译器失效。
  • 我已将您的ScoringMatrixVal语句转换为级联的if语句,因为它们似乎是互斥的。我猜测编译器将使用谓词指令,并将交错四个if-elseif块。
  • 您可以考虑将所有内容替换为if-elseif,其中所有内容都设置为char[256],但4Aa的字符代码除外, C等等......
  • 如果将c - 语句转换为表查找,则可以为if-elseifinput_sequence[seq_index - 1] == 'T'使用两个不同的表,从而将它们全部保存在一个循环中。

我希望我没有太多搞乱代码,这有帮助!

答案 1 :(得分:0)

据我所知,在你的内核中,每个Thread最多读取32个字符并检查每个char并输出一些数据。

您可以通过使用不同的块方法和不同的索引来隐式模拟它来完全删除循环(如果在您的情况下可以这样做)。

每个块有32个线程,每个线程计算一次循环迭代的结果。

我不知道它是否更快但值得考验。

佩德罗使用表格查找来代替你的条件的答案应该被明确考虑。

小改动:

input_sequence[seq_index - 1] == 'T' || input_sequence[seq_index - 1] == 't'是否只针对一个内存读取进行了优化?

通过仅删除一次使用的变量threadId来保存寄存器。