C++ 将函子应用于设备数组子集的最有效方法是什么?

C++ 将函子应用于设备数组子集的最有效方法是什么?,c++,cuda,thrust,C++,Cuda,Thrust,我正在重写一个库,该库对存储在连续内存块中的数据执行计算和其他操作,以便它可以使用CUDA框架在GPU上工作。数据表示生活在四维网格上的信息。栅格的总大小可以从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 现在,我所做的是通过嵌套循环

我正在重写一个库,该库对存储在连续内存块中的数据执行计算和其他操作,以便它可以使用CUDA框架在GPU上工作。数据表示生活在四维网格上的信息。栅格的总大小可以从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

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

{
纽约、新西兰、新西兰、新西兰国际机场;
nx=10,ny=10,nz=10,nq=10;
typedef推力::设备_矢量阵列;
阵列A(nx*ny*nz*nq);
推力:填充(A.开始(),A.结束(),(双)1);

对于(auto q=1;q我不确定最有效的方法是什么。我可以建议我认为比您的框架代码更有效的方法

您在本文中的建议方向正确。与其使用一组嵌套for循环(可能会重复多次),我们应该设法在一次推力调用中完成所有操作。但我们仍然需要一次推力调用仅修改“立方体”中索引处的数组值要操作的卷

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

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

下面的示例显示了嵌套循环方法(1)与我的方法(2)在64x64x64x64数组和62x62x62x62修改卷的计时方面的差异:

$ 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
$
$cat t39.cu
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
结构我的idx
{
int nx、ny、nz、nq、lx、ly、lz、lq、dx、dy、dz、dq;
我的idx(整数nx,整数ny,整数nz,整数nq,整数lx,整数ly,整数lz,整数lq,整数hx,整数hy,整数hz,整数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;
//可以在这里进行大量的断言检查
}
__主机设备__
int运算符()(int idx){
int rx=idx/dx;
int ix=idx-(rx*dx);
int ry=rx/dy;
int-iy=rx-(y*dy);
int rz=ry/dz;
int-iz=ry-(rz*dz);
int rq=rz/dq;
int iq=rz-(rq*dq);
返回(((iq+lq)*nz+iz+lz)*ny+iy+ly)*nx+ix+lx;
}
};
#包括
#包括
#定义USECPSEC 10000000ull
无符号长时间dtime\u usec(无符号长时间启动){
蒂梅瓦尔电视;
gettimeofday(&tv,0);
返回((tv.tv_sec*USECPSEC)+tv.tv_usec)-开始;
}
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;
推力:装置_矢量A(nx*ny*nz*nq);
推力:设备_矢量B(nx*ny*nz*nq);
推力:填充(A.开始(),A.结束(),(双)1);
推力:填充(B.开始(),B.结束(),(双)1);
//方法1
无符号长m1_时间=dtime_usec(0);

对于(auto q=lq;q我不确定最有效的方法是什么。我可以建议我认为比您的框架代码更有效的方法

您在本文中的建议方向正确。与其使用一组嵌套for循环(可能会重复多次),我们应该设法在一次推力调用中完成所有操作。但我们仍然需要一次推力调用仅修改“立方体”中索引处的数组值要操作的卷

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

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

下面的示例显示了嵌套循环方法(1)与我的方法(2)在64x64x64x64数组和62x62x62x62修改卷的计时方面的差异:

$ 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
$
$cat t39.cu
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
结构我的idx
{
int nx、ny、nz、nq、lx、ly、lz、lq、dx、dy、dz、dq;
我的idx(整数nx,整数ny,整数nz,整数nq,整数lx,整数ly,整数lz,整数lq,整数hx,整数hy,整数hz,整数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;
//可以在这里进行大量的断言检查
}
__主机设备__
int运算符()(int idx){
int rx=idx/dx;