【问题标题】:AMP C++ speed up volume calculationAMP C++ 加速体积计算
【发布时间】:2013-09-06 08:45:08
【问题描述】:
  • 设备:Tesla C2050
  • 操作系统:Windows 7 企业版
  • IDE : VS 2012

大家好。我正在使用 AMP C++ 进行一些体积计算。

我有数百万个四面体,一点在 (0,0,0)。所以我可以用简单的方法得到四面体的体积:

sum += triangle.x1 * triangle.y2 * triangle.z3 + \
       triangle.y1 * triangle.z2 * triangle.x3 + \
       triangle.x2 * triangle.y3 * triangle.z1 - \
       triangle.x3 * triangle.y2 * triangle.z1 - \
       triangle.x2 * triangle.y1 * triangle.z3 - \
       triangle.y3 * triangle.z2 * triangle.x1;

所以,我想通过使用 AMP C++ 来加快计算速度。

这里是代码。

typedef struct
{
    double x1;
    double y1;
    double z1;
    double x2;
    double y2;
    double z2;
    double x3;
    double y3;
    double z3;
} Triangle;

主要功能是:

accelerator my_accelerator(accelerator::default_accelerator);
accelerator_view acc_view = my_accelerator.get_default_view();

const int BLOCK_SIZE = 64;
int outputSize = int(numTriangles / BLOCK_SIZE);

int dimA = int(numTriangles / BLOCK_SIZE) * BLOCK_SIZE;
std::cout<<dimA<<std::endl;

//copy triangles from host to device
array<Triangle,1> triangle(numTriangles);
copy(vTriangle.begin(),vTriangle.end(), triangle);

//Volume
std::vector<double> volumeCPP;
for (int i=0; i < outputSize; i++)
{
    volumeCPP.push_back(double(0));
}
array_view<double,1> volume(outputSize,volumeCPP);
volume.discard_data();

clock_t start,finish;
start = clock();
parallel_for_each(
    volume.extent.tile<1>(),
    [=, &triangle](tiled_index<1> t_idx) restrict(amp)
    {
        double sum = 0.0f;
        tile_static Triangle tile_triangle[4];
        tile_triangle[t_idx.local[0]] = triangle[t_idx.global];
        if (t_idx.local[0] == 0)
        {
            for (int idx=0; idx < BLOCK_SIZE; idx++){
                sum += tile_triangle[idx].x1 * tile_triangle[idx].y2 * tile_triangle[idx].z3 + tile_triangle[idx].y1 * tile_triangle[idx].z2 * tile_triangle[idx].x3 + tile_triangle[idx].x2 * tile_triangle[idx].y3 * tile_triangle[idx].z1 - tile_triangle[idx].x3 * tile_triangle[idx].y2 * tile_triangle[idx].z1 - tile_triangle[idx].x2 * tile_triangle[idx].y1 * tile_triangle[idx].z3 - tile_triangle[idx].y3 * tile_triangle[idx].z2 * tile_triangle[idx].x1;
                //t_idx.barrier.wait();
            }
            //t_idx.barrier.wait();
        }
        volume[t_idx.global] = sum;
    }
);

acc_view.wait();
finish = clock();
copy(volume, volumeCPP.begin());

所以,每一项工作都失败了。但有趣的是。它比 CPU(单核)代码成本更高。

CPU(单核)上的 C++ 花费 0.085 秒来完成 1024 * 1024 * 2 个三角形的计算。 但是 AMP C++ 代码需要 0.530 秒。远远超过 c++ 代码。

在网上搜索后,有一个提示:如果我们先预热设备,我们可以在计算上得到“实时”的时间成本。

所以我首先计算 128 个三角形来预热设备(花费大约 0.2 秒),然后通过计算 1024 * 1024 * 2 个三角形来获得体积。它变得更快(花费大约 0.091 秒),但仍然比 CPU(单核)代码慢。

我想知道原因,以及任何可以帮助我加快计算速度的人。

非常感谢。

