我有一个递归数组定义。 让它成为
A(x, y + 1) = f(A(x - 1, y), A(x, y), A(x + 1, y))
初始化第一层
A(x, 0) = g(x)
我想使用CUDA逐层计算这样的数组。问题是做这些事情的首选方式是什么。单个内核是否应在A(tid, y)
中为y
计算每个步骤同步的[1, height)
数组{{1}}?或者它应该只计算单点,但多次调用?或者将问题分解成更大的独立部分可能更好?例如。如果完成前一层rhombs,则可以通过rhombs分割这个数组,使得每个整个菱形都可以独立计算(在rhomb内部没有同步)。
如果图层是2D而不是1D,情况会变得不同吗?
我打算计算这样一个宽度为~10000(可能更少就足够了)和每秒高度44100的数组。问题实际上是3D(200x50x44100),如果重要的话。我只是为了简单而将其制定为2D。
答案 0 :(得分:3)
直截了当的方法可能只是从你在这里概述的内容开始:
"单个内核是否应计算每个步骤同步的[1,高度] y的数组A(tid,y)?"
这应该很容易实现。
x"宽度"为了保持GPU在这么多线程中的合理忙碌,10,000个人在球场上。
对于复杂的f()
函数,能够每秒执行44100次迭代(平均迭代时间约为22 us)可能具有挑战性。但是,对于一个相当简单的f()
函数,似乎可以基于我的快速测试。通过像这样迭代地启动内核这一事实我们受益,大部分内核启动开销都被隐藏了。
这是一个示例代码,旨在展示概念证明:
$ cat t708.cu
#include <thrust/device_vector.h>
#include <thrust/host_vector.h>
#include <thrust/for_each.h>
#include <thrust/iterator/zip_iterator.h>
#include <thrust/copy.h>
#include <stdlib.h>
#include <iostream>
#define DSIZE 10000
#define YSIZE 2
#define NUM_ITER 44100
#define AVG_SIZE 3
#define DISP_WIDTH 5
struct f
{
template <typename T>
__host__ __device__
void operator()(T t) {
thrust::get<AVG_SIZE>(t) = thrust::get<0>(t);
thrust::get<AVG_SIZE>(t) += thrust::get<1>(t);
thrust::get<AVG_SIZE>(t) += thrust::get<2>(t);
thrust::get<AVG_SIZE>(t) /= AVG_SIZE;}
};
int main(){
thrust::host_vector<float> h_A(DSIZE);
for (int i =0; i < DSIZE; i++) h_A[i] = rand()/(float)RAND_MAX; // A(x, 0) = g(x)
thrust::device_vector<float> d_A[YSIZE];
d_A[0].resize(h_A.size());
d_A[1].resize(h_A.size());
thrust::copy(h_A.begin(), h_A.end(), d_A[0].begin());
thrust::copy(h_A.begin(), h_A.end(), d_A[1].begin());
std::cout << "input left end: " << std::endl;
thrust::copy(d_A[0].begin(), d_A[0].begin()+DISP_WIDTH, std::ostream_iterator<float>(std::cout, ","));
std::cout << std::endl << "input right end: " << std::endl;
thrust::copy(d_A[0].end() - DISP_WIDTH, d_A[0].end(), std::ostream_iterator<float>(std::cout, ","));
std::cout << std::endl;
cudaEvent_t start, stop;
cudaEventCreate(&start); cudaEventCreate(&stop);
int cur = 0;
int nxt = 1;
cudaEventRecord(start, 0);
for (int i = 0; i < NUM_ITER; i++){
thrust::for_each(thrust::make_zip_iterator(thrust::make_tuple(d_A[cur].begin(), d_A[cur].begin()+1, d_A[cur].begin()+2, d_A[nxt].begin()+1)), thrust::make_zip_iterator(thrust::make_tuple(d_A[cur].end()-2, d_A[cur].end()-1, d_A[cur].end(), d_A[nxt].end()-1)), f());
cur = (cur==0) ? 1:0; // modify for a full storage in y
nxt = (nxt==0) ? 1:0;}
cudaDeviceSynchronize();
cudaEventRecord(stop, 0);
cudaEventSynchronize(stop);
float et;
cudaEventElapsedTime(&et, start, stop);
std::cout << "elapsed time: " << et << "ms" << std::endl << "output left end: " << std::endl;
thrust::copy(d_A[cur].begin(), d_A[cur].begin()+DISP_WIDTH, std::ostream_iterator<float>(std::cout, ","));
std::cout << std::endl << "output right end: " << std::endl;
thrust::copy(d_A[cur].end() - DISP_WIDTH, d_A[cur].end(), std::ostream_iterator<float>(std::cout, ","));
std::cout << std::endl;
return 0;
}
$ nvcc -O3 -o t708 t708.cu
$ ./t708
input left end:
0.840188,0.394383,0.783099,0.79844,0.911647,
input right end:
0.865333,0.828169,0.311025,0.373209,0.888766,
elapsed time: 368.337ms
output left end:
0.840188,0.838681,0.837174,0.835667,0.83416,
output right end:
0.881355,0.883207,0.88506,0.886913,0.888766,
$
注意:
f()
函数(在本例中为仿函数)。这实际上是一种“放松”的形式。所以我们期望数据集的左端收敛到左端值(不会改变),数据集的右端收敛到右端值,并且之间有一条近似直线。thrust::for_each
的调用中。