4

非常に短期間の大量のタスクに適したExecutorServiceはありますか?同期待機に切り替える前に、内部でビジー待機を試みるものを想定しています。タスクの順序を維持することは重要ではありませんが、メモリの一貫性を強制することは可能であるはずです(すべてのタスクが発生します-メインスレッドが制御を取り戻す前に)。

以下に投稿されたテストは、それぞれが連続して100秒を生成する100,000のタスクで構成されていますdouble。コマンドラインパラメータとしてスレッドプールのサイズを受け入れ、常にシリアルバージョンとパラレルバージョンをテストします。(コマンドライン引数が指定されていない場合は、シリアルバージョンのみがテストされます。)パラレルバージョンは固定サイズのスレッドプールを使用し、タスクの割り当ては時間測定の一部でもありません。それでも、パラレルバージョンはシリアルバージョンよりも速くなることはありません。私は最大80スレッドを試しました(40個のハイパースレッドコアを備えたマシンで)。なんで?

import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorPerfTest {
    public static final int TASKS = 100000;
    public static final int SUBTASKS = 100;

    static final ThreadLocal<Random> R = new ThreadLocal<Random>() {
        @Override
        protected synchronized Random initialValue() {
            return new Random();
        }
    };

    public class SeqTest implements Runnable {
        @Override
        public void run() {
            Random r = R.get();
            for (int i = 0; i < TASKS; i++)
                for (int j = 0; j < SUBTASKS; j++)
                    r.nextDouble();
        }
    }

    public class ExecutorTest implements Runnable {
        private final class RandomGenerating implements Callable<Double> {
            @Override
            public Double call() {
                double d = 0;
                Random r = R.get();
                for (int j = 0; j < SUBTASKS; j++)
                    d = r.nextDouble();
                return d;
            }
        }

        private final ExecutorService threadPool;
        private ArrayList<Callable<Double>> tasks = new ArrayList<Callable<Double>>(TASKS);

        public ExecutorTest(int nThreads) {
            threadPool = Executors.newFixedThreadPool(nThreads);
            for (int i = 0; i < TASKS; i++)
                tasks.add(new RandomGenerating());
        }

        public void run() {
            try {
                threadPool.invokeAll(tasks);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                threadPool.shutdown();
            }
        }
    }

    public static void main(String[] args) {
        ExecutorPerfTest executorPerfTest = new ExecutorPerfTest();
        if (args.length > 0)
            executorPerfTest.start(new String[]{});
        executorPerfTest.start(args);
    }

    private void start(String[] args) {
        final Runnable r;
        if (args.length == 0) {
            r = new SeqTest();
        }
        else {
            final int nThreads = Integer.parseInt(args[0]);
            r = new ExecutorTest(nThreads);
        }
        System.out.printf("Starting\n");
        long t = System.nanoTime();
        r.run();
        long dt = System.nanoTime() - t;
        System.out.printf("Time: %.6fms\n", 1e-6 * dt);
    }
}
4

1 に答える 1

2

toを呼び出すと、からタスクを読み取るExecutors.newFixedThreadPool(nThreads)が作成されます。エグゼキュータ内のすべてのスレッドは、次のタスクを取得するために同じキューにロックされます。ThreadPoolExecutorLinkedBlockingQueue

各タスクのサイズが非常に小さく、引用するスレッド/ CPUの数が比較的多いことを考えると、高度なロック競合とコンテキスト切り替えが発生するため、プログラムの実行速度が遅い可能性があります。

ReentrantLockスレッドが放棄してブロックする前にロックを取得しようとしている間、すでに使用されているの実装はLinkedBlockingQueue短時間(最大約1us)スピンすることに注意してください。

ユースケースで許可されている場合は、代わりにDisruptorパターンを使用してみてください。http://lmax-exchange.github.com/disruptor/を参照してください。

于 2012-11-04T18:25:56.263 に答える