これは何?

aparapi の取得

  1. git からソースコードを取得してきてビルドする
    [/opt]$ git clone https://github.com/aparapi/aparapi.git
    [/opt]$ cd aparapi/
    [/opt/aparapi]$ ant dist
    すると /opt/aparapi/dist_mac_x86_64 に
    LICENSE.TXT
    aparapi.jar
    api
    libaparapi_x86_64.dylib
    samples
    ができる
  2. ローカルの Maven レポジトリに Java 部分を登録する
    $ mvn install:install-file -Dfile=aparapi.jar -DgroupId=aparapi -DartifactId=aparapi -Dversion=1.0 -Dpackaging=jar -DgeneratePom=true

Hello GPU World!

Quick Reference

GPGPU を使った計算の概要

GPGPU.png

パフォーマンス上の留意点

GPGPU の呼び出し



実行IDの整理

execid.png

実行モード

Kernel.EXECUTION_MODE.ACCXeon Phi
Kernel.EXECUTION_MODE.JTPJava Thread Pool
Kernel.EXECUTION_MODE.SEQJava Single Thread
Kernel.EXECUTION_MODE.GPUGPU
Kernel.EXECUTION_MODE.CPUCPU
Kernel.EXECUTION_MODE.NONEAuto

デフォルトは NONE (自動設定)

明示的なバッファ管理

GPGPU組み込み関数

FFT(高速フーリエ変換)

おさらい

GPGPU で FFT を計算するための工夫

ということで作ってみた

https://github.com/kagyuu/AparapiExam/blob/master/src/main/java/com/mycompany/aparapiexam/FFTKernel.java

package com.mycompany.aparapiexam;

import com.amd.aparapi.Kernel;
import java.util.LinkedList;
import java.util.List;
import org.apache.commons.lang.ArrayUtils;

public class FFTKernel extends Kernel {
    // データサイズ
    private int n = 0;
    // 入力データの実数部
    private float[] real;
    // 入力データの虚数部
    private float[] imag;
    // ビット反転表
    private int[] bitrev;
    // バタフライ演算の指示書(i)
    private int[] buttI;
    // バタフライ演算の指示書(ik)
    private int[] buttIK;
    // バタフライ演算の指示書(sin)
    private float[] buttSin;
    // バタフライ演算の指示書(cos)
    private float[] buttCos;
    // バタフライ演算の段数
    private int buttStep;
    // バタフライ演算の一段あたりの計算の組数
    private int buttCal;
    // FFT(1) / 逆FFT(-1) フラグ
    private float sign = 1.0f;
        
    public FFTKernel (int size) {
        n = size;
                
        // 入力データ格納領域
        real = new float[n];
        imag = new float[n];
                
        // FFT Step0 ビット反転表を作る
        createBitrev();
        
        // FFT Step1〜n バタフライ演算の指示書を作る
        createButterfly();
    }
    
    private void createBitrev(){
        bitrev = new int[n];
        int i = 0;
        int j = 0;
        bitrev[i] = 0;
        while (++i < n) {
            int k = n / 2;
            while (k <= j) {
                j -= k;
                k /= 2;
            }
            j += k;
            bitrev[i] = j;
        }
    }
    
