以下数据处理任务是否适合GPU计算

Is the following data processing task suitable for GPU computing?

本文关键字:GPU 计算 是否 数据处理 任务      更新时间:2023-10-16

我希望升级我的显卡,以便能够并行处理以下任务。由于我没有GPU计算的经验,这个任务合适吗?在我购买之前,有可能估计处理的速度吗
我的项目是公共资助的,但预算有限,所以我需要做出正确的选择。

我有一个内部构建的相机芯片,可以以100fps的速度生成4x256x256图像。通过调用c函数来访问数据,并将指针传递给unsigned short类型的数组数据。我可以足够快地将数据读入内存缓冲区。目前,原始数据被保存到磁盘上,然后离线处理,但对于未来使用该相机的实验室实验,我希望在实验运行时访问从图像中获得的数据。我已经用c++编写了使用valarray的方法来计算导出的数据,但在我目前的硬件上,每帧大约40ms的速度太慢了。(我已经进行了优化实验,并将时间从>100ms大幅缩短)如果帧由S表示,则四个子帧(在时间上)是S1、S2、S3、S4。我必须计算以下图像和图像平均值,(S1+S2+S3+S4)/4,
Sqrt((S3-S1)^2+(S4-S2)^2),
arctan(S3-S1/S2-S4)

这似乎很适合由GPU执行操作。GPU比CPU更适合执行大量相对简单的计算。当"线程"之间存在逻辑或相互依存关系时,它们就没有那么高效了。尽管这种情况会进入"观点"领域,但我会尝试用一些数字来支持我的答案。

为了快速估计您所期望的性能,我制作了一个快速的HLSL像素着色器,它可以执行您建议的操作(未经测试-无法保证功能!):

Texture2D S[4] : register(t0);
SamplerState mySampler : register(s0);
struct PS_OUT
{
    float4 average : SV_Target0;
    float4 sqrt    : SV_Target1;
    float4 arctan  : SV_Target2;
};
PS_OUT main(float2 UV: TEXCOORD0)
{
    PS_OUT output;
    float4 SSamples[4];
    int i;
    for (i = 0; i < 4; i++)
    {
        SSamples[i] = S[i].Sample(mySampler, UV);
    }
    float4 s3ms1 = SSamples[2] - SSamples[0];
    float4 s4ms2 = SSamples[3] - SSamples[1];
    output.average = (SSamples[0] + SSamples[1] + SSamples[2] + SSamples[3]) / 4.0;
    output.sqrt    = sqrt(s3ms1*s3ms1 + s4ms2*s4ms2);
    output.arctan  = atan(s3ms1 / s4ms2);   
    return output;
}

当编译这个(fxc /T ps_4_0 example.ps)时,它给出了估计:大约使用了32个指令槽。

如果你每帧处理256x256(64k像素),那么在100fps的速度下,这大约是2.1米/帧,即210米/秒。查看GPU性能图表(例如,Nvidia:http://en.wikipedia.org/wiki/List_of_Nvidia_graphics_processing_units),他们所有超过Geforce 4(大约2005年)的GPU都有足够的速度来实现这一点。

请注意,这个着色器性能只是一个估计,列出的速率是理论上的最大值,我只考虑像素单位的工作(尽管它将完成大部分工作)。然而,对于任何足够新的视频卡,FLOPS都将远远超出您的需求,因此您应该能够在GPU上以100fps的速度轻松完成这项工作。假设你有一台比2005年更新的电脑,你可能已经有了足够强大的视频卡。

除了@MuertoExcobito已经写的内容外,您还必须考虑将数据复制到GPU和从GPU复制数据,但在您的情况下,这并不是太多数据。

我创建了一个简单的基于推力的实现,可以使用CUDA7编译和运行,如下所示:

nvcc -std=c++11 main.cu && ./a.out

在我的计算机上(Ubuntu 14.04 x64,IntelXeon@3.6Ghz、Geforce GTX 680)。


文件"helper_math.h"改编自CUDA SDK,可在以下位置找到:

https://gist.github.com/dachziegel/70e008dee7e3f0c18656

#include <thrust/device_vector.h>
#include <thrust/iterator/zip_iterator.h>
#include <thrust/transform.h>
#include <vector_types.h>
#include <iostream>
#include <chrono>
#include "helper_math.h"
template<typename T>
struct QuadVec
{
  T S1, S2, S3, S4;
  QuadVec(const int N) : S1(N), S2(N), S3(N), S4(N){}
};
template<typename T>
struct Result
{
  T average, sqrt, arctan;
  Result(const int N) : average(N), sqrt(N), arctan(N){}
};

typedef thrust::tuple<float4,float4,float4,float4> QuadInput;
typedef thrust::tuple<float4,float4,float4> TripleOutput;
struct CalcResult : public thrust::unary_function<QuadInput,TripleOutput>
{
  __host__ __device__
  TripleOutput operator()(const QuadInput& f) const
  {
      const float4 s3ms1 = thrust::get<2>(f) - thrust::get<0>(f);
      const float4 s4ms2 = thrust::get<3>(f) - thrust::get<1>(f);
      const float4 sqrtArg = s3ms1*s3ms1 + s4ms2*s4ms2;
      const float4 atanArg = s3ms1 / s4ms2;
      return thrust::make_tuple((thrust::get<0>(f) + thrust::get<1>(f) + thrust::get<2>(f) + thrust::get<3>(f)) / 4.0f,
              make_float4(sqrtf(sqrtArg.x), sqrtf(sqrtArg.y), sqrtf(sqrtArg.z), sqrtf(sqrtArg.w)),
              make_float4(atanf(atanArg.x), atanf(atanArg.y), atanf(atanArg.z), atanf(atanArg.w))
              );
  }
};

int main()
{
  typedef thrust::host_vector<float4> HostVec;
  typedef thrust::device_vector<float4> DevVec;
  const int N = 256;
  QuadVec<HostVec> hostFrame(N*N);
  QuadVec<DevVec> devFrame(N*N);
  Result<HostVec> hostResult(N*N);
  Result<DevVec> devResult(N*N);
  const int runs = 10000;
  int accumulatedDuration = 0;
  for (int i = 0; i < runs; ++i)
  {
        auto start = std::chrono::system_clock::now();
        thrust::copy(hostFrame.S1.begin(), hostFrame.S1.end(), devFrame.S1.begin());
        thrust::copy(hostFrame.S2.begin(), hostFrame.S2.end(), devFrame.S2.begin());
        thrust::copy(hostFrame.S3.begin(), hostFrame.S3.end(), devFrame.S3.begin());
        thrust::copy(hostFrame.S4.begin(), hostFrame.S4.end(), devFrame.S4.begin());
        thrust::transform(thrust::make_zip_iterator(make_tuple(devFrame.S1.begin(), devFrame.S2.begin(), devFrame.S3.begin(), devFrame.S4.begin())),
              thrust::make_zip_iterator(make_tuple(devFrame.S1.end(), devFrame.S2.end(), devFrame.S3.end(), devFrame.S4.end())),
              thrust::make_zip_iterator(make_tuple(devResult.average.begin(), devResult.sqrt.begin(), devResult.arctan.begin())),
              CalcResult() );
        thrust::copy(devResult.average.begin(), devResult.average.end(), hostResult.average.begin());
        thrust::copy(devResult.sqrt.begin(), devResult.sqrt.end(), hostResult.sqrt.begin());
        thrust::copy(devResult.arctan.begin(), devResult.arctan.end(), hostResult.arctan.begin());
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - start);
        accumulatedDuration += duration.count();
  }
  std::cout << accumulatedDuration/runs << std::endl;
  return 0;
}