将仿函数应用于设备阵列子集的最有效方法是什么?

问题描述 投票:-1回答:1

我正在重写一个库,该库对存储在连续内存块中的数据执行计算和其他操作,以便它可以使用CUDA框架在GPU上工作。数据表示生活在4维网格上的信息。网格的总大小可以从1000到数百万个网格点。沿着每个方向,网格可以具有少至8个或多达100个点。我的问题是什么是在网格子集上实现操作的最佳方法。例如,假设我的网格是[0,nx] x [0,ny)x [0,nz] x [0,nq],我想实现一个转换,它将索引所属的所有点相乘[1 ,nx-1)x [1,ny-1)x [1,nz-1)x [0,nq-1]减1。

现在,我所做的是通过嵌套循环。这是代码的骨架

{ 
int nx,ny,nz,nq;
nx=10,ny=10,nz=10,nq=10;
typedef thrust::device_vector<double> Array;
Array A(nx*ny*nz*nq);
thrust::fill(A.begin(), A.end(), (double) 1);

for (auto q=1; q<nq-1; ++q){
for (auto k=1; k<nz-1; ++k){
for (auto j=1; j<ny-1; ++j){
int offset1=+1+j*nx+k*nx*ny+q*nx*ny*nz;
int offset2=offset1+nx-2;
thrust::transform(A.begin()+offset1, 
                  A.begin()+offset2, 
                  thrust::negate<double>());
      }
    }
  }
}

但是,我想知道这是否是最有效的方式,因为在我看来,在这种情况下,最多只能同时运行nx-2个线程。所以我想也许更好的方法是生成一个序列迭代器(沿着数组返回线性位置),使用zip迭代器将其压缩到数组,并定义一个检查元组的第二个元素的仿函数(位置值)并且如果该值落入可接受的范围内,则修改元组的第一个元素。但是,可能有更好的方法。我是CUDA的新手,更糟糕的是我真的用Fortran切齿,所以我很难在for-loop框外思考......

c++ cuda thrust
1个回答
2
投票

我不确定什么是最有效的方式。我可以建议我认为比骨架代码更有效的东西。

您在案文中的提议正朝着正确的方向发展。我们应该设法在一次推力调用中完成所有操作,而不是使用一组嵌套的for循环,这些循环可能会迭代很多次。但是我们仍然需要让一个推力调用仅修改要操作的“立方”体积内的指数处的数组值。

我们不希望使用涉及对有效索引卷测试生成的索引的方法,但是,正如您似乎建议的那样。这将要求我们启动与我们的阵列一样大的网格,即使我们只想修改它的一小部分。

相反,我们启动一个足够大的操作来覆盖所需要修改的元素数量,并创建一个执行线性索引的算符 - > 4D索引 - >调整后的线性索引转换。该仿函数然后在变换迭代器中操作,以将从0,1,2等开始的普通线性序列转换为开始并保持在要修改的体积内的序列。然后使用置换迭代器与此修改序列一起选择要修改的数组的值。

下面是一个示例,显示了64x64x64x64数组和62x62x62x62修改后的嵌套循环方法(1)与mine(2)的时序差异:

$ cat t39.cu
#include <thrust/device_vector.h>
#include <thrust/transform.h>
#include <thrust/iterator/permutation_iterator.h>
#include <thrust/iterator/zip_iterator.h>
#include <thrust/iterator/counting_iterator.h>
#include <thrust/functional.h>
#include <thrust/equal.h>
#include <cassert>
#include <iostream>

struct my_idx
{
  int nx, ny, nz, nq, lx, ly, lz, lq, dx, dy, dz, dq;
  my_idx(int _nx, int _ny, int _nz, int _nq, int _lx, int _ly, int _lz, int _lq, int _hx, int _hy, int _hz, int _hq) {
    nx = _nx;
    ny = _ny;
    nz = _nz;
    nq = _nq;
    lx = _lx;
    ly = _ly;
    lz = _lz;
    lq = _lq;
    dx = _hx - lx;
    dy = _hy - ly;
    dz = _hz - lz;
    dq = _hq - lq;
    // could do a lot of assert checking here
  }

  __host__ __device__
  int operator()(int idx){
    int rx = idx / dx;
    int ix = idx - (rx * dx);
    int ry = rx / dy;
    int iy = rx - (ry * dy);
    int rz = ry / dz;
    int iz = ry - (rz * dz);
    int rq = rz / dq;
    int iq = rz - (rq * dq);
    return (((iq+lq)*nz+iz+lz)*ny+iy+ly)*nx+ix+lx;
  }
};

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

unsigned long long dtime_usec(unsigned long long start){

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


int main()
{
  int nx,ny,nz,nq,lx,ly,lz,lq,hx,hy,hz,hq;
  nx=64,ny=64,nz=64,nq=64;
  lx=1,ly=1,lz=1,lq=1;
  hx=nx-1,hy=ny-1,hz=nz-1,hq=nq-1;
  thrust::device_vector<double> A(nx*ny*nz*nq);
  thrust::device_vector<double> B(nx*ny*nz*nq);
  thrust::fill(A.begin(), A.end(), (double) 1);
  thrust::fill(B.begin(), B.end(), (double) 1);
  // method 1
  unsigned long long m1_time = dtime_usec(0);
  for (auto q=lq; q<hq; ++q){
    for (auto k=lz; k<hz; ++k){
      for (auto j=ly; j<hy; ++j){
        int offset1=lx+j*nx+k*nx*ny+q*nx*ny*nz;
        int offset2=offset1+(hx-lx);
        thrust::transform(A.begin()+offset1,
                  A.begin()+offset2, A.begin()+offset1,
                  thrust::negate<double>());
      }
    }
  }
  cudaDeviceSynchronize();
  m1_time = dtime_usec(m1_time);

  // method 2
  unsigned long long m2_time = dtime_usec(0);
  auto p = thrust::make_permutation_iterator(B.begin(), thrust::make_transform_iterator(thrust::counting_iterator<int>(0), my_idx(nx, ny, nz, nq, lx, ly, lz, lq, hx, hy, hz, hq)));
  thrust::transform(p, p+(hx-lx)*(hy-ly)*(hz-lz)*(hq-lq), p, thrust::negate<double>());
  cudaDeviceSynchronize();
  m2_time = dtime_usec(m2_time);
  if (thrust::equal(A.begin(), A.end(), B.begin()))
    std::cout << "method 1 time: " << m1_time/(float)USECPSEC << "s method 2 time: " << m2_time/(float)USECPSEC << "s" << std::endl;
  else
    std::cout << "mismatch error" << std::endl;
}
$ nvcc -std=c++11 t39.cu -o t39
$ ./t39
method 1 time: 1.6005s method 2 time: 0.013182s
$
© www.soinside.com 2019 - 2024. All rights reserved.