9

Executor フレームワークについて詳しく知るために、いくつかの Java コードを作成しました。

具体的には、コラッツ仮説を検証するコードを書きました。これは、次の関数を任意の整数に繰り返し適用すると、最終的に 1 になることを示しています。

f(n) = ((n % 2) == 0) ? n/2 : 3*n + 1

CH はまだ証明されていませんが、Executor について学ぶには良い方法だと思いました。各スレッドには、チェックする整数の範囲 [l,u] が割り当てられます。

具体的には、私のプログラムは 3 つの引数を取ります - N (CH をチェックしたい数)、RANGESIZE (スレッドが処理しなければならない間隔の長さ)、および NTHREAD、スレッドプールのサイズです。

私のコードは正常に動作しますが、1 スレッドから 4 スレッドに変更したときは 30% 程度の速度向上が期待したほどではありませんでした。

私の論理では、計算は完全に CPU バウンドであり、各サブタスク (CH の固定サイズ範囲のチェック) にはほぼ同じ時間がかかります。

3 倍から 4 倍の速度の向上が見られない理由について、誰か考えがありますか?

スレッドの数を (マシン、JVM、OS とともに) 増やしてランタイムを報告できれば、それも素晴らしいことです。

仕様

ランタイム:

java -d64 -server -cp . Collat​​z 10000000 1000000 4 => 4 スレッド、28412 ミリ秒かかります

java -d64 -server -cp . Collat​​z 10000000 1000000 1 => 1 スレッド、38286 ミリ秒かかります

プロセッサー:

2.4GHZ、4GB のクアッドコア Intel Q6600。マシンがアンロードされます。

ジャワ:

Java バージョン "1.6.0_15" Java(TM) SE ランタイム環境 (ビルド 1.6.0_15-b03) Java HotSpot(TM) 64 ビット サーバー VM (ビルド 14.1-b02、混合モード)

OS:

Linux quad0 2.6.26-2-amd64 #1 SMP Tue Mar 9 22:29:32 UTC 2010 x86_64 GNU/Linux

