我刚刚开始学习CUDA,我对此感到困惑。为了争论,想象一下我在海洋里有几百个浮标。想象一下,他们每隔几毫秒间歇性地广播一个std :: vector。矢量可能是5个读数,或10个读数等,具体取决于当时海洋中的条件。没有办法告诉事件什么时候会发生,这不是确定性的。
想象一下,我有一个想法,我可以通过实时收集所有这些信息来预测温度,但是预测器必须首先对所有浮标上的所有std :: vectos进行排序。我的问题是这个。每次单个玩家发起活动时,是否必须将整个数据复制回GPU?由于其他浮标的数据没有改变,我可以将这些数据保留在GPU中并只更新已更改的内容并要求内核重新运行预测吗?
如果是,那么[推力伪]代码会是什么?最好用流和事件以及固定内存吗?对于使用实时数据更新GPU的速度有多大限制?
有人告诉我,这种问题并不适合GPU,也不适合FPGA。
答案 0 :(得分:2)
基本序列可能是这样的。
设置阶段(初始排序):
创建一组平行向量,每个浮标一个,长度等于浮标向量的初始长度,并由浮标索引填充:
b1: 1.5 1.7 2.2 2.3 2.6
i1: 1 1 1 1 1
b2: 2.4 2.5 2.6
i2: 2 2 2
b3: 2.8
i3: 3
将所有向量连接成一个浮标临时向量和浮标索引向量:
b: 1.5 1.7 2.2 2.3 2.6 2.4 2.5 2.6 2.8
i: 1 1 1 1 1 2 2 2 3
b: 1.5 1.7 2.2 2.3 2.4 2.5 2.6 2.6 2.8
i: 1 1 1 1 2 2 1 2 3
设置阶段已完成。每当收到浮标更新时,都会执行更新阶段。假设浮标2
发送更新:
b2: 2.5 2.7 2.9 3.0
如果相应的索引向量位置保持更新的浮标编号(在这种情况下为2
),请对浮标向量执行thrust::remove_if。使用相同的规则在索引向量上重复remove_if
:
b: 1.5 1.7 2.2 2.3 2.6 2.8
i: 1 1 1 1 1 3
为要更新的浮标生成相应的索引向量,并将两个向量(浮标2
临时值和索引向量)复制到设备:
b2: 2.5 2.7 2.9 3.0
i2: 2 2 2 2
对来自浮标2
b: 1.5 1.7 2.2 2.3 2.5 2.6 2.7 2.8 2.9 3.0
i: 1 1 1 1 2 1 2 3 2 2
在更新周期中必须复制到设备的唯一数据是要更新的实际浮标数据。注意,通过一些工作,可以消除设置阶段,并且矢量的初始组装可以仅被视为从每个浮标“更新”到初始空浮标值和浮标索引向量。但是对于描述,我认为使用设置阶段可以更容易地进行可视化。以上描述没有明确指出所需的各种矢量大小和调整,但这可以使用在std::vector
上使用的相同方法来实现。矢量大小调整可能在GPU上“代价高昂”,就像它在CPU上“昂贵”一样(如果调整大小会触发新的分配和复制......)但如果最大数量的浮标也可以消除这种情况已知并且每次更新的最大元素数是已知的。在这种情况下,我们可以将我们的整体浮标值和浮标索引向量分配为最大必要尺寸。
以下是遵循上述概要的完整工作示例。作为占位符,我添加了一个虚拟prediction_kernel
调用,显示您可以在哪里插入专门的预测代码,对已排序的数据进行操作。
#include <stdio.h>
#include <stdlib.h>
#include <thrust/host_vector.h>
#include <thrust/device_vector.h>
#include <thrust/sort.h>
#include <thrust/merge.h>
#include <sys/time.h>
#include <time.h>
#define N_BUOYS 1024
#define N_MAX_UPDATE 1024
#define T_RANGE 100
#define N_UPDATES_TEST 1000
struct equal_func{
const int idx;
equal_func(int _idx) : idx(_idx) {}
__host__ __device__
bool operator()(int test_val) {
return (test_val == idx);
}
};
__device__ float dev_result[N_UPDATES_TEST];
// dummy "prediction" kernel
__global__ void prediction_kernel(const float *data, int iter, size_t d_size){
int idx=threadIdx.x+blockDim.x*blockIdx.x;
if (idx == 0) dev_result[iter] = data[d_size/2];
}
void create_vec(unsigned int id, thrust::host_vector<float> &data, thrust::host_vector<int> &idx){
size_t mysize = rand()%N_MAX_UPDATE;
data.resize(mysize);
idx.resize(mysize);
for (int i = 0; i < mysize; i++){
data[i] = ((float)rand()/(float)RAND_MAX)*(float)T_RANGE;
idx[i] = id;}
thrust::sort(data.begin(), data.end());
}
int main(){
timeval t1, t2;
int pp = 0;
// ping-pong processing vectors
thrust::device_vector<float> buoy_data[2];
buoy_data[0].resize(N_BUOYS*N_MAX_UPDATE);
buoy_data[1].resize(N_BUOYS*N_MAX_UPDATE);
thrust::device_vector<int> buoy_idx[2];
buoy_idx[0].resize(N_BUOYS*N_MAX_UPDATE);
buoy_idx[1].resize(N_BUOYS*N_MAX_UPDATE);
// vectors for initial buoy data
thrust::host_vector<float> h_buoy_data[N_BUOYS];
thrust::host_vector<int> h_buoy_idx[N_BUOYS];
//SETUP
// populate initial data
int lidx=0;
for (int i = 0; i < N_BUOYS; i++){
create_vec(i, h_buoy_data[i], h_buoy_idx[i]);
thrust::copy(h_buoy_data[i].begin(), h_buoy_data[i].end(), &(buoy_data[pp][lidx]));
thrust::copy(h_buoy_idx[i].begin(), h_buoy_idx[i].end(), &(buoy_idx[pp][lidx]));
lidx+= h_buoy_data[i].size();}
// sort initial data
thrust::sort_by_key(&(buoy_data[pp][0]), &(buoy_data[pp][lidx]), &(buoy_idx[pp][0]));
//UPDATE CYCLE
gettimeofday(&t1, NULL);
for (int i = 0; i < N_UPDATES_TEST; i++){
unsigned int vec_to_update = rand()%N_BUOYS;
int nidx = lidx - h_buoy_data[vec_to_update].size();
create_vec(vec_to_update, h_buoy_data[vec_to_update], h_buoy_idx[vec_to_update]);
thrust::remove_if(&(buoy_data[pp][0]), &(buoy_data[pp][lidx]), buoy_idx[pp].begin(), equal_func(vec_to_update));
thrust::remove_if(&(buoy_idx[pp][0]), &(buoy_idx[pp][lidx]), equal_func(vec_to_update));
lidx = nidx + h_buoy_data[vec_to_update].size();
thrust::device_vector<float> temp_data = h_buoy_data[vec_to_update];
thrust::device_vector<int> temp_idx = h_buoy_idx[vec_to_update];
int ppn = (pp == 0)?1:0;
thrust::merge_by_key(&(buoy_data[pp][0]), &(buoy_data[pp][nidx]), temp_data.begin(), temp_data.end(), buoy_idx[pp].begin(), temp_idx.begin(), buoy_data[ppn].begin(), buoy_idx[ppn].begin() );
pp = ppn; // update ping-pong buffer index
prediction_kernel<<<1,1>>>(thrust::raw_pointer_cast(buoy_data[pp].data()), i, lidx);
}
gettimeofday(&t2, NULL);
unsigned int tdiff_us = ((t2.tv_sec*1000000)+t2.tv_usec) - ((t1.tv_sec*1000000)+t1.tv_usec);
printf("Completed %d updates in %f sec\n", N_UPDATES_TEST, (float)tdiff_us/(float)1000000);
// float *temps = (float *)malloc(N_UPDATES_TEST*sizeof(float));
// cudaMemcpyFromSymbol(temps, dev_result, N_UPDATES_TEST*sizeof(float));
// for (int i = 0; i < 100; i++) printf("temp %d: %f\n", i, temps[i]);
return 0;
}
在Linux上使用CUDA 6,在Quadro 5000 GPU上,1000次“更新”需要大约2秒钟。大部分时间花在thrust::remove_if
和thrust::merge_by_key
的调用上。我想最坏情况下的实时估算,你会想要尝试和最坏情况更新的时间,这可能是接收最长的更新。