【问题讨论】:

    标签: c++ performance parallel-processing gpu c++-amp


    【解决方案1】:

    首先,下面是我认为使用一些 cmets 的更好的实现。你的代码正在做一些可以避免的事情。

    但是,您在这里真正做的是减少。这是一种经过大量研究和优化的算法。 AMP Algorithms Codeplex site 上有一个 C++ AMP 实现,它被实现为 STL 风格的算法。在得出结论认为 C++ AMP 不能满足您的需求之前,我会尝试使用这个 reduce 实现,因为这样做很简单,并且可能会给您带来更好的性能。我很想看看你过得怎么样。

    AMP Book Codeplex site 包含一个用于计时 C++ AMP 内核的辅助类。随附的书还讨论了实施减少。它有一整章。

    void Foo()
    {
        const int numTriangles = 128;
        std::vector<Triangle> vTriangle;
    
        accelerator my_accelerator(accelerator::default_accelerator);
        accelerator_view acc_view = my_accelerator.get_default_view();
    
        const int BLOCK_SIZE = 64;
        int outputSize = int(numTriangles / BLOCK_SIZE);
    
        const int dimA = numTriangles;
        std::cout<<dimA<<std::endl;
    
        //copy triangles from host to device
        // Use and array_view to automatically sync your data. 
        // You can use acc_view.flush() to make sure that copy is complete 
        // when you are running your timing code. Make this const so that AMP does
        // not copy your input data back to the CPU.
    
        array_view<const Triangle, 1> triangle(vTriangle.size(), vTriangle.data());
    
        //Volume
        // Don't push_back this causes (re)allocation as the vector grows. 
        // Set size and fill at the same time.
    
        std::vector<double> volumeCPP(outputSize, 0.0);
    
        array_view<double, 1> volume(outputSize, volumeCPP);
        volume.discard_data();
    
        // I would use the timing code on CodePlex. 
        // It will be more accurate than this.
        clock_t start, finish;
        start = clock();
        parallel_for_each(
            // Not sure a tile size of 1 will be handled that 
            // well by the runtime in terms of perf. I see why you
            // are doing it to get tile_static. You might be better off having larger tiles.
    
            volume.extent.tile<1>(),
            [=](tiled_index<1> t_idx) restrict(amp)
            {
                double sum = 0.0f;
                for (int idx = 0; idx < BLOCK_SIZE; idx++)
                {
                    // Loading the single triangle into tiled memory is a good idea because
                    // elements are read more than once.
                    tile_static Triangle tile_triangle;
                    tile_triangle = triangle[t_idx.global * BLOCK_SIZE + idx];
    
                    sum += tile_triangle.x1 * tile_triangle.y2 * tile_triangle.z3 + 
                        tile_triangle.y1 * tile_triangle.z2 * tile_triangle.x3 + 
                        tile_triangle.x2 * tile_triangle.y3 * tile_triangle.z1 - 
                        tile_triangle.x3 * tile_triangle.y2 * tile_triangle.z1 - 
                        tile_triangle.x2 * tile_triangle.y1 * tile_triangle.z3 - 
                        tile_triangle.y3 * tile_triangle.z2 * tile_triangle.x1;
                }
                volume[t_idx.global] = sum;
            }
        );
        // Force data copy back to CPU.
        volume.synchronize();
        double sum = std::accumulate(begin(volumeCPP), end(volumeCPP), 0.0);
    }
    

    下面是另一个示例,它使用 AMP 算法库通过 map/reduce 模式实现您的问题的解决方案。

    std::vector<Triangle> triangles_cpu(1000);
    
    array_view<const Triangle, 1> triangles_gpu(triangles_cpu.size(), triangles_cpu.data());
    concurrency::array<double, 1> volumes_gpu(triangles_cpu.size());
    array_view<double, 1> volumes_gpuvw(volumes_gpu);
    amp_stl_algorithms::transform(begin(triangles_gpu), end(triangles_gpu), begin(volumes_gpuvw), 
        [=](const triangle& t) restrict(amp)
    {
        return t.x1 * (t.y2 * t.z3 - t.y3 * t.z2)
            + t.y1 * (t.z2 * t.x3 - t.x2 * t.z3)
            + t.z1 * (t.x2 * t.y3 - t.x3 * t.y2);
    });
    double sum = amp_stl_algorithms::reduce(begin(volumes_gpuvw), end(volumes_gpuvw), 0.0);
    

    【讨论】:

    • 您好,感谢您帮助我并感谢您的提示。按照您的建议,我修改了我的代码,但只能看到很少的改进。可能 AMP C++ 太新用不起来,我们需要等待更多的时间让它更完整。顺便说一句,我想在我的 AMP C++ 代码上使用共享内存,这是否意味着我必须将我的系统升级到 Win 8.1?我看到一篇文章显示只有 Win 8.1 支持共享内存。
    • C++ AMP 没问题。您是否尝试过增加图块大小以提高 GPU 的占用率?我的代码不这样做? tile size 为 1 将导致大部分 GPU 内核处于空闲状态。您是否尝试过使用 Codeplex 的计时码?这将确保您的计时中不包含任何复制时间我还使用一些使用我建议的库的附加代码更新了答案。您可能想尝试一下。
    • 您是否使用过 Visual Studio 中的分析工具来查看您有多少时间与复制到 GPU 相关以及有多少时间用于计算?如果这不是花费时间的原因,那么迁移到 8.1 以消除复制时间是没有意义的。总是测量。而且,总是听 Ade :-) 在写这本书时,它对我很有帮助,尤其是关于性能问题。
    • @Xaview Xu 你有没有尝试我们的任何建议?结果如何?
    • @AdeMiller 抱歉,我很久没有勾选我的复选框了。谢谢你的回答,对我帮助很大。谢谢!
    【解决方案2】:

    你应该可以通过分解来加快速度。

    注意你的四面体体积公式:

    + x1 * y2 * z3
    + y1 * z2 * x3
    + x2 * y3 * z1
    - x3 * y2 * z1
    - x2 * y1 * z3
    - y3 * z2 * x1
    

    相当于:

    + x1 * (y2 * z3 - y3 * z2)
    + y1 * (z2 * x3 - x2 * z3)
    + z1 * (x2 * y3 - x3 * y2)
    

    原始公式有 12 次乘法,等效公式有 9 次乘法(减少 25%)。很难说它会有多大的总体改进,但如果它给你 20%,我不会感到惊讶。

    【讨论】:

    • 非常感谢,我试试看。
    • 前者和后者没有太大区别,完成计算的时间是一样的……不知道为什么……
    • 编译器现在非常聪明,GPU 编译器非常努力地发出尽可能多的 FMAD 操作以提高算术吞吐量。如果编译器自己还没有这样做,我会感到非常惊讶。
    • @talonmies 是的,你是对的。而且我认为 AMP C++ 目前还不够好用
    • @ZavierXu 鉴于您似乎没有尝试过任何我建议您得出的结论,即 C++ AMP“不够好,无法在今天使用”,充其量来说,这似乎还为时过早。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多