如何在这个cuda示例中同步线程

时间:2017-01-27 03:43:31

标签: cuda

我有以下粗略的代码大纲:

  1. 运行循环,数百万次
  2. 在该循环中,计算值'我是 - 请参阅
  3. 下面的此类函数的示例
  4. 毕竟'我已经计算过了,计算其他值'V'
  5. 重复循环
  6. I或V的每次计算都可能涉及多达20个数学运算,(例如I1 = A + B / C * D + 1 / exp(V1) - E + F + V2等)。

    大致有:

    1. 50'我是
    2. 10'V's
    3. 每个I和V中的10个值,即它们是长度为10的矢量

      起初我尝试在C中运行一个简单的循环,每个时间步都有内核调用,但这真的很慢。如果主循环在调用其他内核的内核中,似乎我可以让代码运行得更快。但是,我担心内核调用开销(也许我不应该这样)所以我提出了类似下面的内容,其中每个I和V独立循环,必要时在内核之间进行同步。

      作为参考,下面的变量被硬编码为__device__值,但最终我会将一些值传递给特定的内核以使系统变得有趣。

      __global__ void compute_IL1()
      {
          int id = threadIdx.x; 
          //n_t = 1e6;
          for (int i = 0; i < n_t; i++){
              IL1[id] = gl_1*(V1[id] - El_1);
              //atomic, sync, event????,
          }
      }        
      
      __global__ void compute_IK1()
      {
          int id = threadIdx.x; 
          for (int i = 0; i < n_t; i++){
              Ik1[id] = gk_1*powf(0.75*(1-H1[id]),4)*(V1[id]-Ek_1);
              //atomic, sync, event?
          }
      }   
      
      __global__ void compute_V1()
      {
         int id = threadIdx.x; 
         for (int i = 0; i < n_t; i++){
             //wait for IL1 and Ik1 and others, but how????
             V1[id] = Ik1[id]+IL1[id] + ....
             //trigger the I's again
         }
      }
      
      
      //main function
      compute_IL1<<<1,10,0,s0>>>();
      compute_IK1<<<1,10,0,s1>>>();
      //repeat this for many 50 - 70 more kernels (Is and Vs)
      

      所以问题是,我将如何同步这些内核?事件方法最好吗?在这里使用是否有更好的范例?

2 个答案:

答案 0 :(得分:1)

没有一种理智的机制,我可以想到让多个常驻内核同步,而不采用可能无法可靠运行的hacky原子技巧。

如果您正在运行具有10个线程的块,并且这些内核由于正确性原因而无法并发执行,则您(在最好的情况下)使用设备的1/64计算容量。你所描述的这个问题听起来完全不适合GPU。

答案 1 :(得分:0)

所以,我尝试了几种方法。

  1. 具有少量内核调用的循环,其中最后一次内核调用依赖于先前的内核调用。这可以通过cudaStreamWaitEvent来完成,它可以等待多个事件。我发现了这个:http://cedric-augonnet.com/declaring-dependencies-with-cudastreamwaitevent/。不幸的是,内核调用过于昂贵。

  2. 并发流之间的全局变量。逻辑非常简单,只有一个线程暂停,直到全局变量等于循环变量,表明所有线程都可以继续。然后是同步线程调用。不幸的是,这种方法效果不佳。

  3. 最终,我认为我已经确定了一个嵌套循环,其中外部循环表示时间,内部循环根据依赖性指示要运行的集合指令中的哪一个。我还启动了每个块的最大线程数(1024),并将需要处理的向量分解为warp。粗糙的伪代码是:

    run_main<<<1,1024>>>();
    
    __global__ void run_main(){
    int warp = threadIdx.x/32;
    int id   = threadIdx.x - warp*32;
    
    if (id < 10){
        for (int i = 0; i < n_t; i++){
            for(int j = 0; j < n_j; j++){
                switch (j){
                    case 0:
                        switch(warp){
                            case 0:
                                I1[id] = a + b + c*d ...
                                break;
                            case 1:
                                I2[id] = f*g/h
                                break;
                        }
                    break;
                    //These things depend on case 0 OR
                    //we've run out of space in the first pass
                    //32 cases max [0 ... 31]
                    case 1:
                        switch(warp){
                            case 0:
                                V1[ID] = I1*I2+ ...
                                break;
                            case 1:
                                V2[ID] = ...
    
            //syncs across the block
            __syncthreads();
    

    这个设计基于我的印象,每组32个线程独立运行但应该运行相同的代码,否则事情可能会显着减慢。

    所以最后,我同时运行大约32 * 10条指令。 其中32是warp的数量,它取决于我可以同时计算多少个不同的值(由于依赖性),10是每个向量中的元素数。由于所有warp在进入下一步之前需要合并(由于syncthreads调用),因此每个warp中的计算中的任何不平衡都会减慢这种速度。我在此之上运行不同的参数(参数扫描),因此我可能在块中一次运行3次,乘以卡上的流式处理器数量(或任何正式名称)。 / p>

    我需要改变的一件事是,我目前正在测试连接到显示器的视频卡。显然,如果内存持续时间超过5秒,Windows将终止内核,因此我需要在分组时间步骤中调用内核,例如每1e5个时间步骤(在我的情况下)。

相关问题