<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      高性能計算-GPU并行掃描(28)

      1. 掃描概念

      • 對數組arr[N]掃描就是得到數組prefix[N],每個元素是之前arr元素的求和.
      • 開掃描定義:prefix1[N] = { arr[0], arr[0]+arr[1], ..., arr[0]+arr[1]+arr[N-1] }
      • 閉掃描定義: prefix2[N] = { 0, arr[0], arr[0]+arr[1], ..., arr[0]+arr[1]+arr[N-12}

      2. Hillis steele 掃描算法

      (1)一種閉掃描算法。基礎的Hillis steele 掃描算法只能掃描單塊,步驟復雜度為 O(logN),任務復雜度為 O(N*logN-(N-1)).
      (2)基礎版算法介紹:假設步長為s每次迭代 >=s的線程參與計算,本線程id作為被加數,往前 -s 索引為加數
      image
      (3)支持任意block倍數長度的數據輸入,介紹:

      a) 掃描block:按blocksize劃分數據,每個block處理一個數據塊,塊內進行掃描,輸出規約結果數組,并將最后一個塊內和元素按 blockid 保存到輔助數組;
      b) 遞歸閉掃描輔助數組:遞歸為了將塊內求和輔助數組的結果加到塊內掃描結果上,需要掃描輔助數組,如果輔助數組大于256那么要考慮輔助數組遞歸掃描;直到輔助數組被掃描求和數組block為1,掃描結束,反向依次彈棧把求和數組加到 block+1 的掃描結果數組上。

      image

      (4)優化:共享內存使用雙buffer,讀寫分離,避免讀寫 bank沖突
      (5)代碼:

      /*
      任意長度掃描,單塊掃描使用Hillis Steele算法,使用double buffer避免讀寫bank沖突;默認輸入數據長度是blockSize的倍數
      */
      
      #include "common.h"
      #include <stdio.h>
      
      #define BlockSize 512
      
      //單塊掃描函數, in 輸入數據, out 掃描結果, len1 掃描數據長度,sum block求和數組,len2 sum數組大小
      __global__ void kernel_BlockScan(int *in,int * out,int len1,int *sum,int len2)
      {
          //取單塊數據到共享內存,使用雙buffer,讀寫分離,避免讀寫bank沖突
          __shared__ int arrShared[2*BlockSize];
          int bid = blockIdx.x;   //塊id
          int tid = blockIdx.x * blockDim.x + threadIdx.x;    //全局線程id
          int tbid = threadIdx.x; //塊內線程id
          int write = 0;  //初始化第0個buff write
          int read = 1;   //初始化第1個buff read
          
          if(tid<len1)
          {
              //共享內存read buffer數據初始化
              arrShared[read*BlockSize + tbid] = in[tid]; 
              __syncthreads();
              for(int s=1;s<BlockSize;s<<=1)
              {
                  // Hillis Steele算法:索引大于步長的線程參與計算,對應位置上的倍加數+塊內前面步長位置的加數
                  if(tbid>=s)
                  {
                      //被加數索引
                      int id = read*BlockSize + tbid;
                      arrShared[write*BlockSize + tbid] = arrShared[id] + arrShared[id -s]; 
                  }
                  else//否則保留上一輪計算的前綴和
                      arrShared[write*BlockSize + tbid] = arrShared[read*BlockSize + tbid];
                  //每循環一次,讀寫buffer區域交換
                  __syncthreads();
                  write = 1 - write;
                  read = 1- read;
              }
              // 將block計算結果賦值給out
              out[tid] = arrShared[read*BlockSize + tbid];
              //將塊內求和按塊索引號放求和數組
              if(tbid==(BlockSize-1))
                  sum[bid] = arrShared[read*BlockSize + tbid];
          }
      }
      
      //對第一輪掃描結果后處理函數,len 數據總長度
      __global__ void handPost(int* first,int len,int *second)
      {
          int tid = blockDim.x * blockIdx.x + threadIdx.x;
          int bid = blockIdx.x;
          int tbid = threadIdx.x; //塊內線程id
      
          //從第二塊開始加上前一個塊的sum值
          if(bid>0 && tid<len)
          {
              //取出被加數
              __shared__ int temp;
              if(tbid==0)
                  temp = second[bid-1];
              __syncthreads();
              first[tid] += temp;
          }
      }
      
      //輸入分塊掃描分配及后處理函數,輸出結果的空間在外面分配
      //這里第一二輪掃描都使用該函數遞歸調用,n輸入數據長度
      void scan(int *in,int *arrResult,int n)
      {
          int GridSize = (n-1)/BlockSize + 1;
          printf("gridSize:%d\n",GridSize);
          int *arrResultFirstSum;
          CudaSafeCall(cudaMalloc((void**)&arrResultFirstSum,GridSize*sizeof(int)));
      
          kernel_BlockScan<<<GridSize,BlockSize,2*BlockSize*sizeof(int)>>>(in,arrResult,n,arrResultFirstSum,GridSize);
          CudaCheckError();
      
          //循環掃描遞歸終止條件:假如第一次掃描gridsize=4096大于256,組要循環掃描到 gridsize==1,block的和需要被handpost處理累加到后面
          if(GridSize==1)
          {
              cudaFree(arrResultFirstSum);
              return;
          }
      
          //遞歸掃描:對本輪計算結果和的數組進行掃描
          int *arrResultSecond;
          //cudaMalloc 隱式同步
          CudaSafeCall(cudaMalloc((void**)&arrResultSecond,GridSize*sizeof(int)));
          scan(arrResultFirstSum,arrResultSecond,GridSize);
          //空流核函數隱式同步
          // cudaDeviceSynchronize();
      
          //將本輪掃描結果數組的索引+1作為第一次掃描結果按blockIdx.x的索引作為塊內掃描結果的加數,得到最終掃描結果arrResult
          handPost<<<GridSize,BlockSize,sizeof(int)>>>(arrResult,n,arrResultSecond);
          CudaCheckError();
      
          //cudafree是隱式同步
          CudaSafeCall(cudaFree(arrResultFirstSum));
          CudaSafeCall(cudaFree(arrResultSecond));
      
          return;
      }
      
      
      int main(int argc ,char** argv)
      {
          int N = 1<<20; //數據規模
          if(argc>1)
              N = 1 << atoi(argv[1]);
          
          //cpu串行計算
          int *arrHost = (int*)calloc(N,sizeof(int));
          int *resultHost = (int*)calloc(N,sizeof(int));
          int *resultFD = (int*)calloc(N,sizeof(int));
          float cpuTime = 0.0;
          initialDataInt(arrHost,N);
      
          double start = cpuSecond();
          //閉掃描
          //初始化第一個元素,避免循環內有判斷,cpu分支預測錯誤
          resultHost[0] = arrHost[0];
          for(int i=1;i<N;i++)
              resultHost[i] = resultHost[i-1] + arrHost[i]; 
          double end = cpuSecond();
          cpuTime = (end - start)*1000;
      
          //gpu計算
          int *arrD;
          int *resultD;
          float gpuTime = 0.0;
          CudaSafeCall(cudaMalloc((void**)&arrD,N*sizeof(int)));
          CudaSafeCall(cudaMalloc((void**)&resultD,N*sizeof(int)));
          cudaEvent_t startD,endD;
          CudaSafeCall(cudaEventCreate(&startD));
          CudaSafeCall(cudaEventCreate(&endD));
      
          CudaSafeCall(cudaMemcpy(arrD,arrHost,N*sizeof(int),cudaMemcpyHostToDevice));
          cudaEventRecord(startD);
          scan(arrD,resultD,N);
          CudaCheckError();
          cudaEventRecord(endD);
          cudaEventSynchronize(endD);
          
          CudaSafeCall(cudaMemcpy(resultFD,resultD,N*sizeof(int),cudaMemcpyDeviceToHost));
          cudaEventElapsedTime(&gpuTime,startD,endD);
          cudaEventDestroy(startD);
          cudaEventDestroy(endD);
          
          //結果比對
          checkResultInt(resultHost,resultFD,N);
      
          free(arrHost);
          free(resultHost);
          free(resultFD);
          CudaSafeCall(cudaFree(arrD));
          CudaSafeCall(cudaFree(resultD));
      
          printf("數據規模 %d kB,cpu串行耗時 %.3f ms ;gpu 單個block使用Hillis Steele 掃描(double buffer)計算耗時 %.3f ms,加速比為 %.3f .\n",N>>10,cpuTime,gpuTime,cpuTime/gpuTime);
          return 0;
      }
      

      3. Blelloch掃描算法

      (1)一種開掃描算法。基礎的 Blelloch掃描算法,只能處理單塊數據。步驟復雜度為 O(2logN),任務復雜度為 O(2N)。
      (2)基礎版算法介紹:

      a) 規約:對塊進行規約,每次循更新計算結果的位置的數值,其他數值不變。
      image
      b) 清零和散出:

      • 清零:最后一個元素置為0
      • 散出:假設數據長度為blocksize ,迭代循環步長為 s = blocksize/2 ,最后一個元素 E1,與向前s的元素 E2相加,結果放在 E1位置,并將原數據 E1放在 E2位置;每輪循環步長減半。
        image

      (3)支持任意block倍數長度的數據輸入,介紹:

      a)開掃描block:按blocksize劃分數據,每個block處理一個數據塊,塊內進行掃描,輸出前綴和和塊內求和結果輔助數組
      b) 遞歸開掃描輔助數組:遞歸為了將塊內求和輔助數組的結果加到塊內掃描結果上,需要掃描輔助數組,如果輔助數組大于256那么要考慮輔助數組遞歸掃描;直到輔助數組被掃描求和數組block為1,掃描結束,反向依次彈棧把求和數組加到對應 block前綴數組上(此處與Hillis steele不同)。
      image

      (4)優化

      • 塊內共享內存每個warp后添加一個padding位,避免訪存 bank沖突
      • 掃描核函數內規約和散出階段根據步長計算需要線程的數量,只讓小于該數量的塊內線程id參與計算

      (5)代碼

      /*
      使用blelloch 算法對任意長度(blocksize的倍數)做開掃描
      // 優化:
          1.存放數據:共享內存每32個數據添加一個padding,避免 bank沖突;
          2.讀取數據:handPost 現成處理的數據
          3.掃描核函數內規約和散出階段根據步長計算需要線程的數量,只讓小于該數量的塊內線程id參與計算
      */
      #include "common.h"
      #include <stdio.h>
      
      #define BlockSize 512
      #define WarpSize 32
      #define ADD_PADDING_MEM(A) ((A)+((A)>>5))   //根據原數據的索引計算機padding 后的索引;或者輸入原數據大小,獲取padding后的大小
      
      //單塊掃描函數, in 輸入數據, out 掃描結果, len1 掃描數據長度,sum block求和數組,len2 sum數組大小
      __global__ void kernel_BlockScan(int *in,int * out,int len1,int *sum,int len2)
      {
          int bid = blockIdx.x;
          // int tid = blockIdx.x * blockDim.x + threadIdx.x;
          int btid = threadIdx.x;
      
          //取數據到共享內存,一個塊計算2個快的數據
          //優化:每32個數據添加一個padding數據位,防止bank沖突
          __shared__ int arrShared[ADD_PADDING_MEM(2*BlockSize)];
      
          //每個線程取第一個數
          if(2*(BlockSize*bid+btid) < len1)
              arrShared[ADD_PADDING_MEM(2*btid)] = in[2*(BlockSize*bid+btid)];
          //取第二個數
          if(2*(BlockSize*bid+btid)+1 < len1)
              arrShared[ADD_PADDING_MEM(2*btid+1)] = in[2*(BlockSize*bid+btid)+1];
          //保留最后一個值,避免最后再次訪存
          int lastValue = 0;
          //如果用其他線程保存數據,應先同步,保證最后一個線程訪存之后再保存
          if(btid == BlockSize-1)
              lastValue = arrShared[ADD_PADDING_MEM(2*BlockSize-1)];
          __syncthreads();
      
          //規約
          //優化:減少線程束分化,由于計算的結果保存原共享內存,計算數據的位置分配計算線程從0號開始,將計算結果放入對應位置,節約了線程束的需求
          for(int s=1;s<=BlockSize;s<<=1)
          {
              //根據步長計算本輪需要的線程數量
              if(btid<(2*BlockSize)/(2*s))
              {
                  // 根據步長和計算線程索引,得到被加數索引
                  int index = 2*s*(btid+1)-1;
                  //減少線程束優化,根據需要的線程數量
                  if(index<2*BlockSize)
                      arrShared[ADD_PADDING_MEM(index)] += arrShared[ADD_PADDING_MEM(index-s)];
              }
              //防止空閑線程進行下一輪計算
              __syncthreads();
          }
          //最后一個結果清0
          if(btid == 0)
              arrShared[ADD_PADDING_MEM(2*BlockSize-1)] = 0;
          __syncthreads();
      
          // down sweep 
          for(int s=BlockSize;s>0;s>>=1)
          {
              //根據步長計算本輪需要的線程數量
              if(btid<(2*BlockSize)/(2*s))
              {
                  //根基 btid和步長計算出被加數索引
                  int index = 2*s*(btid+1)-1;
                  int addSecondIndex = ADD_PADDING_MEM(index);
                  int addFirstIndex = ADD_PADDING_MEM(index-s);
                  int temp = arrShared[addSecondIndex];
                  arrShared[addSecondIndex] += arrShared[addFirstIndex];
                  arrShared[addFirstIndex] = temp;
              }
              //防止空閑線程進行下一輪計算
              __syncthreads();
          }
          //保存開掃描結果
          //保存線程對應第一個位置數據
          int addFirstIndex = 2*(bid*BlockSize+btid);
          int addSecondIndex = addFirstIndex+1;
          out[addFirstIndex] =  arrShared[ADD_PADDING_MEM(2*btid)];
          out[addSecondIndex] =  arrShared[ADD_PADDING_MEM(2*btid+1)];
          
          //block求和結果放入輔助數組
          if(btid == BlockSize-1)
              sum[bid] = lastValue + arrShared[ADD_PADDING_MEM(2*BlockSize-1)];
      }
      
      //對第一輪掃描結果后處理函數,len 數據總長度
      __global__ void handPost(int* first,int len,int *second)
      {
          int tid = blockIdx.x * blockDim.x + threadIdx.x;
          int btid = threadIdx.x;
          int bid = blockIdx.x;
          int wid = tid/WarpSize;     //warpid
          int wtid = tid%WarpSize;    //warp內線程id
      
          //開掃描輔助數組第一個值為0,該索引塊不參與計算
          if(bid>0)
          {
              //這里有bank沖突:如果一個線程處理相鄰數據,0和16線程在bank0和bank1雙路沖突
              //優化: 一個warp計算兩個warp大小的數據,一個線程取的兩個數據偏移warp大小,可以避免bank沖突
              __shared__ int secondValue;
              if(btid==0)
                  secondValue = second[bid];
              __syncthreads();
              first[wid*2*WarpSize+wtid] += secondValue;
              first[wid*2*WarpSize+wtid+WarpSize] += secondValue;
          }
          //沒有依賴可以不同步
      }
      
      //輸入分塊掃描分配及后處理函數,輸出結果的空間在外面分配
      //這里第一二輪掃描都使用該函數遞歸調用,n輸入數據長度
      void scan(int *in,int *arrResult,int n)
      {
          int GridSize = (n-1)/(2*BlockSize) + 1;
          // printf("gridSize:%d\n",GridSize);
          int *arrResultFirstSum;
          CudaSafeCall(cudaMalloc((void**)&arrResultFirstSum,GridSize*sizeof(int)));
      
          kernel_BlockScan<<<GridSize,BlockSize,ADD_PADDING_MEM(2*BlockSize)*sizeof(int)>>>(in,arrResult,n,arrResultFirstSum,GridSize);
          CudaCheckError();
      
          //循環掃描遞歸終止條件:假如第一次掃描gridsize=4096大于256,組要循環掃描到 gridsize==1,block的和需要被handpost處理累加到后面
          if(GridSize==1)
          {
              cudaFree(arrResultFirstSum);
              return;
          }
      
          //二次掃描:對本輪計算結果和的數組進行開掃描
          int *arrResultSecond;
          //cudaMalloc 隱式同步
          CudaSafeCall(cudaMalloc((void**)&arrResultSecond,GridSize*sizeof(int)));
          scan(arrResultFirstSum,arrResultSecond,GridSize);
          //空流核函數隱式同步
          // cudaDeviceSynchronize();
      
      
      
          //將二輪開掃描結果按數組索引加到對應索引塊的第一輪掃描結果上,得到最終掃描結果arrResult
          handPost<<<GridSize,BlockSize,sizeof(int)>>>(arrResult,n,arrResultSecond);
          CudaCheckError();
      
          //cudafree是隱式同步
          CudaSafeCall(cudaFree(arrResultFirstSum));
          CudaSafeCall(cudaFree(arrResultSecond));
      
          return;
      }
      
      
      int main(int argc ,char** argv)
      {
          int N = 1<<20; //數據規模
          if(argc>1)
              N = 1 << atoi(argv[1]);
          
          //cpu串行計算
          int *arrHost = (int*)calloc(N,sizeof(int));
          int *resultHost = (int*)calloc(N,sizeof(int));
          int *resultFD = (int*)calloc(N,sizeof(int));
          initialDataInt(arrHost,N);
      
          double start = cpuSecond();
          //閉掃描
          //初始化第一個元素,避免循環內有判斷,cpu分支預測錯誤
          resultHost[0] = 0;
          for(int i=1;i<N;i++)
              resultHost[i] = resultHost[i-1] + arrHost[i-1]; 
          double cpuTime = (cpuSecond()-start)*1000;
      
          //gpu計算
          int *arrD;
          int *resultD;
          float gpuTime = 0.0;
          CudaSafeCall(cudaMalloc((void**)&arrD,N*sizeof(int)));
          CudaSafeCall(cudaMalloc((void**)&resultD,N*sizeof(int)));
          cudaEvent_t startD,endD;
          CudaSafeCall(cudaEventCreate(&startD));
          CudaSafeCall(cudaEventCreate(&endD));
      
          CudaSafeCall(cudaMemcpy(arrD,arrHost,N*sizeof(int),cudaMemcpyHostToDevice));
          cudaEventRecord(startD);
          scan(arrD,resultD,N);
          CudaCheckError();
          cudaEventRecord(endD);
          cudaEventSynchronize(endD);
          
          CudaSafeCall(cudaMemcpy(resultFD,resultD,N*sizeof(int),cudaMemcpyDeviceToHost));
          cudaEventElapsedTime(&gpuTime,startD,endD);
          cudaEventDestroy(startD);
          cudaEventDestroy(endD);
          
          //結果比對
          checkResultInt(resultHost,resultFD,N);
          free(arrHost);
          free(resultHost);
          free(resultFD);
          CudaSafeCall(cudaFree(arrD));
          CudaSafeCall(cudaFree(resultD));
      
          printf("數據規模 %d kB,cpu串行耗時 %.3f ms ;gpu 單個block使用Blelloch 掃描計算耗時 %.3f ms,加速比為 %.3f .\n",N>>10,cpuTime,gpuTime,cpuTime/gpuTime);
          return 0;
      }
      

      4. 測試數據

      掃描算法 串行(ms) gpu并行(ms) 加速比
      Hillis steele (0.1M數據) 0.003 0.2 0.015
      Blelloch (0.1M數據) 0.005 0.131 0.038
      thrust庫 (0.1M數據) - 0.16 0.025
      Hillis steele (1M數據) 5.363 1.947 2.754
      Blelloch (1M數據) 5.593 2.047 2.732
      thrust庫 (1M數據) - 0.39 13.850
      Hillis steele (25M數據) 146.608 10.376 14.129
      Blelloch (25M數據) 149.882 11.981 12.510
      thrust庫 (25M數據) - 0.62 238.000

      備注:thrust 的時間包主機與GPU的內存拷貝,測試代碼不包含內存拷貝耗時。

      5. 結果分析

      (1) Hillis steele 比 Belloch掃描算法的效率分析,Hillis steele的任務復雜度大于Blelloch,Blelloch的步驟復雜度大于Hillis steele

      掃描算法 步驟復雜度 任務復雜度
      Hillis steele O(logN) O(N*logN-(N-1))
      Blelloch O(2logN) O(2N)

      (2) 耗時分析

      • 當數據量較小時串行效率最高,
      • 當數據量較大時 Hillis steele 比 Belloch算法更高效,并且cuda thrust的效率極高

      (3) 算法選擇策略:

      • 理論上:當數據規模大于核心數量時,應選用任務總量少,步驟多的算法比如 Blelloch ;當數據規模小于核心數量時應選用計算任務總量多,將計算分攤到更多的核心上,步驟少的算法比如 Hillis steele。
      • 實際測試中:無論數據規模大還是小 Hillis steele 都比 Belloch 算法高效,使用 nvprof 分析后發現 Belloch 掃描核函數的步驟過多,對效率影響較大,應進一步改進。
      posted @ 2025-04-01 18:53  安洛8  閱讀(154)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 91久久国产成人免费观看| 国产一区二区三区韩国| 色窝窝免费一区二区三区| 孟村| 国产无遮挡无码视频在线观看| 欧美大胆老熟妇乱子伦视频| 成人污视频| 国产精品日日摸夜夜添夜夜添无码 | 天天爽天天摸天天碰| 国产精品中文字幕一区| 欧美人与动zozo在线播放| 欧美熟妇乱子伦XX视频| 成全高清在线播放电视剧| 早起邻居人妻奶罩太松av| 九九热在线视频观看最新| 亚洲精品成人A在线观看| 久热伊人精品国产中文| 亚洲a片无码一区二区蜜桃| 熟女视频一区二区三区嫩草| 色www视频永久免费| 祁东县| 在线观看热码亚洲av每日更新| 人妻饥渴偷公乱中文字幕| 成人拍拍拍无遮挡免费视频| 亚洲国产中文字幕在线视频综合| 色一乱一伦一图一区二区精品| 夜夜添狠狠添高潮出水| 精品国产精品中文字幕| 又大又硬又爽免费视频| 亚洲精品无码久久一线| 亚洲精品喷潮一区二区三区| 亚洲无av中文字幕在线| 亚洲中文字幕五月五月婷| 国产av永久无码天堂影院| 午夜福利国产精品视频| 精品国精品国自产在国产| 麻花传媒免费网站在线观看| 精品国产成人国产在线观看| 精品久久久久久无码人妻蜜桃| 国产欲女高潮正在播放| 99福利一区二区视频|