私はJavaプログラムの性質上、多くのCPUを使用するプログラムを書いています。しかし、その多くは並行して実行でき、プログラムをマルチスレッドにしました。実行すると、必要になるまで1つのCPUのみを使用し、別のCPUを使用するようです-Javaで異なるスレッドを異なるコアで実行するためにできることはありますか/ CPU?
私がそれを実行すると、別のCPUを使用するよりも必要になるまで1つのCPUのみを使用するようです-Javaで異なるコア/ CPUで異なるスレッドを実行するためにできることはありますか? ?
質問のこの部分は、アプリケーションをマルチスレッド対応にするという問題に既に対処していることを意味すると解釈します。それにもかかわらず、すぐに複数のコアの使用を開始するわけではありません。
「強制する方法はありますか...」に対する答えは、直接ではありません。 JVMやホストOSは、使用する「ネイティブ」スレッドの数と、それらのスレッドを物理プロセッサにマップする方法を決定します。チューニングにはいくつかのオプションがあります。例えば、私は このページ を見つけましたJava Solarisでのスレッド化の調整方法について話します。そして このページ は他のことができることについて話しますマルチスレッドアプリケーションの速度を低下させます。
Javaでマルチスレッドを実行するには、2つの基本的な方法があります。これらのメソッドを使用して作成する各論理タスクは、必要に応じて利用可能なときに新しいコアで実行する必要があります。
方法1:RunnableまたはThreadオブジェクト(コンストラクターでRunnableを取得できる)を定義し、Thread.start()メソッドで実行を開始します。 OSが提供するすべてのコア(一般的に負荷の少ないコア)で実行されます。
チュートリアル: スレッドの定義と開始
方法2:Runnable(値を返さない場合)またはCallable(そうする場合)インターフェイスを実装するオブジェクトを定義します。これには処理コードが含まれます。これらをタスクとしてJava.util.concurrentパッケージからExecutorServiceに渡します。 Java.util.concurrent.Executorsクラスには、標準の便利な種類のExecutorServicesを作成するための多数のメソッドがあります。 リンク Executorsチュートリアルへ。
個人的な経験から、エグゼキューターの固定およびキャッシュされたスレッドプールは非常に優れていますが、スレッド数を微調整する必要があります。 Runtime.getRuntime()。availableProcessors()は、実行時に使用可能なコアをカウントするために使用できます。アプリケーションの終了時にスレッドプールをシャットダウンする必要があります。シャットダウンしないと、ThreadPoolスレッドの実行が継続されるため、アプリケーションは終了しません。
優れたマルチコアパフォーマンスを得るのは難しい場合があり、落とし穴がたくさんあります:
もう1つの問題:作業の制御が難しい!タスクを作成して送信する1つのマネージャースレッドと、(ExecutorServiceを使用して)ワークキューを持つ2つの作業スレッドを用意することをお勧めします。
ここで重要な点に触れているだけです。マルチスレッドプログラミングは、多くの専門家によって最も難しいプログラミングの課題の1つと考えられています。それは直感的ではなく、複雑であり、抽象化はしばしば弱いです。
編集-ExecutorServiceを使用した例:
public class TaskThreader {
class DoStuff implements Callable {
Object in;
public Object call(){
in = doStep1(in);
in = doStep2(in);
in = doStep3(in);
return in;
}
public DoStuff(Object input){
in = input;
}
}
public abstract Object doStep1(Object input);
public abstract Object doStep2(Object input);
public abstract Object doStep3(Object input);
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
ArrayList<Callable> tasks = new ArrayList<Callable>();
for(Object input : inputs){
tasks.add(new DoStuff(input));
}
List<Future> results = exec.invokeAll(tasks);
exec.shutdown();
for(Future f : results) {
write(f.get());
}
}
}
まず、プログラムが複数のコアでfasterを実行することを自分で証明する必要があります。多くのオペレーティングシステムは、同じコア上でプログラムスレッドを実行するように努力します可能な限り。
同じコアで実行すると、多くの利点があります。 CPUキャッシュはホットです。つまり、そのプログラムのデータがCPUにロードされます。ロック/モニター/同期オブジェクトはCPUキャッシュにあります。つまり、他のCPUはバスを介してキャッシュ同期操作を行う必要がありません(高価です!)。
プログラムを常に同じCPUで非常に簡単に実行できる1つのことは、ロックと共有メモリの過剰使用です。スレッドは互いに対話しないでください。スレッドが同じメモリ内で同じオブジェクトを使用する頻度が少ないほど、異なるCPUで実行される頻度が高くなります。同じメモリを使用する頻度が高いほど、他のスレッドの待機をブロックしなければならない頻度が高くなります。
OSは、別のスレッドの1つのスレッドブロックを検出するたびに、可能な限り同じCPUでそのスレッドを実行します。 CPU間バスを介して移動するメモリの量を減らします。それが私があなたのプログラムで見るものを引き起こしていると私が推測することです。
まず、 "Concurrency in Practice" by Brian Goetz を読むことをお勧めします。
これは、並行Javaプログラミングを説明している最高の本です。
並行性は「習得が容易で、習得が困難」です。私はそれを試みる前に主題について十分に読むことをお勧めします。マルチスレッドプログラムが99.9%の時間で正しく動作し、0.1%で失敗するのは非常に簡単です。ただし、開始するためのヒントを次に示します。
プログラムに複数のコアを使用させるには、2つの一般的な方法があります。
最低レベルでは、 スレッドの作成と破棄 が可能です。 Javaを使用すると、移植性のあるクロスプラットフォーム方式でスレッドを簡単に作成できます。
常にスレッドを作成および破棄するにはコストがかかる傾向があるため、Javaには Executors が含まれ、再利用可能なスレッドプールが作成されます。タスクはエグゼキューターに割り当てることができます、および結果はFutureオブジェクトを介して取得できます。
通常、1つには小さなタスクに分割できるタスクがありますが、最終結果を一緒に戻す必要があります。たとえば、マージソートでは、すべてのコアがソートを実行するまで、リストをより小さな部分に分割できます。ただし、各サブリストはソートされているため、最終的なソート済みリストを取得するには、サブリストをマージする必要があります。これは「分割統治」の問題であることがかなり一般的であるため、基礎となる配布と参加を処理できる JSRフレームワーク があります。このフレームワークは、おそらくJava 7。
JavaでCPUアフィニティを設定する方法はありません。 http://bugs.Sun.com/bugdatabase/view_bug.do?bug_id=4234402
必要な場合は、JNIを使用してネイティブスレッドを作成し、それらのアフィニティを設定します。
lot Callableの形式でExecutorServiceに渡され、invokeAll(...)で実行されるプログラムを作成する必要があります。
その後、Executorsクラスから実行時に適切な実装を選択できます。提案は、ビジー状態を保つためにCPUコアの数にほぼ対応する数でExecutors.newFixedThreadPool()を呼び出すことです。
以下のAPIを使用できます Executors with Java 8 version
public static ExecutorService newWorkStealingPool()
使用可能なすべてのプロセッサーをそのターゲット並列処理レベルとして使用して、ワークスチールスレッドプールを作成します。
ワークスチールメカニズムにより、アイドルスレッドはビジースレッドのタスクキューからタスクをスチールし、全体的なスループットが増加します。
grepcode から、newWorkStealingPool
の実装は次のとおりです。
/**
* Creates a work-stealing thread pool using all
* {@link Runtime#availableProcessors available processors}
* as its target parallelism level.
* @return the newly created thread pool
* @see #newWorkStealingPool(int)
* @since 1.8
*/
public static ExecutorService newWorkStealingPool() {
return new ForkJoinPool
(Runtime.getRuntime().availableProcessors(),
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
JVMのパフォーマンスチューニングについては、以前に なぜJavaコードはすべてのCPUコアを使用しないのですか? で説明されています。これはJVMにのみ適用されるため、アプリケーションは既にスレッドを使用している(そして、多少なりとも「正しく」):
http://ch.Sun.com/sunnews/events/2009/apr/adworkshop/pdf/5-1-Java-Performance.pdf
最も簡単なことは、プログラムを複数のプロセスに分割することです。 OSはそれらをコア全体に割り当てます。
プログラムを複数のスレッドに分割し、JVMがそれらを適切に割り当てることを信頼することは、やや困難です。これは、一般に、利用可能なハードウェアを利用するために人々が行うことです。
編集
マルチプロセッシングプログラムを「簡単に」するにはどうすればよいですか?これがパイプラインのステップです。
public class SomeStep {
public static void main( String args[] ) {
BufferedReader stdin= new BufferedReader( System.in );
BufferedWriter stdout= new BufferedWriter( System.out );
String line= stdin.readLine();
while( line != null ) {
// process line, writing to stdout
line = stdin.readLine();
}
}
}
パイプラインの各ステップは同様に構造化されています。どんな処理が含まれていても、9行のオーバーヘッド。
これは絶対的に最も効率的ではないかもしれません。しかし、それは非常に簡単です。
並行プロセスの全体的な構造は、JVMの問題ではありません。 OSの問題なので、シェルを使用してください。
Java -cp pipline.jar FirstStep | Java -cp pipline.jar SomeStep | Java -cp pipline.jar LastStep
残っているのは、パイプライン内のデータオブジェクトのシリアル化を行うことだけです。標準シリアル化はうまく機能します。 http://Java.Sun.com/developer/technicalArticles/Programming/serialization/ を読んで、シリアル化の方法に関するヒントを確認してください。これを行うには、BufferedReader
とBufferedWriter
をObjectInputStream
とObjectOutputStream
に置き換えることができます。
この問題はJava Parallel Proccesing Framework(JPPF)に関連していると思います。これを使用すると、異なるプロセッサで異なるジョブを実行できます。