    private void createButterfly() {
        double step = 2.0 * Math.PI / n;
        List<Integer> buttIArray = new LinkedList<>();
        List<Integer> buttIKArray = new LinkedList<>();
        List<Float> buttSinArray = new LinkedList<>();
        List<Float> buttCosArray = new LinkedList<>();
        
        int cnt = 0;
        buttStep = 0;
        for (int k = 1; k < n; k *= 2) {
            int h = 0;
            int d = n / (k * 2);
            buttCal = 0; // バタフライ計算の段数を数える
            for (int j = 0; j < k; j++) {
                float s = (float) Math.sin(step * h);
                float c = (float) Math.cos(step * h);
                for (int i = j; i < n; i+= k * 2) {
                    int ik = i + k;
                    
                    buttIArray.add(i);
                    buttIKArray.add(ik);
                    buttSinArray.add(s);
                    buttCosArray.add(c);
                }
                h += d;
                buttCal += 1; // 一段につき何組の計算があるかを数える
            }
            buttStep += 1;
        }
        buttI = ArrayUtils.toPrimitive(buttIArray.toArray(new Integer[0]));
        buttIK = ArrayUtils.toPrimitive(buttIKArray.toArray(new Integer[0]));
        buttSin = ArrayUtils.toPrimitive(buttSinArray.toArray(new Float[0]));
        buttCos = ArrayUtils.toPrimitive(buttCosArray.toArray(new Float[0]));
        
//        for(int i = 0; i < buttI.length; i++) {
//            System.out.println(String.format("%d %d %d %f %f", i, buttI[i], buttIK[i], buttSin[i], buttCos[i]));
//        }
    }

    @Override
    public void run() {
        int p = getPassId() * buttCal + getGlobalId();
        
        int i = buttI[p];
        int ik = buttIK[p];
        float s = sign * buttSin[p];
        float c = buttCos[p];
        
        float dx = s * imag[ik] + c * real[ik];
        float dy = c * imag[ik] - s * real[ik];
        
        real[ik] = real[i] - dx;
        real[i] += dx;
        
        imag[ik] = imag[i] -dy;
        imag[i] += dy;
    }
    
    public void fft(float[] x, float[] y) {
        fftsub(x,y,1.0f);
    }
    
    public void ifft(float[] x, float[] y) {
        fftsub(x,y,-1.0f);
        
        for (int cnt = 0; cnt < n; cnt++) {
            real[cnt] /= n;
            imag[cnt] /= n;
        }
    }
    
    private void fftsub(float[] x, float[] y, float sign) {
        if (n != x.length || n != y.length) {
            throw new IllegalArgumentException("Illegal data size");
        }
        for (int cnt = 0; cnt < n; cnt++) {
            this.real[cnt] = x[cnt];
            this.imag[cnt] = y[cnt];
        }
        this.sign = sign;
        
        // ビット反転
        for (int i = 0; i < n; i++) {
            int j = bitrev[i];
            if (i < j) {
                float t;
                t = real[i];
                real[i] = real[j];
                real[j] = t;
                
                t = imag[i];
                imag[i] = imag[j];
                imag[j] = t;
            }
        }
        
        // バタフライ演算
        // buttCal 組の演算を buttStep 段繰り返す
        // このようにループにしないと、GPGPU はステップごとに順に計算せずに
        // 一気に全段計算してしまい、まともな結果が得られない
        this.execute(buttCal, buttStep);
    }
    
    public float[] getReal() {
        return real;
    }
    
    public float[] getImag() {
        return imag;
    }
    
    public float[] getSin() {
        float[] sin = new float[n/2];
        
        sin[0] = imag[0] / n; // this must be zero.
        for (int cnt = 1; cnt < sin.length; cnt++) {
            sin[cnt] = -2.0f * imag[cnt] / n;
        }
        
        return sin;
    }
    
    public float[] getCos() {
        float[] cos = new float[n/2];
        
        cos[0] = real[0] / n;
        for (int cnt = 1; cnt < cos.length; cnt++) {
            cos[cnt] = 2.0f * real[cnt] / n;
        }
        
        return cos;
    }
}

テスト実行

https://github.com/kagyuu/AparapiExam/blob/master/src/test/java/com/mycompany/aparapiexam/test/FFTKernelTest.java

package com.mycompany.aparapiexam.test;

import com.mycompany.aparapiexam.FFTKernel;
import org.junit.Test;

public class FFTKernelTest {

