Java Aparabi GPU执行速度比CPU慢

Java Aparabi GPU执行速度比CPU慢,java,parallel-processing,aparapi,Java,Parallel Processing,Aparapi,我正在尝试测试Aparabi的性能。 我已经看到一些结果显示Aparabi在执行数据并行操作时确实提高了性能 但在我的测试中我看不到这一点。我写了两个程序,一个使用Aparabi,另一个使用普通循环 方案1:在阿帕拉比 import com.amd.aparapi.Kernel; import com.amd.aparapi.Range; public class App { public static void main( String[] args ) {

我正在尝试测试Aparabi的性能。 我已经看到一些结果显示Aparabi在执行数据并行操作时确实提高了性能

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

方案1:在阿帕拉比

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);
    }
}
import com.amd.aparapi.Kernel;
导入com.amd.aparapi.Range;
公共类应用程序
{
公共静态void main(字符串[]args)
{
最终整型尺寸=50000000;
最终浮动[]a=新浮动[大小];
最终浮动[]b=新浮动[尺寸];
对于(int i=0;i
程序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);

    }
}
公共类App2{
公共静态void main(字符串[]args){
最终整型尺寸=50000000;
最终浮动[]a=新浮动[大小];
最终浮动[]b=新浮动[尺寸];
对于(int i=0;i对于(inti=0;i您没有做错任何事情-针对基准本身执行

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

对于java版本,当循环执行时,可能会期望循环执行一次循环时减少了一次循环。有许多附加的警告要考虑。详细情况,您应该参考。在这个简单的测试中,JIT的效果可能并不明显,但我在更现实或更复杂的场景中,这会有所不同。无论如何:当重复循环10次时,在我的机器上执行循环的时间大约为70毫秒

对于Aparabi版本,注释中已经提到了可能的GPU初始化点。这里,这确实是主要问题:当运行内核10次时,我的机器上的计时是

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

调用
Kernel.execute()
后,会延迟创建
KernelRunner

这种情况的影响——即,第一次执行的延迟相对较大——导致Aparabi邮件列表上出现了这个问题:。唯一的解决方法是创建一个“初始化调用”,如

在没有实际工作负载的情况下,只会触发整个设置,因此后续调用速度很快。(这也适用于您的示例)


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

作为一个过度暗示的例子,如果您可能从GPU中获益,您可能需要修改您的测试,以便创建一个人工的计算范围任务:当您将内核更改为包含一些昂贵的三角函数时,如下所示

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])));
    }
};
for (int i = 0; i < size; i++) {
    sum[i] = (float)(Math.cos(Math.sin(a[i])) + Math.sin(Math.cos(b[i])));;
}
相应地,普通Java循环版本如下

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])));
    }
};
for (int i = 0; i < size; i++) {
    sum[i] = (float)(Math.cos(Math.sin(a[i])) + Math.sin(Math.cos(b[i])));;
}
for(int i=0;i
然后你会看到一个不同。在我的机器上(GeForce 970 GPU与AMD K10 CPU),Aparapi版本的计时时间约为140毫秒,而普通Java版本的计时时间则高达12000毫秒,这是Aparapi的近90倍加速


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

只需在主循环内核执行之前添加即可

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

在它之后

虽然Aparabi确实分析
内核的字节码。run()
方法(以及可从
Kernel.run()
访问的任何方法)Aparabi没有 呼叫站点的可见性。在上面的代码中,没有 Aparabi检测到HugarRay未在for中修改 不幸的是,Aparabi必须默认为“安全”和 将HugarRay的内容前后复制到GPU 装置


只是一个想法:你的计时不包括GPU初始化时间吗?你能在代码中连续运行两次(第一次作为“热身”,第二次是真实的)然后只计时第二次吗?@Gilles谢谢你的建议,我每个循环运行了4次