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

时间:2015-03-20 13:18:57

标签: c++ image-processing cuda

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

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

2 个答案:

答案 0 :(得分:2)

这似乎非常适合由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像素),那么大约2.1m /帧,或210m / s,100fps。看一下GPU性能图表(例如Nvidia:http://en.wikipedia.org/wiki/List_of_Nvidia_graphics_processing_units),他们所有的GPU都经过Geforce 4(大约2005年),有足够的速度来实现这一目标。

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

答案 1 :(得分:0)

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

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

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

平均超过10000次运行一次迭代,包括复制到GPU,计算三个结果图像并从GPU复制结果在我的计算机上需要 1.79 ms (Ubuntu 14.04 x64,Intel Xeon @ 3.6 Ghz,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;
}