    @Test
    public void testFFT2() {
        int size = 8;
        
        FFTKernel fftKernel = new FFTKernel(size);
        
        float[] real = new float[size];
        float[] imag = new float[size];
        
        for (int cnt = 0; cnt < size; cnt++) {
            real[cnt] = (float) (
                1.0 * Math.sin(0 * Math.PI * cnt / size)
                + 2.0 * Math.cos(0 * Math.PI * cnt / size)
                + 3.0 * Math.sin(2 * Math.PI * cnt / size)
                + 4.0 * Math.cos(2 * Math.PI * cnt / size)
                + 5.0 * Math.sin(4 * Math.PI * cnt / size)
                + 6.0 * Math.cos(4 * Math.PI * cnt / size)
                + 7.0 * Math.sin(6 * Math.PI * cnt / size)
                + 8.0 * Math.cos(6 * Math.PI * cnt / size)
            );

            imag[cnt] = 0.0f;
        }
        
        fftKernel.fft(real, imag);
        
        real = fftKernel.getReal();
        imag = fftKernel.getImag();
        
        for (int cnt = 0; cnt < real.length; cnt++) {
            System.out.println(String.format("%d %f \t+ %fi", cnt, real[cnt], imag[cnt]));
        }
        
        float[] cos = fftKernel.getCos();
        float[] sin = fftKernel.getSin();
        
        System.out.println("* cos\t\tsin");
        for (int cnt = 0; cnt < cos.length; cnt++) {
            System.out.println(String.format("%d %f\t%f", cnt, cos[cnt], sin[cnt]));
        }
        
        System.out.println(fftKernel.getExecutionMode());        
    }
    
    @Test
    public void testReverse() {
        int size = 8;
        
        FFTKernel fftKernel = new FFTKernel(size);
        
        float[] real = new float[size];
        float[] imag = new float[size];
        
        for (int cnt = 0; cnt < size; cnt++) {
            real[cnt] = (float) (
                1.0 * Math.sin(2 * Math.PI * cnt / size)
                + 2.0 * Math.cos(2 * Math.PI * cnt / size)
                + 3.0 * Math.sin(4 * Math.PI * cnt / size)
                + 4.0 * Math.cos(4 * Math.PI * cnt / size)
                + 5.0 * Math.sin(6 * Math.PI * cnt / size)
                + 6.0 * Math.cos(6 * Math.PI * cnt / size)
            );

            imag[cnt] = 0.0f;
        }
        
        fftKernel.fft(real, imag);
        
        fftKernel.ifft(fftKernel.getReal(), fftKernel.getImag());
        
        float[] resReal = fftKernel.getReal();
        float[] resImag = fftKernel.getImag();
        
        System.out.println("* fft->ifft\t\t\toriginal");
        for (int cnt = 0; cnt < resReal.length; cnt++) {
            System.out.println(String.format("%d %f\t+%fi\t%f\t+%fi", cnt, resReal[cnt], resImag[cnt], real[cnt], imag[cnt]));
        }
        
        System.out.println(fftKernel.getExecutionMode());        
    }
}

ハマったところ

// バタフライ演算
// buttCal 組の演算を buttStep 段繰り返す
// このようにループにしないと、GPGPU はステップごとに順に計算せずに
// 一気に全段計算してしまい、まともな結果が得られない
this.execute(buttCal, buttStep);

最初、

this.execute(計算指示書の配列数);

とやっていて、CPU での逐次処理ではうまくいくのに GPGPU で並列処理をしたら変な値になって悩んだ。
考えて見れば当たり前で、this.execute(計算指示書の配列数); で GPGPU で実行すると、Step1〜StepN のバタフライ演算が同時に実行されてしまう。Step 内のバタフライ演算は同時実行可能だけど、Step1,2,3...N は同時実行可能でなく、きちんと順番を守ってやる必要がある。
ということで Kernel.execute(range, pass) を使った。
めでたしめでたし

性能評価


Java#Others


添付ファイル: filenikkei.png 147件 [詳細] filefft.png 121件 [詳細] fileexecid.png 131件 [詳細] fileGPGPU.png 207件 [詳細] filepath.png 192件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS   sitemap
Last-modified: 2015-09-16 (水) 01:33:27 (444d)
ISBN10
ISBN13
9784061426061