4

floatたとえば、それぞれの平均を計算するなど、大きなベクトルの大きなセットに対して計算を行っているとします。

public static float avg(float[] data, int offset, int length) {
  float sum = 0;
  for (int i = offset; i < offset + length; i++) {
    sum += data[i];
  }
  return sum / length;
}

すべてのベクトルをメモリ内に保存している場合はfloat[]、次のようにループを実装できます。

float[] data; // <-- vectors here
float sum = 0;
for (int i = 0; i < nVectors; i++) {
  sum += avg(data, i * vectorSize, vectorSize);
}

ベクトルが代わりにファイルに保存されている場合、OSがすべてをキャッシュしたら、理論的には、最初のソリューションと同じくらい高速にメモリマッピングを行う必要があります。

RandomAccessFile file; // <-- vectors here
MappedByteBuffer buffer = file.getChannel().map(READ_WRITE, 0, 4*data.length);
FloatBuffer floatBuffer = buffer.asFloatBuffer();
buffer.load(); // <-- this forces the OS to cache the file

float[] vector = new float[vectorSize];
float sum = 0;
for (int i = 0; i < nVectors; i++) {
  floatBuffer.get(vector);
  sum += avg(vector, 0, vector.length);
}

ただし、私のテストでは、メモリマップドバージョンはメモリ内バージョンよりも約5倍遅いことが示されています。私はそれFloatBuffer.get(float[])がメモリをコピーしていることを知っています、そしてそれが減速の理由だと思います。もっと速くなることはできますか?メモリのコピーをまったく回避し、OSのバッファからデータを取得する方法はありますか?

試してみたい場合に備えて、この要点に完全なベンチマークをアップロードしました。

$ java -Xmx1024m ArrayVsMMap 100 100000 100

編集:

結局、このシナリオで私が得た最高のものはMappedByteBuffer、通常のシナリオを使用するよりもfloat[]35%も遅くなります。これまでの秘訣は次のとおりです。

  • 変換を回避するには、ネイティブバイトオーダーを使用します。buffer.order(ByteOrder.nativeOrder())
  • を使用MappedByteBufferしてラップFloatBufferbuffer.asFloatBuffer()
  • floatBuffer.get(int index)バルクバージョンの代わりにシンプルバージョンを使用すると、メモリのコピーが回避されます。

この要点で新しいベンチマークと結果を確認できます。

1.35のスローダウンは5の1つよりもはるかに優れていますが、それでも1にはほど遠いです。おそらくまだ何かが足りないか、JVMで改善する必要があるものです。

4

2 に答える 2

3

配列ベースの時間は途方もなく速いです!フロートあたり.0002ナノ秒を取得します。JVMはおそらくループを最適化しています。

これが問題です:

    void iterate() {
        for (int i = 0; i < nVectors; i++) {
            calc(data, i * vectorSize, vectorSize);
        }
    }

JVMは、これにはcalc副作用がないことを認識しているため、副作用もiterateありません。そのため、NOPに置き換えることができます。簡単な修正は、からの結果を累積してcalc返すことです。iterateタイミングループの結果についても同じことを行い、結果を出力する必要があります。これにより、オプティマイザーがすべてのコードを削除するのを防ぎます。

編集:

これはおそらくJava側のオーバーヘッドであり、メモリマッピング自体とは関係なく、Javaへのインターフェイスだけであるように見えます。次のテストを試してください。これは、FloatBuffer周りをラップByteBufferするだけbyte[]です。

  private static final class ArrayByteBufferTest extends IterationTest {
    private final FloatBuffer floatBuffer;
    private final int vectorSize;
    private final int nVectors;

    ArrayByteBufferTest(float[] data, int vectorSize, int nVectors) {
      ByteBuffer bb = ByteBuffer.wrap(new byte[data.length * 4]);
      for (int i = 0; i < data.length; i++) {
        bb.putFloat(data[i]);
      }
      bb.rewind();
      this.floatBuffer = bb.asFloatBuffer();
      this.vectorSize = vectorSize;
      this.nVectors = nVectors;
    }

    float iterate() {
      float sum = 0;
      floatBuffer.rewind();
      float[] vector = new float[vectorSize];
      for (int i = 0; i < nVectors; i++) {
        floatBuffer.get(vector);
        sum += calc(vector, 0, vector.length);
      }
      return sum;
    }
  }

フロート自体に対してほとんど作業を行っていないため(追加するだけで、おそらく1サイクル)、4バイトの読み取り、フロートの作成、および配列へのコピーのコストがすべて加算されます。少なくともベクトルが(L1?)キャッシュよりも大きくなるまでは、オーバーヘッドのベクトルを少なくして大きくするのに少し役立つことに気づきました。

于 2012-08-26T22:58:54.947 に答える
0

理論的には、同じように実行する必要がある理由はありません。マップされたソリューションは、ページフォールトとディスクI/Oを完全に予測できない程度に暗示しています。float[]配列はそうではありません。ファイル全体がメモリにマップされ、ファイルを変更せずマップされたままでページアウトされないという特別な場合を除いて、後者の方が高速であると期待する必要があります。これらの要因のほとんどは、制御または予測できません。

于 2012-08-27T00:35:06.563 に答える