【问题标题】:ArrayFire CUDA application is extremely slow in the first minuteArrayFire CUDA 应用程序在第一分钟非常慢
【发布时间】:2018-05-09 07:18:35
【问题描述】:

我正在编写一个使用 ArrayFire 在 windows 10 + Nvidia Gtx 970 上运行的测试程序。该程序是用 SGD 求解器训练一个神经网络。因此,主要计算是更新网络参数的迭代。迭代在一个名为 step() 的函数中。

该程序执行预期的操作,只是它在第一分钟执行得非常慢。以下是程序的输出。第一列是经过的时间。

ArrayFire v3.5.1 (CUDA, 64-bit Windows, build 0a675e8) 平台:CUDA Toolkit 8,驱动程序:CUDA 驱动程序版本:8000 [0] GeForce GTX 970、4096 MB、CUDA 计算 5.2 时间训练误差 5 0.002 5.6124567 6 0.007 5.5981609 7 0.010 5.3560046 8 0.015 5.2485286 9 0.020 5.1370633 10 0.022 5.1081303 …… 52 0.148 3.2528560 53 0.150 3.2425120 54 0.153 3.2180901 55 0.155 3.2048657 56 0.157 3.1949191 57 0.158 3.1816899 58 0.160 3.1717312 59 0.162 3.1597322 60 0.165 3.1370639 60 0.498 2.1359600 61 0.548 2.0685355 61 0.882 1.7098215 62 0.943 1.6575973 62 1.277 1.4156345 63 1.343 1.3845720 63 1.677 1.1789854 64 1.733 1.1549067 64 2.067 1.0162785 …… 71 4.517 0.4732214 71 4.850 0.4522045 72 4.910 0.4501807 72 5.243 0.4355422 73 5.305 0.4307187

如您所见,在第一分钟,它甚至没有完成 1/5 个 epoch。但一分钟后,它突然加速,大约4秒完成一个纪元。

分析数据也说明了同样的事情:在第一分钟,函数 step() 的平均执行时间约为 500 毫秒,但在第一分钟之后,它下降到 6 毫秒。

Nvidia 视觉分析器显示内核在第一分钟几乎一直处于空闲状态。

我不知道什么会导致第一分钟之前|之后的性能变化。任何帮助表示赞赏。

【问题讨论】:

    标签: c++ cuda arrayfire


    【解决方案1】:

    ArrayFire 在运行时使用 JIT 编译来融合对函数的多次调用。因此,当您执行添加或任何其他元素操作时,ArrayFire 将创建一个自定义内核并执行此内核。当您第一次生成此内核时,这会产生一些开销,但这些内核会被缓存,并且不需要编译额外的调用。通常,在不需要额外编译之前,它应该只需要几次迭代。奇怪的是,即使经过 60 次左右的迭代,内核仍然很慢。

    使用基于内存和内核大小的内部启发式方法评估 JIT 内核。也许您的应用程序没有以最佳方式触发内核并导致额外的内核编译。您可以通过在变量上调用 eval 函数来强制评估来解决此问题。这是一个人为的例子:

    array a = randu(10, 10);
    array b = randu(10, 10);
    for(int i = 0; i < 100; i++) {
          a += b / 4;
          b *= i;
          eval(a, b);
    }
    

    在这里,您在每次迭代中评估变量 a 和 b 的 JIT 树。这将在每次迭代中重用相同的内核,而不是为不同的迭代倍数创建内核。

    需要注意的一点是元素方面的,以及一些条件函数,如 select 和 shift 是 JITed。其他函数在使用之前强制评估其参数。此外,如果您过于频繁地评估,则会降低应用程序的性能。

    【讨论】:

    • 尽管我不完全理解它是如何工作的,但添加 eval() 确实可以大大提高性能。有没有关于这个主题的进一步阅读?例如,在什么情况下,添加 eval() 最有可能提高性能?目前,我尽可能在代码中添加 eval() 。但正如您也提到的,添加太多 eval() 会降低性能。那么有没有最有效的方法呢?
    • 如果我觉得这样做有好处,我通常会将 eval 放在执行许多操作的循环和函数的末尾。您希望避免添加太多 eval,因为那样您将启动许多效率低下的较小内核。我建议您避免在程序中使用 eval,并且仅在完成算法后它们对性能产生可衡量的差异时才添加它们。
    • 我又做了一些测试。看来我只需要触摸直接在函数step() 中使用的全局数组。 step()(带函数作用域)中子函数和临时数组中使用的变量无关。
    猜你喜欢
    • 2021-02-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-23
    • 2010-10-28
    • 1970-01-01
    • 2012-10-12
    • 2020-04-09
    • 2017-04-27
    相关资源
    最近更新 更多