【问题标题】:Aparapi GPU execution slower than CPUAparapi GPU 执行速度比 CPU 慢
【发布时间】:2015-12-27 19:22:05
【问题描述】:

我正在尝试测试 Aparapi 的性能。 我见过一些blogs,结果表明 Aparapi 在进行数据并行操作时确实提高了性能。

但我在测试中看不到这一点。这是我所做的,我写了两个程序,一个使用 Aparapi,另一个使用普通循环。

程序 1:在 Aparapi 中

import com.amd.aparapi.Kernel;
import com.amd.aparapi.Range;

public class App 
{
    public static void main( String[] args )
    {
        final int size = 50000000;

        final float[] a = new float[size];
        final float[] b = new float[size];

        for (int i = 0; i < size; i++) {
           a[i] = (float) (Math.random() * 100);
           b[i] = (float) (Math.random() * 100);
        }

        final float[] sum = new float[size];

        Kernel kernel = new Kernel(){
           @Override public void run() {
              int gid = getGlobalId();
              sum[gid] = a[gid] + b[gid];
           }
        };
        long t1 = System.currentTimeMillis();
        kernel.execute(Range.create(size));
        long t2 = System.currentTimeMillis();
        System.out.println("Execution mode = "+kernel.getExecutionMode());
        kernel.dispose();
        System.out.println(t2-t1);
    }
}

程序 2:使用循环

public class App2 {

    public static void main(String[] args) {

        final int size = 50000000;

        final float[] a = new float[size];
        final float[] b = new float[size];

        for (int i = 0; i < size; i++) {
           a[i] = (float) (Math.random() * 100);
           b[i] = (float) (Math.random() * 100);
        }

        final float[] sum = new float[size];
        long t1 = System.currentTimeMillis();
        for(int i=0;i<size;i++) {
            sum[i]=a[i]+b[i];
        }

        long t2 = System.currentTimeMillis();
        System.out.println(t2-t1);

    }
}

程序 1 大约需要 330 毫秒,而程序 2 只需要大约 55 毫秒。 我在这里做错了吗?我确实打印出了Aparpai程序中的执行模式,它打印出执行模式是GPU

【问题讨论】:

  • 只是一个想法:你的时间不包括GPU初始化时间吗?您能否在代码中连续运行 2 次再试一次(第一次用作“热身”,第二次是真实的)并且只计时第二次?
  • @Gilles 感谢您的建议,我每个循环运行了 4 次,结果仍然相同 - 862ms vs 216ms

标签: java parallel-processing aparapi


【解决方案1】:

在主循环内核执行前添加

kernel.setExplicit(true);
kernel.put(a);
kernel.put(b);

kernel.get(sum);

在它之后。

虽然 Aparapi 确实分析了 Kernel.run() 的字节码 方法(以及可从Kernel.run() 访问的任何方法) Aparapi 没有 呼叫站点的可见性。在上面的代码中,没有办法 Aparapi 检测到该 hugeArray 未在 for 中修改 循环体。不幸的是,Aparapi 必须默认为“安全”并且 将hugeArray的内容前后复制到GPU 设备。

https://github.com/aparapi/aparapi/blob/master/doc/ExplicitBufferHandling.md

【讨论】:

  • 这是一个被低估的答案。但是,我没有看到任何显着的性能改进。这可能是由于接受的答案中提到的原因
【解决方案2】:

您没有做错任何事情 - 执行基准测试本身。

基准测试总是很棘手,尤其是在涉及 JIT 的情况下(如 Java),以及许多细节对用户隐藏的库(如 Aparapi)。在这两种情况下,您至少应该多次执行要进行基准测试的代码部分。

对于 Java 版本,由于 JIT 的作用,当循环本身被多次执行时,人们可能期望单次执行循环的计算时间会减少。还有许多额外的注意事项需要考虑 - 详情,请参考this answer。在这个简单的测试中,JIT 的效果可能并不明显,但在更现实或更复杂的场景中,这会有所作为。总之:重复循环 10 次时,在我的机器上执行一次循环的时间约为 70 毫秒