コード: (投稿するコードを取得できません。SO 要件には長すぎると思います。ソースはGoogle ドキュメントで入手できます。

import java.math.BigInteger;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class MyRunnable implements Runnable {
  public int lower;
  public int upper;

  MyRunnable(int lower, int upper) {
    this.lower = lower;
    this.upper = upper;
  }

  @Override
  public void run() {
    for (int i = lower ; i <= upper; i++ ) {
      Collatz.check(i);
    }
    System.out.println("(" + lower + "," + upper + ")" );
  }
}


public class Collatz {

  public static boolean check( BigInteger X ) {
    if (X.equals( BigInteger.ONE ) ) {
      return true;
    } else if ( X.getLowestSetBit() == 1 ) { 
      // odd
      BigInteger Y = (new BigInteger("3")).multiply(X).add(BigInteger.ONE);
      return check(Y);
    } else {
      BigInteger Z = X.shiftRight(1); // fast divide by 2
      return check(Z);
    }
  }

  public static boolean check( int x ) {
    BigInteger X = new BigInteger( new Integer(x).toString() );
    return check(X);
  }

  static int N = 10000000;
  static int RANGESIZE = 1000000;
  static int NTHREADS = 4;

  static void parseArgs( String [] args ) {

    if ( args.length >= 1 ) {
      N = Integer.parseInt(args[0]);
    }
    if ( args.length >= 2 ) {
      RANGESIZE = Integer.parseInt(args[1]);
    }
    if ( args.length >= 3 ) {
      NTHREADS = Integer.parseInt(args[2]);
    }
  }

  public static void maintest(String [] args ) {
    System.out.println("check(1): " + check(1));
    System.out.println("check(3): " + check(3));
    System.out.println("check(8): " + check(8));
    parseArgs(args);
  }

  public static void main(String [] args) {
    long lDateTime = new Date().getTime();
    parseArgs( args );
    List<Thread> threads = new ArrayList<Thread>();
    ExecutorService executor = Executors.newFixedThreadPool( NTHREADS );
    for( int i = 0 ; i < (N/RANGESIZE); i++) {
      Runnable worker = new MyRunnable( i*RANGESIZE+1, (i+1)*RANGESIZE );
      executor.execute( worker );
    }
    executor.shutdown();
    while (!executor.isTerminated() ) {
    }
    System.out.println("Finished all threads");
    long fDateTime = new Date().getTime();
    System.out.println("time in milliseconds for checking to " + N + " is " + 
                            (fDateTime - lDateTime )  + 
                            " (" + N/(fDateTime - lDateTime ) + " per ms)" );
  }
}
4

4 に答える 4

11

ビジー待機は問題になる可能性があります。

while (!executor.isTerminated() ) { 
} 

代わりに使用できますawaitTermination()

while (!executor.awaitTermination(1, TimeUnit.SECONDS)) {}
于 2010-11-24T21:04:16.580 に答える
2

BigInteger を使用しています。多くのレジスタスペースを消費します。コンパイラ レベルで発生する可能性が最も高いのは、プロセスがメモリにバインドされるレジスタ スピルです。

また、結果のタイミングを計っている場合、JVM がスレッドを割り当ててスレッド プールを処理するために必要な余分な時間を考慮していないことに注意してください。

定数文字列を使用している場合にも、メモリの競合が発生する可能性があります。すべての文字列は共有文字列プールに格納されるため、Java が本当に巧妙でない限り、ボトルネックになる可能性があります。

全体として、この種のものに Java を使用することはお勧めしません。pthreads を使用することは、あなたにとってより良い方法です。

于 2010-11-24T21:04:59.520 に答える
2

@axtavtが答えたように、忙しい待機は問題になる可能性があります。それは答えの一部であるため、最初に修正する必要がありますが、すべてではありません。何らかの理由で2つのコアでボトルネックになっているように見えるため、あなたの場合(Q6600の場合)には役に立たないようです。別のコアがビジーループに利用可能であり、明らかな速度低下はありませんが、私のCore i5では4 スレッド バージョンを著しく高速化します。

Q6600 の場合、特定のアプリは、利用可能な共有キャッシュの量またはその CPU のアーキテクチャに固有の何かによって制限されると思われます。Q6600 には 2 つの 4MB L2 キャッシュがあり、これは CPU がそれらを共有していることを意味し、L3 キャッシュはありません。私のコア i5 では、各 CPU に専用の L2 キャッシュがあります (256K の場合、さらに大きな 8MB の共有 L3 キャッシュがあります。CPU ごとのキャッシュが 256K 増えると違いが生じる可能性があります...そうでない場合は、他のアーキテクチャが賢明です。

Collat​​z.java を実行している Q6600 と Core i5 750 の比較を次に示します。

私の仕事用 PC も Q6600 @ 2.4 GHz で、6 GB RAM、Windows 7 64 ビット、および JDK 1.6.0_21* (64 ビット) を使用した場合の基本的な結果は次のとおりです。

  • 10000000 500000 1 (3 回の実行の平均): 36982 ミリ秒
  • 10000000 500000 4 (3 回の実行の平均): 21252 ミリ秒

確かに高速ですが、期待する 4 分の 1 の時間、または半分の時間で完了するわけではありません (大まかに言えば半分より少し多いですが、それについては後で詳しく説明します)。私の場合、ワークユニットのサイズを半分にし、デフォルトの最大ヒープを 1500m に設定したことに注意してください。

Core i5 750 (ハイパースレッディングなしの 4 コア)、4GB RAM、Windows 7 64 ビット、jdk 1.6.0_22 (64 ビット) の自宅で:

  • 10000000 500000 1 (3 回の実行の平均) 32677 ミリ秒
  • 10000000 500000 4 (3 回の実行の平均) 8825 ミリ秒
  • 10000000 500000 4 (3 回の実行の平均) 11475 ミリ秒 (ビジー待機の修正なし、参照用)

4 スレッド バージョンは、ビジー待機ループが削除されると、1 スレッド バージョンの 27% の時間を要します。ずっといい。明らかに、コードは 4 つのコアを効率的に使用できます...

  • 注: Java 1.6.0_18 以降では、デフォルトのヒープ設定が変更されています。したがって、デフォルトのヒープ サイズは、職場の PC でほぼ 1500m、自宅の PC で約 1000m です。

ガベージ コレクションが発生して 4 スレッド バージョンの速度が少し低下する場合に備えて、デフォルトのヒープを増やすことをお勧めします。役立つかもしれませんが、そうでないかもしれません。

少なくともあなたの例では、より大きなワークユニットサイズが結果をわずかにゆがめている可能性があります.4つのスレッドがより長い時間ビジー状態に保たれるため、半分にすると少なくとも2倍の速度に近づくことができます. Q6600がこの特定のタスクではるかに優れているとは思いません...それがキャッシュであろうと、他の固有のアーキテクチャであろうと.

いずれの場合も、単純に「java Collat​​z 10000000 500000 X」を実行しています。ここで、x = 示されたスレッドの数です。

私があなたのJavaファイルに加えた唯一の変更は、printlnの1つを印刷物にすることでした。そのため、ワークユニットあたり500000で実行した場合の改行が少なくなり、コンソールで一度により多くの結果を表示できるようになり、ビジーを捨てましたこれは i5 750 では重要ですが、Q6600 では違いがありませんでした。

于 2010-11-25T02:05:44.803 に答える
-1

submit 関数を使用してみてから、スレッドが終了したかどうかを確認するためにそれらをチェックしている Future を監視する必要があります。

Terminate は、シャットダウンするまで戻りません。

Future submit(Runnable task) Runnable タスクを実行のためにサブミットし、そのタスクを表す Future を返します。

isTerminated() シャットダウン後にすべてのタスクが完了した場合に true を返します。

これを試して...

public static void main(String[] args) {
    long lDateTime = new Date().getTime();
    parseArgs(args);
    List<Thread> threads = new ArrayList<Thread>();
    List<Future> futures = new ArrayList<Future>();

    ExecutorService executor = Executors.newFixedThreadPool(NTHREADS);
    for (int i = 0; i < (N / RANGESIZE); i++) {
        Runnable worker = new MyRunnable(i * RANGESIZE + 1, (i + 1) * RANGESIZE);
        futures.add(executor.submit(worker));
    }
    boolean done = false;
    while (!done) {
        for(Future future : futures) {
            done = true;
            if( !future.isDone() ) {
                done = false;
                break;
            }
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    System.out.println("Finished all threads");
    long fDateTime = new Date().getTime();
    System.out.println("time in milliseconds for checking to " + N + " is " +
            (fDateTime - lDateTime) +
            " (" + N / (fDateTime - lDateTime) + " per ms)");
    System.exit(0);
}
于 2010-11-24T21:10:31.030 に答える