7

基本的に、今日はこのコードを最適化する必要がありました。最初の 100 万の開始番号に対して、関数によって生成された最長のシーケンスを見つけようとします。

public static void main(String[] args) {
    int mostLen = 0;
    int mostInt = 0;
    long currTime = System.currentTimeMillis();
    for(int j=2; j<=1000000; j++) {
        long i = j;
        int len = 0;
        while((i=next(i)) != 1) {
            len++;
        }
        if(len > mostLen) {
            mostLen = len;
            mostInt = j;
        }
    }
    System.out.println(System.currentTimeMillis() - currTime);
    System.out.println("Most len is " + mostLen + " for " + mostInt);
}


static long next(long i) {
    if(i%2==0) {
        return i/2;
    } else {
        return i*3+1;
    }
}

私の間違いは、マルチスレッドを導入しようとしたことです:

void doSearch() throws ExecutionException, InterruptedException {
    final int numProc = Runtime.getRuntime().availableProcessors();
    System.out.println("numProc = " + numProc);
    ExecutorService executor = Executors.newFixedThreadPool(numProc);
    long currTime = System.currentTimeMillis();
    List<Future<ValueBean>> list = new ArrayList<Future<ValueBean>>();
    for (int j = 2; j <= 1000000; j++) {
        MyCallable<ValueBean> worker = new MyCallable<ValueBean>();
        worker.setBean(new ValueBean(j, 0));
        Future<ValueBean> f = executor.submit(worker);
        list.add(f);
    }
    System.out.println(System.currentTimeMillis() - currTime);

    int mostLen = 0;
    int mostInt = 0;
    for (Future<ValueBean> f : list) {
        final int len = f.get().getLen();
        if (len > mostLen) {
            mostLen = len;
            mostInt = f.get().getNum();
        }
    }
    executor.shutdown();
    System.out.println(System.currentTimeMillis() - currTime);
    System.out.println("Most len is " + mostLen + " for " + mostInt);
}

public class MyCallable<T> implements Callable<ValueBean> {
    public ValueBean bean;

    public void setBean(ValueBean bean) {
        this.bean = bean;
    }

    public ValueBean call() throws Exception {
        long i = bean.getNum();
        int len = 0;
        while ((i = next(i)) != 1) {
            len++;
        }
        return new ValueBean(bean.getNum(), len);
    }
}

public class ValueBean {
    int num;
    int len;

    public ValueBean(int num, int len) {
        this.num = num;
        this.len = len;
    }

    public int getNum() {
        return num;
    }

    public int getLen() {
        return len;
    }
}

long next(long i) {
    if (i % 2 == 0) {
        return i / 2;
    } else {
        return i * 3 + 1;
    }
}

残念ながら、マルチスレッド バージョンは、4 つのプロセッサ (コア) でシングルスレッド バージョンよりも 5 倍遅く動作しました。

次に、もう少し大雑把なアプローチを試みました。

static int mostLen = 0;
static int mostInt = 0;

synchronized static void updateIfMore(int len, int intgr) {
    if (len > mostLen) {
        mostLen = len;
        mostInt = intgr;
    }
}

public static void main(String[] args) throws InterruptedException {
    long currTime = System.currentTimeMillis();
    final int numProc = Runtime.getRuntime().availableProcessors();
    System.out.println("numProc = " + numProc);
    ExecutorService executor = Executors.newFixedThreadPool(numProc);
    for (int i = 2; i <= 1000000; i++) {
        final int j = i;
        executor.execute(new Runnable() {
            public void run() {
                long l = j;
                int len = 0;
                while ((l = next(l)) != 1) {
                    len++;
                }
                updateIfMore(len, j);
            }
        });
    }
    executor.shutdown();
    executor.awaitTermination(30, TimeUnit.SECONDS);
    System.out.println(System.currentTimeMillis() - currTime);
    System.out.println("Most len is " + mostLen + " for " + mostInt);
}


static long next(long i) {
    if (i % 2 == 0) {
        return i / 2;
    } else {
        return i * 3 + 1;
    }
}

はるかに高速に動作しましたが、それでもシングル スレッド アプローチよりも低速でした。