对于 Aparapi 版本,可能的 GPU 初始化点已在 cmets 中提及。在这里,这确实是主要问题:运行内核10次时,我机器上的计时是

1248
72
72
72
73
71
72
73
72
72

您会看到初始调用会导致所有开销。这样做的原因是,在第一次调用Kernel#execute() 期间,它必须进行所有初始化(基本上将字节码转换为 OpenCL,编译 OpenCL 代码等)。 KernelRunner 类的文档中也提到了这一点:

KernelRunner 是由于调用Kernel.execute()懒惰地创建的。

这样做的影响 - 即第一次执行的相对较大的延迟 - 导致了 Aparapi 邮件列表上的这个问题:A way to eagerly create KernelRunners。建议的唯一解决方法是创建一个“初始化调用”,如

kernel.execute(Range.create(1));

没有真正的工作量,只触发整个设置,以便后续调用快速。 (这也适用于您的示例)。


您可能已经注意到,即使在初始化之后,Aparapi 版本仍然没有比普通 Java 版本快。原因是像这样的简单向量加法任务是memory bound - 有关详细信息,您可以参考this answer,它解释了这个术语以及GPU编程的一些问题。

作为一个过度暗示的示例,您可能希望从 GPU 中受益,您可能希望修改您的测试,以创建一个人为的 compute bound 任务:当您将内核更改为涉及一些昂贵的三角函数,像这样

Kernel kernel = new Kernel() {
    @Override
    public void run() {
        int gid = getGlobalId();
        sum[gid] = (float)(Math.cos(Math.sin(a[gid])) + Math.sin(Math.cos(b[gid])));
    }
};

和相应的普通 Java 循环版本,像这样

for (int i = 0; i < size; i++) {
    sum[i] = (float)(Math.cos(Math.sin(a[i])) + Math.sin(Math.cos(b[i])));;
}

然后你会看到不同。在我的机器(GeForce 970 GPU 与 AMD K10 CPU)上,Aparapi 版本的时间约为 140 毫秒,而普通 Java 版本的时间则高达 12000 毫秒 - 这就是通过 Aparapi 加速了近 90 倍!

还请注意,即使在 CPU 模式下,与普通 Java 相比,Aparapi 也可能提供优势。在我的机器上,在 CPU 模式下,Aparapi 只需要 2300 毫秒,因为它仍然使用 Java 线程池并行执行。

【讨论】:

  • 谢谢 Marco,我根据您的建议更改了代码,但现在我收到警告“警告:恢复到 JavaCL.App$1 类的 Java 线程池 (JTP):需要 FP64 但不支持”并且正在回退到 JTP。我把代码改回简单的向量加法,执行模式是GPU。我的显卡是 AMD radeon r9 370 mx。是不是说明我的显卡不支持FP64
  • 是的,这个消息基本意思是显卡不支持double计算,只支持float。我很确定应该可以仅使用 float 参数进行 sin/cos 计算,以便代码可以正确转换为 Aparapi 中的 sinf/cosf 调用,但我必须尝试一下,或者再看看源代码。然而,主要的一点是表明,随着计算量的增加,GPU 将比 CPU 更快。您可能会观察到与 result[gid] = a[gid]*b[gid]*a[gid]*b[gid]*a[gid]*b[gid]; 之类的相同效果...
  • 只是为了比较,使用相同内核(但使用 float4)进行 64M 浮点的流水线主机代码优化 c++ opencl 程序在 R7-240 上产生 190 毫秒,在 FX8150 和 60 上产生 67 毫秒毫秒同时使用。所以 Aparapi 绝对在做它的工作。这是 gpu 的 pci-e 带宽瓶颈操作,而 cpu-opencl 根本不如 AVX 代码(或 SSE)快。性能可能会从 %10 变化到 %90,但代码编写时间会减少 %1000 到 %10000。
猜你喜欢
  • 2019-04-18
  • 2016-10-23
  • 2015-09-23
  • 2019-07-10
  • 2011-11-23
  • 2020-05-13
  • 2023-03-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多