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

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

程序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);
    }
}


程序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

最佳答案

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

基准测试总是很棘手,特别是在涉及到JIT的情况下(对于Java),以及对于向用户隐藏了许多实质性细节的库(对于Aparapi)而言。在这两种情况下,至少应多次执行要进行基准测试的代码部分。

对于Java版本,由于JIT的介入,人们可能希望执行多次循环本身时,执行一次循环所需要的计算时间会减少。还有很多其他注意事项-有关详细信息,您应该请参阅this answer。在这个简单的测试中,JIT的效果可能并不是很明显,但是在更现实或更复杂的情况下,这会有所作为。无论如何:当重复执行10次循环时,在我的机器上执行一次循环的时间约为70毫秒。

对于Aparapi版本,注释中已经提到了可能的GPU初始化点。在这里,这确实是主要的问题:当运行内核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版本快。这样做的原因是,像这样的简单向量加法的任务受内存限制-有关详细信息,您可以参考this answer,它解释了该术语以及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])));
    }
};


以及相应的纯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线程池并行执行。

10-08 13:25