4

I am trying to test the performance of Aparapi. I have seen some blogs where the results show that Aparapi does improve the performance while doing data parallel operations.

But I am not able to see that in my tests. Here is what I did, I wrote two programs, one using Aparapi, the other one using normal loops.

Program 1: In 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);
    }
}

Program 2: using loops

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);

    }
}

Program 1 takes around 330ms whereas Program 2 takes only around 55ms. Am I doing something wrong here? I did printout the execution mode in Aparpai program and it prints that the mode of execution is GPU

4

2 に答える 2

7

あなたは何も悪いことはしていません - ベンチマーク自体に対しては execpt です。

ベンチマークは常に注意が必要で、特に JIT が関与する場合 (Java の場合) や、多くの核心的な詳細がユーザーから隠されているライブラリー (Aparapi の場合) の場合はなおさらです。どちらの場合も、少なくとも、ベンチマークするコード セクションを複数回実行する必要があります。

Java バージョンの場合、ループ自体が複数回実行されると、JIT が開始されるため、ループの 1 回の実行の計算時間が減少すると予想される場合があります。考慮すべき追加の注意事項が多数あります。詳細については、この回答を参照してください。この単純なテストでは、JIT の効果はあまり目立たないかもしれませんが、より現実的または複雑なシナリオでは、これが違いを生みます。とにかく: ループを 10 回繰り返すと、私のマシンでのループの 1 回の実行時間は約70 ミリ秒でした。

Aparapi バージョンの場合、可能性のある GPU 初期化のポイントは、コメントで既に言及されています。ここで、これが主な問題です。カーネルを 10 回実行すると、私のマシンでのタイミングは次のようになります。

1248
72
72
72
73
71
72
73
72
72

最初の呼び出しがすべてのオーバーヘッドを引き起こしていることがわかります。この理由は、 への最初の呼び出し中に、Kernel#execute()すべての初期化 (基本的にはバイトコードを OpenCL に変換する、OpenCL コードをコンパイルするなど) を行う必要があるためです。これは、KernelRunnerクラスのドキュメントにも記載されています。

は、 を呼び出した結果、遅延KernelRunnerして作成されます。Kernel.execute()

この結果、つまり、最初の実行で比較的大きな遅延が発生したため、Aparapi メーリング リストで次のような質問が寄せられました。唯一の回避策は、次のような「初期化呼び出し」を作成することでした

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

実際のワークロードなしで、セットアップ全体をトリガーするだけなので、後続の呼び出しが高速になります。(これはあなたの例でも機能します)。


お気づきかもしれませんが、初期化でも、Aparapi バージョンはプレーンな 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])));
    }
};

それに応じて、このようにプレーン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 ミリ秒です。

また、CPUモードであっても、Aparapi はプレーン Java と比較して利点を提供する可能性があることに注意してください。私のマシンでは、CPU モードで、Aparapi は2300 ミリ秒しか必要としません。これは、Java スレッド プールを使用して実行を並列化するためです。

于 2015-10-16T19:30:00.530 に答える