7

乱数の比較的短い (5 ~ 100 要素) 配列を多数 (現在は数百万、最終的には数十億) 消費し、それらを使用してあまり精力的ではない計算を行うコードがあります。乱数は、まあ、ランダムです。理想的には、複数のコアで乱数を生成したいと思います。これは、乱数の生成がプロファイリングの実行時間の 50% を超えるためです。ただし、シングルスレッドのアプローチよりも遅くない方法で多数の小さなタスクを分散するのは困難です。

私のコードは現在、次のようになっています。

for(int i=0;i<1000000;i++){
    for(RealVector d:data){
        while(!converged){
            double[] shortVec = new double[5];
            for(int i=0;i<5;i++) shortVec[i]=rng.nextGaussian();
            double[] longerVec = new double[50];
            for(int i=0;i<50;i++) longerVec[i]=rng.nextGaussian();
            /*Do some relatively fast math*/
        }
    }
}

うまくいかなかった私が取ったアプローチは次のとおりです。

  • 1 つ以上のスレッドが ArrayBlockingQueue にデータを入力し、メイン ループが配列を消費してデータを入力します (ここでは、ボックス化/ボックス化解除がキラーでした)
  • 数学の非依存部分を実行しながら、Callable を使用してベクトルを生成する (未来を生成する) (間接処理のオーバーヘッドが、私が得た並列処理の利点を上回っているようです)
  • 2 つの ArrayBlockingQueue を使用し、それぞれがスレッドによって読み込まれます。1 つは短い配列用で、もう 1 つは長い配列用です (直接シングル スレッドの場合よりも約 2 倍遅くなります)。

私は、小さな独立したプリミティブの大きなストリームを並行して生成し、それらを単一のスレッドから消費するという一般的なケースを処理する方法ほど、特定の問題に対する「解決策」を探しているわけではありません。

4

2 に答える 2

5

パフォーマンスの問題は、個々のジョブが小さすぎるため、ほとんどの時間がジョブ自体の同期とキューイングに費やされていることです。考慮すべきことの 1 つは、小さなジョブの大規模なストリームを生成するのではなく、各作業スレッドに中規模のジョブのコレクションを配信して、答えを注釈付けすることです。

たとえば、最初のスレッドで反復 #0 を実行し、次のスレッドで反復 #1 を実行してループを反復する代わりに、最初のスレッドで反復 #0 から #999 などを実行するようにします。彼らは独立して作業しJob、計算の答えでクラスに注釈を付ける必要があります。そして最後に、完了したジョブのコレクション全体を として返すことができFutureます。

クラスJobは次のようになります。

public class Job {
    Collection<RealVector> dataCollection;
    Collection<SomeAnswer> answerCollection = new ArrayList<SomeAnswer>();
    public void run() {
        for (RealVector d : dataCollection) {
           // do the magic work on the vector
           while(!converged){
              ...
           }
           // put the associated "answer" in another collection
           answerCollection.add(someAnswer);
        }
    }
}
于 2012-08-03T18:28:16.743 に答える
4

これは、キューを使用するよりも効率的です。

  • double[]ペイロードは、バックグラウンド スレッドがデータを渡す前により多くのデータを生成できることを意味する配列です。
  • すべてのオブジェクトがリサイクルされます。

.

public class RandomGenerator {
    private final ExecutorService generator = Executors.newSingleThreadExecutor(new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r, "generator");
            t.setDaemon(true);
            return t;
        }
    });
    private final Exchanger<double[][]> exchanger = new Exchanger<>();
    private double[][] buffer;
    private int nextRow = Integer.MAX_VALUE;

    public RandomGenerator(final int rows, final int columns) {
        buffer = new double[rows][columns];
        generator.submit(new Callable<Void>() {
            @Override
            public Void call() throws Exception {
                Random random = new Random();
                double[][] buffer2 = new double[rows][columns];
                while (!Thread.interrupted()) {
                    for (int r = 0; r < rows; r++)
                        for (int c = 0; c < columns; c++)
                            buffer2[r][c] = random.nextGaussian();
                    buffer2 = exchanger.exchange(buffer2);
                }
                return null;
            }
        });
    }

    public double[] nextArray() throws InterruptedException {
        if (nextRow >= buffer.length) {
            buffer = exchanger.exchange(buffer);
            nextRow = 0;
        }
        return buffer[nextRow++];
    }
}

Random はスレッドセーフで同期されます。これは、各スレッドが同時に実行するには独自の Random が必要であることを意味します。

小さな独立したプリミティブの大きなストリームを並行して生成し、単一のスレッドからそれらを消費する一般的なケースを処理する方法。

を使用Exchanger<double[][]>してバックグラウンドで値を入力し、それらを効率的に渡します(GCのオーバーヘッドはあまりありません)

于 2012-08-03T18:55:06.590 に答える