OpenCL:为啥这两种情况的性能差异如此之大?

Posted

技术标签:

【中文标题】OpenCL:为啥这两种情况的性能差异如此之大?【英文标题】:OpenCL: Why does the performance differ so greatly between these two cases?OpenCL:为什么这两种情况的性能差异如此之大? 【发布时间】:2011-12-03 03:09:36 【问题描述】:

这是我正在处理的 OpenCL 内核中的两段代码;它们显示出截然不同的运行时间。

代码比较复杂,我把它简化了。

这个版本运行不到一秒:

for (int ii=0; ii<someNumber;ii++)

    for (int jj=0; ii<someNumber2;jj++)
    
        value1 = value2 + value3;
        value1 = value1 * someFunction(a,b,c);
        double nothing = value1;
    

这个版本大约需要 38 秒才能运行:

for (int ii=0; ii<someNumber;ii++)

    for (int jj=0; ii<someNumber2;jj++)
    
        value1 = value2 + value3;
        value1 = value1 * someFunction(a,b,c);
    
    double nothing = value1;

正如我所说,代码比这要复杂一些(循环中还有很多其他事情),但变量“nothing”确实会从大括号之前立即移动到大括号之后。

我对 OpenCL 很陌生,我无法弄清楚发生了什么,更不用说如何解决它了。不用说,缓慢的情况实际上是我在实施中所需要的。我试过搞乱地址空间(这里的所有变量都在 __private 中)。

我只能想象,由于某种原因,当大括号关闭时,GPU 会将变量“value1”推到较慢的内存中。这是一个可能的解释吗?我能做什么?

提前致谢!

更新:这也在不到一秒的时间内运行:(但如果取消注释任何一行,它就会恢复到极度缓慢)。这没有对循环进行任何其他更改,并且 value1 仍然声明在与以前相同的位置。

for (int ii=0; ii<someNumber;ii++)

    for (int jj=0; ii<someNumber2;jj++)
    
//        value1 = value2 + value3;
//        value1 = value1 * someFunction(a,b,c);
    
    double nothing = value1;

更新 2:代码实际上嵌套在另一个循环中,如下所示,value1 的声明:

double value1=0;
for (int kk=0; kk<someNumber3;kk++)

    for (int ii=0; ii<someNumber;ii++)
    
        for (int jj=0; ii<someNumber2;jj++)
        
            value1 = value2 + value3;
            value1 = value1 * someFunction(a,b,c);
        
        double nothing = value1;
    

移动到声明 value1 的位置也让我们回到快速案例:

for (int kk=0; kk<someNumber3;kk++)

    double value1=0;
    for (int ii=0; ii<someNumber;ii++)
    
        for (int jj=0; ii<someNumber2;jj++)
        
            value1 = value2 + value3;
            value1 = value1 * someFunction(a,b,c);
        
        double nothing = value1;
    

OpenCL 似乎是一门非常棘手的艺术!我仍然不明白发生了什么,但至少我现在知道如何解决它!

【问题讨论】:

这很奇怪。您确定需要使用较慢的版本吗?从这些 sn-ps 来看,它们在功能上是相同的。 感谢您的回复。是的,我敢肯定,但你是对的,我给出的示例在功能上是相同的。内大括号中的代码应该有一个 +=。 根据这些代码 sn-ps,我看不出第二个应该变慢的任何原因。我猜想移动分配一定会在某处产生副作用,例如增加分支(一个工作单元执行if,下一个执行else),这确实会减慢GPU。 另外,我知道你说过所有的变量都是__private,但是如果你完全与全局内存同步,你可能会破坏对内存的合并访问。优化 OpenCL 内存访问可能很棘手:***.com/questions/3841877/… 只是抛出一些想法。 :) 这给了我很多思考。我已经用一些可能会带来更多启示的东西更新了我的问题。该代码没有ifs。我怀疑这将成为一个合并问题。 【参考方案1】:

您使用的是什么实现?我希望“双重无= value1;”在任何情况下都会被任何合理的编译器作为死代码消除。

【讨论】:

我想我找到了问题所在,感谢您的帖子。在案例 1(我的问题中的第一个框)中,我认为编译器通过“消除死代码”来优化内部循环。在情况 2 中,它意识到变量 value1 需要在内循环之外,所以它运行它。函数someFunction(a,b,c) 非常慢,因此会导致速度变慢。仅供参考,实施是 AMD 的 Linux SDK。感谢大家的帮助! 您是说因为 value1 未使用,编译器优化了对 someFunction 的调用。如何确定 someFunction 没有副作用? 因为“无”未使用。我不是在谈论 value1。 @vocaro 我不确定,但这是我能找到的最佳解释。它实际上是一个非常简单的函数,它只是对内存造成了很大的影响(我自己的稀疏矩阵访问实现)。我对编译器一无所知,但由于它实际上只是一个复杂的取消引用,也许编译器会注意到?当然,它可能是完全不同的东西,但我不知道是什么!【参考方案2】:

第一种情况只是一个循环(带有编译器优化) 但第二个是带有嵌套循环的循环。那是个大问题。大量检查全局/局部变量。(确定它们是私有的?你在内核中声明了所有这些?)

我建议您在开始循环之前将其保存为私有变量(somenumber 和 somenumber2)。因为这样你每次都会检查私人数据。 作为个人经验,用作 OpenCL 循环检查用例的每个 var 都必须是私有的。 这可以节省高达 80% 的全局内存访问。 (特别是如果循环很短或很简单)

例如,这应该可以快速运行:

int c_somenumber = someNumber;
for (int ii=0; ii<c_someNumber;ii++)

    int c_somenumber2 = someNumber2;    
    for (int jj=0; ii<c_someNumber2;jj++)
    
        value1 = value2 + value3;
        value1 = value1 * someFunction(a,b,c);
    
    double nothing = value1;

编辑: 此外,value1 应该缓存在私有内存中。 (就像您在上次编辑中所做的那样)

【讨论】:

是的,所有内容都保存为私有。请参阅我对问题的解释作为对其他答案的评论!

以上是关于OpenCL:为啥这两种情况的性能差异如此之大?的主要内容,如果未能解决你的问题,请参考以下文章

为啥“快速排序”算法的这两种变体在性能上差别如此之大?

为啥 Google Analytics 和 BigQuery 之间的独特事件差异如此之大?

为啥数组声明的顺序对性能影响如此之大?

为啥 GridPane 列的间距如此之大

为啥使用 XGBoost 的 rmse 和 mse 如此之大?

为啥 C++ 线程/未来的开销如此之大