マルチスレッドの実行方法を台無しにしたためではなく、この特定の計算/アルゴリズムが並列計算に適していないことを願っています。メソッドを次のように置き換えて、計算を変更してプロセッサを集中的に使用する場合next:

long next(long i) {
    Random r = new Random();
    for(int j=0; j<10; j++) {
        r.nextLong();
    }
    if (i % 2 == 0) {
        return i / 2;
    } else {
        return i * 3 + 1;
    }
}

両方のマルチスレッド バージョンは、4 コア マシン上でシングルスレッド バージョンの 2 倍以上の速度で実行を開始します。

したがって、マルチスレッドを導入する価値があるかどうかを判断するために使用できるしきい値があることは明らかです。私の質問は次のとおりです。

特定の計算が、並行して実行することによって最適化されるのに十分なほど集中的であるかどうかを判断するのに役立つ基本的なルールは何ですか?

4

4 に答える 4

4

マルチスレッドを効率的に実装するための鍵は、コストが高すぎないようにすることです。ハードウェアに大きく依存するため、決まったルールはありません。

スレッドの開始と停止には高いコストがかかります。もちろん、Runnables を実行するために多数のワーカー スレッドを使用するため、これらのコストを大幅に削減するエグゼキューター サービスを既に使用しています。ただし、各 Runnable にはまだオーバーヘッドが伴います。ランナブルの数を減らし、それぞれが実行する必要のある作業量を増やすと、パフォーマンスが向上しますが、エグゼキューター サービスがワーカー スレッドに効率的に分散できるように、十分な数のランナブルが必要です。

開始値ごとに 1 つのランナブルを作成することを選択したため、最終的に 1000000 個のランナブルを作成することになります。各 Runnable にたとえば 1000 の開始値のバッチを実行させると、おそらくはるかに良い結果が得られるでしょう。つまり、必要なランナブルは 1000 個だけで、オーバーヘッドが大幅に削減されます。

于 2012-05-22T05:35:10.250 に答える
2

「パフォーマンスの向上は、コンテキストの切り替えとスレッドの作成のコストよりも大きくなりますか?」

これは非常にOS、言語、ハードウェアに依存するコストです。この質問には、Javaでのコストについての議論がありますが、コストの計算方法に関するいくつかの数値といくつかの指針があります。

また、CPUを集中的に使用する作業のために、CPUごとに1つ以下のスレッドが必要です。その数を計算する方法に関するスレッドへのポインタを提供してくれたDavidHarknessに感謝します。

于 2012-05-22T05:24:57.710 に答える
2

これには、あなたが考慮していない別の要素があると思います。並列化は、作業単位が相互に依存していない場合に最適に機能します。後の計算結果が前の計算結果に依存する場合、計算の並列実行は最適ではありません。依存性は、「2 番目の値を計算するために最初の値が必要」という意味で強い可能性があります。その場合、タスクは完全に逐次的であり、後の値は前の計算を待たなければ計算できません。「最初の値があれば、2 番目の値をより速く計算できる」という意味で、より弱い依存関係が存在する可能性もあります。その場合、並列化のコストは、一部の作業が重複する可能性があることです。

この問題は、マルチスレッドを使用せずに最適化するのに適しています。これは、以前の結果が既に手元にある場合、後の値の一部をより高速に計算できるためです。たとえば、j == 4。内側のループを通過すると が生成i == 2されますが、2 回前の反復の結果を計算しただけなのでj == 2、 の値を保存lenすると、len(4) = 1 + len(2) として計算できます。

配列を使用して以前に計算された値を格納lenし、メソッドを少しいじるとnext、タスクを 50 倍以上速く完了することができます。

于 2012-05-22T07:13:54.187 に答える
1

スレッドが他のスレッドと対話せずに (直接または共通データを介して) 実行できる作業量を見積もります。その作業が 1 マイクロ秒以下で完了すると、オーバーヘッドが大きくなり、マルチスレッドは役に立ちません。1 ミリ秒以上であれば、マルチスレッドがうまく機能するはずです。間にある場合は、実験的なテストが必要です。

于 2012-05-22T06:48:48.733 に答える