Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/video/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Vector Cuda-每个向量元素中的多个和_Vector_Cuda_Sum_Reduction - Fatal编程技术网

Vector Cuda-每个向量元素中的多个和

Vector Cuda-每个向量元素中的多个和,vector,cuda,sum,reduction,Vector,Cuda,Sum,Reduction,两系列切比雪夫多项式与系数a和b的乘积可用公式表示 问题是尽可能多地将其并行化 我已经成功地使用cuda通过简单地为每个向量元素应用一个线程来并行化上面的公式。因此,一个线程执行求和/乘法 #include <stdio.h> #include <iostream> #include <cuda.h> #include <time.h> __global__ void chebyprod(int n, float *a, float *b, f

两系列切比雪夫多项式与系数a和b的乘积可用公式表示

问题是尽可能多地将其并行化

我已经成功地使用cuda通过简单地为每个向量元素应用一个线程来并行化上面的公式。因此,一个线程执行求和/乘法

#include <stdio.h>
#include <iostream>
#include <cuda.h>
#include <time.h>

__global__ void chebyprod(int n, float *a, float *b, float *c){
   int i = blockIdx.x *blockDim.x + threadIdx.x;
   float sum;
   if (i < n) {
      sum = 0.f;
      for (int j = 0; j<=i; j++){
         sum += a[j]*b[j-i];
      }
      for (int j = 1; j < n-i; j++){
         sum += a[j]*b[j+i]+a[j+i]*b[j];
      }
      c[i] = 0.5f*sum;
   }
   /*
   if (i < n)
      c[i] = a[i] + b[i];
   */  
}

