1

Java で (非常に単純な) ベンチマークをプログラムしました。double値を指定された値まで単純にインクリメントし、時間がかかります。

6 コアのデスクトップでこのシングルスレッドまたは少量のスレッド (最大 100) を使用すると、ベンチマークは妥当で再現性のある結果を返します。

しかし、たとえば 1200 スレッドを使用すると、平均マルチコア期間はシングルコア期間よりも大幅に短くなります (約 10 倍以上)。使用するスレッドの数に関係なく、増分の合計量が同じであることを確認しました。

スレッドが増えるとパフォーマンスが大幅に低下するのはなぜですか? この問題を解決するためのトリックはありますか?

ソースを投稿していますが、問題があるとは思いません。

Benchmark.java:

package sibbo.benchmark;

import java.text.DecimalFormat;
import java.util.LinkedList;
import java.util.List;

public class Benchmark implements TestFinishedListener {
            private static final double TARGET = 1e10;
    private static final int THREAD_MULTIPLICATOR = 2;

    public static void main(String[] args) throws InterruptedException {
        Benchmark b = new Benchmark(TARGET);
        b.start();
    }

    private int coreCount;
    private List<Worker> workers = new LinkedList<>();
    private List<Worker> finishedWorkers = new LinkedList<>();
    private double target;

    public Benchmark(double target) {
        this.target = target;
        getSystemInfos();
        printInfos();
    }

    private void getSystemInfos() {
        coreCount = Runtime.getRuntime().availableProcessors();
    }

    private void printInfos() {
        System.out.println("Usable cores: " + coreCount);
        System.out.println("Multicore threads: " + coreCount *                 THREAD_MULTIPLICATOR);
        System.out.println("Loops per core: " + new DecimalFormat("###,###,###,###,##0").format(TARGET));

        System.out.println();
    }

    public synchronized void start() throws InterruptedException {
        Thread.currentThread().setPriority(Thread.MAX_PRIORITY);

        System.out.print("Initializing singlecore benchmark... ");
        Worker w = new Worker(this, 0);
        workers.add(w);

        Thread.sleep(1000);
        System.out.println("finished");

        System.out.print("Running singlecore benchmark... ");
        w.runBenchmark(target);
        wait();

        System.out.println("finished");
        printResult();

        System.out.println();
        // Multicore
        System.out.print("Initializing multicore benchmark...  ");
        finishedWorkers.clear();

        for (int i = 0; i < coreCount * THREAD_MULTIPLICATOR; i++) {
            workers.add(new Worker(this, i));
        }

        Thread.sleep(1000);
        System.out.println("finished");

        System.out.print("Running multicore benchmark...  ");

        for (Worker worker : workers) {
            worker.runBenchmark(target / THREAD_MULTIPLICATOR);
        }

        wait();

        System.out.println("finished");
        printResult();

        Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
    }

    private void printResult() {
        DecimalFormat df = new DecimalFormat("###,###,###,##0.000");

        long min = -1, av = 0, max = -1;
        int threadCount = 0;
        boolean once = true;

        System.out.println("Result:");

        for (Worker w : finishedWorkers) {
            if (once) {
                once = false;

                min = w.getTime();
                max = w.getTime();
            }

            if (w.getTime() > max) {
                max = w.getTime();
            }

            if (w.getTime() < min) {
                min = w.getTime();
            }

            threadCount++;
            av += w.getTime();

            if (finishedWorkers.size() <= 6) {
                System.out.println("Worker " + w.getId() + ": " + df.format(w.getTime() / 1e9) + "s");
            }
        }

        System.out.println("Min: " + df.format(min / 1e9) + "s, Max: " + df.format(max / 1e9) + "s, Av per Thread: "
                + df.format((double) av / threadCount / 1e9) + "s");
    }

    @Override
    public synchronized void testFinished(Worker w) {
        workers.remove(w);
        finishedWorkers.add(w);

        if (workers.isEmpty()) {
            notify();
        }
    }
}

ワーカー.java:

package sibbo.benchmark;

public class Worker implements Runnable {
    private double value = 0;
    private long time;
    private double target;
    private TestFinishedListener l;
    private final int id;

    public Worker(TestFinishedListener l, int id) {
        this.l = l;
        this.id = id;

        new Thread(this).start();
    }

    public int getId() {
        return id;
    }

    public synchronized void runBenchmark(double target) {
        this.target = target;
        notify();
    }

    public long getTime() {
        return time;
    }

    @Override
    public void run() {
        synWait();
        value = 0;
        long startTime = System.nanoTime();

        while (value < target) {
            value++;
        }

        long endTime = System.nanoTime();
        time = endTime - startTime;

        l.testFinished(this);
    }

    private synchronized void synWait() {
        try {
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
4

2 に答える 2

6

OS (または Java スレッド スケジューラ、またはその両方) は、アプリケーション内のすべてのスレッド間でバランスをとって、すべてのスレッドに何らかの作業を実行する機会を与えようとしていること、およびスレッド間の切り替えにはゼロではないコストがかかることを理解する必要があります。スレッド。1200 スレッドの場合、プロセッサが実際の作業を行うよりも多くの時間をコンテキスト切り替えに費やしている転換点に到達した (おそらくそれをはるかに超えた) ことになります。

大まかな類推は次のとおりです。

部屋 A で行う仕事が 1 つあります。部屋 A に 1 日 8 時間立ち、仕事をします。

その後、上司がやってきて、部屋 B でも仕事をしなければならないと言いました。次に、定期的に部屋 A を出て、廊下を歩いて部屋 B に移動し、戻ってくる必要があります。その歩行には 1 日 1 分かかります。今、あなたは 3 時間 59.5 分を各仕事に費やし、部屋の間を 1 分歩いています。

ここで、作業する部屋が 1200 あるとします。実際の作業よりも、部屋の間を移動する時間が長くなります。これは、プロセッサを配置した状況です。コンテキスト間の切り替えに非常に多くの時間を費やしているため、実際の作業は完了していません。

編集: さて、以下のコメントによると、次の作業に移る前に各部屋で一定の時間を過ごす場合があります。作業は進行しますが、部屋間のコンテキスト スイッチの数は、1 つのタスクの全体的な実行時間に影響します。

于 2012-07-05T15:04:07.643 に答える
1

問題は解決したと思いますが、今のところ解決策はありません。

各スレッドが実行されて作業の一部を実行する時間を測定すると、スレッドの合計量ごとに可能な最小値が異なります。最大値は毎回同じです。スレッドが最初に開始され、その後非常に頻繁に一時停止され、最後に終了する場合。たとえば、この最大値は 10 秒です。使用するスレッドの数に関係なく、すべてのスレッドで実行される操作の合計量が同じであると仮定すると、異なる量のスレッドを使用すると、単一のスレッドで実行される操作の量を変更する必要があります。たとえば、1 つのスレッドを使用すると、1000 の操作を実行する必要がありますが、10 のスレッドを使用すると、すべてのスレッドが 100 の操作を実行する必要があります。現在、10 個のスレッドを使用すると、1 つのスレッドが使用できる最小時間は、1 つのスレッドを使用する場合よりもはるかに短くなります。したがって、すべてのスレッドが作業を行うのに必要な平均時間を計算するのはナンセンスです。10 個のスレッドを使用する最小値は 1 秒です。これは、1 つのスレッドが中断することなく作業を行っている場合に発生します。

編集

解決策は、最初のスレッドの開始から最後のスレッドの完了までの時間を単純に測定することです。

于 2012-07-05T15:26:47.880 に答える