int main(void){
  clock_t tStart = clock();
  int N = 10000;
  float *a, *b, *c, *d_a, *d_b, *d_c;
  a = (float*)malloc(N*sizeof(float));
  b = (float*)malloc(N*sizeof(float));
  c = (float*)malloc(N*sizeof(float));

  cudaMalloc(&d_a, N*sizeof(float)); 
  cudaMalloc(&d_b, N*sizeof(float));
  cudaMalloc(&d_c, N*sizeof(float));

  for (int i = 0; i < N; i++) {
    a[i] = 0.1f;
    b[i] = 0.2f;
  }

  cudaMemcpy(d_a, a, N*sizeof(float), cudaMemcpyHostToDevice);
  cudaMemcpy(d_b, b, N*sizeof(float), cudaMemcpyHostToDevice);

  int blockSize, gridSize;
  // Number of threads in each thread block
  blockSize = 1024;

  // Number of thread blocks in grid
  gridSize = (int)ceil((float)N/blockSize);

  std::cout << "blockSize: " << blockSize << "\ngridSize: " << gridSize << "\n";

  // Perform chebyprod on N elements
  chebyprod<<< gridSize, blockSize >>>(N, d_a, d_b, d_c);
  printf("Time taken: %.2fs\n", (double)(clock() - tStart)/CLOCKS_PER_SEC);

  cudaMemcpy(c, d_c, N*sizeof(float), cudaMemcpyDeviceToHost);

  std::cout << "Vector c: [ ";
  for (int k = 0; k < 10; ++k)
    std::cout << c[k] << " ";
  std::cout <<"]\n";

  cudaFree(d_a);
  cudaFree(d_b);
  cudaFree(d_c);
  free(a);
  free(b);
  free(c);
}
#包括
#包括
#包括
#包括
__全局无效chebyprod(整数n,浮点*a,浮点*b,浮点*c){
int i=blockIdx.x*blockDim.x+threadIdx.x;
浮点数;
if(i对于(int j=0;j问题中提供的代码是实现的明智的第一步。线程策略是最常见/最典型的:为每个输出点分配一个线程(
N
output points)。每个线程必须执行计算特定输出点所需的所有计算。提高CUDA代码性能的动机应始终解决至少2个CUDA优化优先级:

  • 公开足够的并行性(大致:创建足够的线程)
  • 有效利用内存(大致上:对于全局内存访问,争取合并)
  • 关于第1项,问题中提供的代码的有效性将取决于GPU。根据粗略的经验,我们希望在我们运行的GPU中,每个SM至少启动2048个线程(图灵上1024个线程),以便有机会“饱和”GPU。对于
    N
    =10000,我们可以用5条短消息使GPU饱和。对于特斯拉V100,用80条短消息,我们不希望用10000条线程使GPU饱和

    关于第2项,所提供的代码在某种程度上也有不足之处;在合并方面存在问题:在许多情况下,相邻线程没有读取内存中的相邻值。仅举一个例子,我看到的第一个全局加载是
    a[j]
    。这是指每个线程加载相同的值/位置,而不是相邻线程中的相邻值

    我们能想出一个替代的实现吗?可能会在这两个方面都有所改进。我们将考虑线程策略的以下变化:分配一个“强> thRead Buth/<强>每个输出点,而不是每个输出点的一个线程。每个输出点所需的计算将被可视化为一行”。指矩阵。螺纹块将“跨步”沿着该行,执行所需的计算,并最终执行threadblock级别的缩减,以生成该行的单个结果。这将允许我们解决这两个问题:扭曲中的相邻线程将能够从

    a
    b
    读取相邻值,并且我们还将立即能够增加我们的总数线程数增加到1024倍(因此,我们可以增加约1000万个线程,而不是1万个线程。1000万个线程足以使任何当前CUDA GPU饱和)。此线程策略还有另一个很好的特性:“行”上述计算具有不同的长度,第一行和最后一行将是最长的,大约有“代码>N< /代码>计算元素,而中间的行将更接近于<代码> N/2</Calp>计算元素。我们可以有效地处理不同的行长度。每个线程块将沿着行“跨步”,仅在需要时,累积结果

    以下是实现这一点的一个工作示例:

    $ cat t1497.cu
    #include <stdio.h>
    #include <iostream>
    #include <cuda.h>
    typedef float mt;
    #include <time.h>
    #include <sys/time.h>
    #define USECPSEC 1000000ULL
    const bool sync = true;
    const bool nosync = false;
    unsigned long long dtime_usec(unsigned long long start, bool use_sync = nosync){
      if (use_sync == sync) cudaDeviceSynchronize();
      timeval tv;
      gettimeofday(&tv, 0);
      return ((tv.tv_sec*USECPSEC)+tv.tv_usec)-start;
    }
    __global__ void chebyprod(int n, const mt * __restrict__ a, const mt * __restrict__ b, mt * __restrict__ c){
       int i = blockIdx.x *blockDim.x + threadIdx.x;
       mt sum;
       if (i < n) {
          sum = 0.f;
          for (int j = 0; j<=i; j++){
             sum += a[j]*b[i-j];
          }
          for (int j = 1; j < n-i; j++){
             sum += a[j]*b[j+i]+a[j+i]*b[j];
          }
          c[i] = 0.5f*sum;
       }
    }
    // assume one threadblock per c_k coefficient
    // assume a power-of-2 threadblock size
    const int tpb_p2 = 8;
    const int nTPB = 1<<tpb_p2;
    const unsigned row_mask = ~((0xFFFFFFFFU>>tpb_p2)<<tpb_p2);
    
    __global__ void chebyprod_imp(int n, const mt * __restrict__ a, const mt * __restrict__ b, mt * __restrict__ c){
    #ifndef NO_WS
      __shared__ mt sd[32];
      if (threadIdx.x < 32) sd[threadIdx.x] = 0;
      __syncthreads();
    #else
      __shared__ mt sd[nTPB];
    #endif
      int k = blockIdx.x;
      mt sum = 0.0f;
      int row_width = (((k)>(n-k))?(k):(n-k))+1;
      int strides = (row_width>>tpb_p2)+ ((row_width&row_mask)?1:0);
      int j = threadIdx.x;
      mt tmp_a;
      for (int s=0; s < strides; s++){ // block-stride loop
        if (j < n) tmp_a = a[j];
        if (j <= k) sum += tmp_a*b[k-j];
        if ((j > 0) && (j < (n-k))) sum += tmp_a*b[j+k] + a[j+k]*b[j];
        j += nTPB;
        }
    #ifndef NO_WS
      // 1st warp-shuffle reduction
      int lane = threadIdx.x & (warpSize-1);
      int warpID = threadIdx.x >> 5; // assumes warpSize == 32
      unsigned mask = 0xFFFFFFFFU;
      for (int offset = warpSize>>1; offset > 0; offset >>= 1)
        sum += __shfl_down_sync(mask, sum, offset);
      if (lane == 0) sd[warpID] = sum;
      __syncthreads(); // put warp results in shared mem
      // hereafter, just warp 0
      if (warpID == 0){
      // reload val from shared mem if warp existed
        sum = sd[lane];
      // final warp-shuffle reduction
        for (int offset = warpSize>>1; offset > 0; offset >>= 1)
          sum += __shfl_down_sync(mask, sum, offset);
      }
    #else
      sd[threadIdx.x] = sum;
      for (int s = nTPB>>1; s > 0; s>>=1){ // sweep reduction
        __syncthreads();
        if (threadIdx.x < s) sd[threadIdx.x] += sd[threadIdx.x+s];}
      if (!threadIdx.x) sum = sd[0];
    #endif
      if (!threadIdx.x) c[k] = sum*0.5f;
    }
    
    int main(int argc, char *argv[]){
      int N = 10000;
      if (argc>1) N = atoi(argv[1]);
      std::cout << "N = " << N << std::endl;
      mt *a, *b, *c, *ic, *d_a, *d_b, *d_c;
      a  = (mt*)malloc(N*sizeof(mt));
      b  = (mt*)malloc(N*sizeof(mt));
      c  = (mt*)malloc(N*sizeof(mt));
      ic = (mt*)malloc(N*sizeof(mt));
    
      cudaMalloc(&d_a, N*sizeof(mt));
      cudaMalloc(&d_b, N*sizeof(mt));
      cudaMalloc(&d_c, N*sizeof(mt));
    
      for (int i = 0; i < N; i++) {
        a[i] = 0.1f;
        b[i] = 0.2f;
      }
    
      cudaMemcpy(d_a, a, N*sizeof(mt), cudaMemcpyHostToDevice);
      cudaMemcpy(d_b, b, N*sizeof(mt), cudaMemcpyHostToDevice);
      int blockSize, gridSize;
      // Number of threads in each thread block
      blockSize = 1024;
    
      // Number of thread blocks in grid
      gridSize = (int)ceil((float)N/blockSize);
    
      std::cout << "blockSize: " << blockSize << "\ngridSize: " << gridSize << "\n";
    
      // Perform chebyprod on N elements
      unsigned long long  dt = dtime_usec(0);
      chebyprod<<< gridSize, blockSize >>>(N, d_a, d_b, d_c);
      dt = dtime_usec(dt,sync);
    
      cudaMemcpy(c, d_c, N*sizeof(mt), cudaMemcpyDeviceToHost);
      printf("Time taken: %fs\n", dt/(float)USECPSEC);
      std::cout << cudaGetErrorString(cudaGetLastError()) << std::endl;
      std::cout << "Vector c: [ ";
      for (int k = 0; k < 10; ++k)
        std::cout << c[k] << " ";
      std::cout <<"]\n";
      dt = dtime_usec(0);
      chebyprod_imp<<< N, nTPB >>>(N, d_a, d_b, d_c);
      dt = dtime_usec(dt,sync);
      cudaMemcpy(ic, d_c, N*sizeof(mt), cudaMemcpyDeviceToHost);
      printf("Time taken: %fs\n", dt/(float)USECPSEC);
      std::cout << cudaGetErrorString(cudaGetLastError()) << std::endl;
      std::cout << "Vector c: [ ";
      for (int k = 0; k < 10; ++k)
        std::cout << ic[k] << " ";
      std::cout <<"]\n";
      mt max_error = 0;
      for (int k = 0; k < N; k++)
        max_error = fmax(max_error, fabs(c[k] - ic[k]));
      std::cout << "Max error = " << max_error << std::endl;
      cudaFree(d_a);
      cudaFree(d_b);
      cudaFree(d_c);
      free(a);
      free(b);
      free(c);
      free(ic);
    }
    $ nvcc -arch=sm_52 -o t1497 t1497.cu
    $ ./t1497
    blockSize: 1024
    gridSize: 10
    Time taken: 0.001687s
    no error
    Vector c: [ 199.996 199.986 199.976 199.966 199.956 199.946 199.936 199.926 199.916 199.906 ]
    Time taken: 0.000350s
    no error
    Vector c: [ 199.99 199.98 199.97 199.96 199.95 199.94 199.93 199.92 199.91 199.9 ]
    Max error = 0.0137787
    $
    

    请将您的代码添加到问题中。我添加了我的代码和用于另一代码中求和的代码函数uu global。主要部分类似。您是否有c_k公式的参考资料?找到c_k的一些递归关系或至少分别为公式中的每个和找到这些关系会很有趣。这将允许使用相同的公式或者与并行计算类似的方法。FWIW,内核执行越界数组访问。使用
    cuda memcheck
    运行代码。并且您的计时度量可能没有度量您认为它是什么。这行内核代码似乎是错误的:
    sum+=a[j]*b[j-i];
    ,我认为应该是:
    sum+=a[j]*b[I-j]
    。这似乎与公式相符,也使内核越界访问消失。我认为线程总数可能会饱和的另一种方式可能是动态并行。这与您的方法不一样吗?我还没有让它工作,因为我还不太了解父线程和子线程之间的关系儿童内存。我删除了关于合并的第一个问题。我想我现在明白了,但是我正在努力弄清楚为什么stride内核比单片内核慢。有什么想法吗?我使用的gpu是gtx 960m。我可以访问更好的gpu,但它应该(据我估计)跨步代码更快。正在使用更多的线程,但共享内存可能会减慢速度?回头看我的回答,GTX 960M有5条SMs。这意味着10000个线程可以完全饱和。额外的线程对第1项没有帮助。建议“更多线程=更快的代码”不总是正确的。此外,这里的输入数据是80k字节。这很小,可以放在GPU缓存中。因此,低效的内存访问问题要小得多(第2项)。另一方面,“改进的”内核有一些开销。并行缩减并不比单个线程的缩减更有效。我在回答中添加了一些文本来解决这个问题。从实验上看,似乎将
    tpb_p2
    设置为8(这将跨行内核的线程块大小设置为256)似乎在较小的GPU上为跨步内核提供了最好的性能。这可能会使您接近奇偶校验,但当我在GTX960上测试跨步内核时,跨步内核的速度并不比您原来的快,
    tpb_p2
    设置为8。GTX960有8条SMs。我没有GTX960M可供测试。
    $ cat t1497.cu
    #include <stdio.h>
    #include <iostream>
    #include <cuda.h>
    typedef float mt;
    #include <time.h>
    #include <sys/time.h>
    #define USECPSEC 1000000ULL
    const bool sync = true;
    const bool nosync = false;
    unsigned long long dtime_usec(unsigned long long start, bool use_sync = nosync){
      if (use_sync == sync) cudaDeviceSynchronize();
      timeval tv;
      gettimeofday(&tv, 0);
      return ((tv.tv_sec*USECPSEC)+tv.tv_usec)-start;
    }
    __global__ void chebyprod(int n, const mt * __restrict__ a, const mt * __restrict__ b, mt * __restrict__ c){
       int i = blockIdx.x *blockDim.x + threadIdx.x;
       mt sum;
       if (i < n) {
          sum = 0.f;
          for (int j = 0; j<=i; j++){
             sum += a[j]*b[i-j];
          }
          for (int j = 1; j < n-i; j++){
             sum += a[j]*b[j+i]+a[j+i]*b[j];
          }
          c[i] = 0.5f*sum;
       }
    }
    // assume one threadblock per c_k coefficient
    // assume a power-of-2 threadblock size
    const int tpb_p2 = 8;
    const int nTPB = 1<<tpb_p2;
    const unsigned row_mask = ~((0xFFFFFFFFU>>tpb_p2)<<tpb_p2);
    
    __global__ void chebyprod_imp(int n, const mt * __restrict__ a, const mt * __restrict__ b, mt * __restrict__ c){
    #ifndef NO_WS
      __shared__ mt sd[32];
      if (threadIdx.x < 32) sd[threadIdx.x] = 0;
      __syncthreads();
    #else
      __shared__ mt sd[nTPB];
    #endif
      int k = blockIdx.x;
      mt sum = 0.0f;
      int row_width = (((k)>(n-k))?(k):(n-k))+1;
      int strides = (row_width>>tpb_p2)+ ((row_width&row_mask)?1:0);
      int j = threadIdx.x;
      mt tmp_a;
      for (int s=0; s < strides; s++){ // block-stride loop
        if (j < n) tmp_a = a[j];
        if (j <= k) sum += tmp_a*b[k-j];
        if ((j > 0) && (j < (n-k))) sum += tmp_a*b[j+k] + a[j+k]*b[j];
        j += nTPB;
        }
    #ifndef NO_WS
      // 1st warp-shuffle reduction
      int lane = threadIdx.x & (warpSize-1);
      int warpID = threadIdx.x >> 5; // assumes warpSize == 32
      unsigned mask = 0xFFFFFFFFU;
      for (int offset = warpSize>>1; offset > 0; offset >>= 1)
        sum += __shfl_down_sync(mask, sum, offset);
      if (lane == 0) sd[warpID] = sum;
      __syncthreads(); // put warp results in shared mem
      // hereafter, just warp 0
      if (warpID == 0){
      // reload val from shared mem if warp existed
        sum = sd[lane];
      // final warp-shuffle reduction
        for (int offset = warpSize>>1; offset > 0; offset >>= 1)
          sum += __shfl_down_sync(mask, sum, offset);
      }
    #else
      sd[threadIdx.x] = sum;
      for (int s = nTPB>>1; s > 0; s>>=1){ // sweep reduction
        __syncthreads();
        if (threadIdx.x < s) sd[threadIdx.x] += sd[threadIdx.x+s];}
      if (!threadIdx.x) sum = sd[0];
    #endif
      if (!threadIdx.x) c[k] = sum*0.5f;
    }
    
    int main(int argc, char *argv[]){
      int N = 10000;
      if (argc>1) N = atoi(argv[1]);
      std::cout << "N = " << N << std::endl;
      mt *a, *b, *c, *ic, *d_a, *d_b, *d_c;
      a  = (mt*)malloc(N*sizeof(mt));
      b  = (mt*)malloc(N*sizeof(mt));
      c  = (mt*)malloc(N*sizeof(mt));
      ic = (mt*)malloc(N*sizeof(mt));
    
      cudaMalloc(&d_a, N*sizeof(mt));
      cudaMalloc(&d_b, N*sizeof(mt));
      cudaMalloc(&d_c, N*sizeof(mt));
    
      for (int i = 0; i < N; i++) {
        a[i] = 0.1f;
        b[i] = 0.2f;
      }
    
      cudaMemcpy(d_a, a, N*sizeof(mt), cudaMemcpyHostToDevice);
      cudaMemcpy(d_b, b, N*sizeof(mt), cudaMemcpyHostToDevice);
      int blockSize, gridSize;
      // Number of threads in each thread block
      blockSize = 1024;
    
      // Number of thread blocks in grid
      gridSize = (int)ceil((float)N/blockSize);
    
      std::cout << "blockSize: " << blockSize << "\ngridSize: " << gridSize << "\n";
    
      // Perform chebyprod on N elements
      unsigned long long  dt = dtime_usec(0);
      chebyprod<<< gridSize, blockSize >>>(N, d_a, d_b, d_c);
      dt = dtime_usec(dt,sync);
    
      cudaMemcpy(c, d_c, N*sizeof(mt), cudaMemcpyDeviceToHost);
      printf("Time taken: %fs\n", dt/(float)USECPSEC);
      std::cout << cudaGetErrorString(cudaGetLastError()) << std::endl;
      std::cout << "Vector c: [ ";
      for (int k = 0; k < 10; ++k)
        std::cout << c[k] << " ";
      std::cout <<"]\n";
      dt = dtime_usec(0);
      chebyprod_imp<<< N, nTPB >>>(N, d_a, d_b, d_c);
      dt = dtime_usec(dt,sync);
      cudaMemcpy(ic, d_c, N*sizeof(mt), cudaMemcpyDeviceToHost);
      printf("Time taken: %fs\n", dt/(float)USECPSEC);
      std::cout << cudaGetErrorString(cudaGetLastError()) << std::endl;
      std::cout << "Vector c: [ ";
      for (int k = 0; k < 10; ++k)
        std::cout << ic[k] << " ";
      std::cout <<"]\n";
      mt max_error = 0;
      for (int k = 0; k < N; k++)
        max_error = fmax(max_error, fabs(c[k] - ic[k]));
      std::cout << "Max error = " << max_error << std::endl;
      cudaFree(d_a);
      cudaFree(d_b);
      cudaFree(d_c);
      free(a);
      free(b);
      free(c);
      free(ic);
    }
    $ nvcc -arch=sm_52 -o t1497 t1497.cu
    $ ./t1497
    blockSize: 1024
    gridSize: 10
    Time taken: 0.001687s
    no error
    Vector c: [ 199.996 199.986 199.976 199.966 199.956 199.946 199.936 199.926 199.916 199.906 ]
    Time taken: 0.000350s
    no error
    Vector c: [ 199.99 199.98 199.97 199.96 199.95 199.94 199.93 199.92 199.91 199.9 ]
    Max error = 0.0137787
    $
    
    #include <stdio.h>
    #include <iostream>
    #include <cuda.h>
    typedef float mt;
    #include <time.h>
    #include <sys/time.h>
    #define USECPSEC 1000000ULL
    const bool sync = true;
    const bool nosync = false;
    unsigned long long dtime_usec(unsigned long long start, bool use_sync = nosync){
      if (use_sync == sync) cudaDeviceSynchronize();
      timeval tv;
      gettimeofday(&tv, 0);
      return ((tv.tv_sec*USECPSEC)+tv.tv_usec)-start;
    }
    __global__ void chebyprod(int n, const mt * __restrict__ a, const mt * __restrict__ b, mt * __restrict__ c){
       int i = blockIdx.x *blockDim.x + threadIdx.x;
       mt sum;
       if (i < n) {
          sum = 0.f;
          for (int j = 0; j<=i; j++){
             sum += a[j]*b[i-j];
          }
          for (int j = 1; j < n-i; j++){
             sum += a[j]*b[j+i]+a[j+i]*b[j];
          }
          c[i] = 0.5f*sum;
       }
    }
    // assume one warp per c_k coefficient
    // assume a multiple-of-32 threadblock size
    const int nTPB = 32*8;
    const int warpSize_p2 = 5; // assumes warpSize == 32
    const int nWarps = nTPB>>warpSize_p2;
    const unsigned row_mask = ~((0xFFFFFFFFU>>warpSize_p2)<<warpSize_p2);
    __global__ void chebyprod_imp(int n, const mt * __restrict__ a, const mt * __restrict__ b, mt * __restrict__ c){
      int warpID = threadIdx.x >> warpSize_p2;
      int k = blockIdx.x*(nWarps)+warpID;
      if (k < n){
        mt sum = 0.0f;
        int lane = threadIdx.x & (warpSize-1);
        int row_width = (((k)>(n-k))?(k):(n-k))+1;
        int strides = (row_width>>warpSize_p2)+ ((row_width&row_mask)?1:0);
        int j = lane;
        mt tmp_a;
        for (int s=0; s < strides; s++){ // warp-stride loop
          if (j < n) tmp_a = a[j];
          if (j <= k) sum += tmp_a*b[k-j];
          if ((j > 0) && (j < (n-k))) sum += tmp_a*b[j+k] + a[j+k]*b[j];
          j += warpSize;
          }
      // warp-shuffle reduction
        for (int offset = warpSize>>1; offset > 0; offset >>= 1)
          sum += __shfl_down_sync(0xFFFFFFFFU, sum, offset);
        if (lane==0) c[k] = sum*0.5f;}
    }
    
    int main(int argc, char *argv[]){
      int N = 10000;
      if (argc>1) N = atoi(argv[1]);
      std::cout << "N = " << N << std::endl;
      mt *a, *b, *c, *ic, *d_a, *d_b, *d_c;
      a  = (mt*)malloc(N*sizeof(mt));
      b  = (mt*)malloc(N*sizeof(mt));
      c  = (mt*)malloc(N*sizeof(mt));
      ic = (mt*)malloc(N*sizeof(mt));
    
      cudaMalloc(&d_a, N*sizeof(mt));
      cudaMalloc(&d_b, N*sizeof(mt));
      cudaMalloc(&d_c, N*sizeof(mt));
    
      for (int i = 0; i < N; i++) {
        a[i] = 0.1f;
        b[i] = 0.2f;
      }
    
      cudaMemcpy(d_a, a, N*sizeof(mt), cudaMemcpyHostToDevice);
      cudaMemcpy(d_b, b, N*sizeof(mt), cudaMemcpyHostToDevice);
      int blockSize, gridSize;
      // Number of threads in each thread block
      blockSize = 1024;
    
      // Number of thread blocks in grid
      gridSize = (int)ceil((float)N/blockSize);
    
      std::cout << "blockSize: " << blockSize << "\ngridSize: " << gridSize << "\n";
    
      // Perform chebyprod on N elements
      unsigned long long  dt = dtime_usec(0);
      chebyprod<<< gridSize, blockSize >>>(N, d_a, d_b, d_c);
      dt = dtime_usec(dt,sync);
    
      cudaMemcpy(c, d_c, N*sizeof(mt), cudaMemcpyDeviceToHost);
      printf("Time taken: %fs\n", dt/(float)USECPSEC);
      std::cout << cudaGetErrorString(cudaGetLastError()) << std::endl;
      std::cout << "Vector c: [ ";
      for (int k = 0; k < 10; ++k)
        std::cout << c[k] << " ";
      std::cout <<"]\n";
      dt = dtime_usec(0);
      chebyprod_imp<<< (N/nWarps)+1, nTPB >>>(N, d_a, d_b, d_c);
      dt = dtime_usec(dt,sync);
      cudaMemcpy(ic, d_c, N*sizeof(mt), cudaMemcpyDeviceToHost);
      printf("Time taken: %fs\n", dt/(float)USECPSEC);
      std::cout << cudaGetErrorString(cudaGetLastError()) << std::endl;
      std::cout << "Vector c: [ ";
      for (int k = 0; k < 10; ++k)
        std::cout << ic[k] << " ";
      std::cout <<"]\n";
      mt max_error = 0;
      for (int k = 0; k < N; k++)
        max_error = fmax(max_error, fabs(c[k] - ic[k]));
      std::cout << "Max error = " << max_error << std::endl;
      cudaFree(d_a);
      cudaFree(d_b);
      cudaFree(d_c);
      free(a);
      free(b);
      free(c);
      free(ic);
    }