web-dev-qa-db-ja.com

Java LinkedBlockingQueueのパフォーマンスの問題

これはstackoverflowに関する私の最初の投稿です...誰かが私を助けてくれることを願っています

Java 6 LinkedBlockingQueueで大きなパフォーマンスの低下があります。最初のスレッドでいくつかのオブジェクトを生成し、それをキューにプッシュします。2番目のスレッドでこれらのオブジェクトを引き出します。パフォーマンスの低下は、LinkedBlockingQueuetake()メソッドが頻繁に呼び出されると発生します。プログラム全体を監視したところ、take()メソッドが全体として最も多くの時間を要求しました。 〜58Mb/sから0.9Mb/sになります...

キューのポップメソッドとテイクメソッドは、このクラスの静的メソッドで呼び出されます

_public class C_myMessageQueue {

    private static final LinkedBlockingQueue<C_myMessageObject> x_queue = new LinkedBlockingQueue<C_myMessageObject>( 50000 );

    /**
     * @param message
     * @throws InterruptedException
     * @throws NullPointerException
     */
    public static void addMyMessage( C_myMessageObject message )
            throws InterruptedException, NullPointerException {
        x_queue.put( message );
    }

    /**
     * @return Die erste message der MesseageQueue
     * @throws InterruptedException
     */
    public static C_myMessageObject getMyMessage() throws InterruptedException {
        return x_queue.take();
    }
}
_

take()メソッドを調整して少なくとも25Mb/sを達成するにはどうすればよいですか、または「キュー」がいっぱいまたは空のときにブロックする他のクラスを使用できますか。

敬具

バート

追伸:英語が下手でごめんなさい、私はドイツ出身です;)

15
lofthouses

プロデューサースレッドは単純にコンシューマーが消費するよりも多くの要素を配置しますであるため、キューは最終的に容量制限に達し、プロデューサーは待機します。

今から私の元の答えを統合すると、基本的に全体像がわかります。

  • 非常に高速なput()sを実行することにより、LinkedBlockingQueue(すべてのキューに1つあります)の固有のスループット制限に達します。この場合、継続的なtake()sでさえ、それ以上の処理はありません。 (ちなみに、これは、この構造では、とにかくJVMとマシンで、put()が読み取りよりも少なくともわずかにコストがかかることを示しています)。
  • コンシューマーがロックする特定のロックがあるため、コンシューマースレッドを増やすことはおそらく役に立ちません(コンシューマーが実際に何らかの処理を行っていて、それがスループットを制限している場合は、コンシューマーを追加すると役立ちます。複数のコンシューマー(またはプロデューサー)の場合は、SynchronousQueueConcurrentLinkedQueue、およびjsr166yの今後のTransferQueueを試すことができます。

いくつかの提案:

  • より粗いオブジェクトを作成して、それぞれをキューに入れるオーバーヘッドが、生成スレッドからオフロードされる実際の作業とバランスが取れるようにします(あなたの場合、ごくわずかな量の作業を表すオブジェクトに対して多くの通信オーバーヘッドを作成するようです)
  • また、消費する作業をオフロードすることで、プロデューサーにコンシューマーを支援させることもできます(実行する作業があるときにアイドル状態で待つことはあまり意味がありません)。

/ジョンW.が私の元の答えが誤解を招くと正しく指摘した後に更新されました

16

通常、パフォーマンスに敏感なコード領域ではLinkedBlockingQueueを使用せず、ArrayBlockingQueueを使用することをお勧めします。 LinkedBlockingQueueよりもはるかに優れたガベージコレクションプロファイルを提供し、キャッシュに適しています。

ArrayBlockingQueueを試して、パフォーマンスを測定してください。

LinkedBlockingQueueの唯一の利点は、無制限にできることですが、これが実際に必要なことはめったにありません。コンシューマーに障害が発生し、キューのバックアップが開始される場合、キューが制限されていると、システムが正常に機能しなくなり、キューが制限されていない場合に発生する可能性のあるOutOfMemoryErrorsのリスクが発生します。

3
Michael Barker

試してみることがいくつかあります。

LinkedBlockingQueueArrayBlockingQueueに置き換えます。ぶら下がっている参照がないため、キューがいっぱいになったときの動作が向上します。具体的には、LinkedBlockingQueueの1.6実装では、キューが実際に空になるまで、要素の完全なGCは発生しません。

プロデューサー側が常にコンシューマー側のパフォーマンスを上回っている場合は、drainまたはdrainToを使用して「バルク」テイク操作を実行することを検討してください。

または、キューにメッセージオブジェクトの配列またはリストを取得させます。プロデューサーはリストまたは配列にメッセージオブジェクトを入力し、各putまたはtakeは、同じロックオーバーヘッドで複数のメッセージを移動します。秘書が「外出中」のメッセージのスタックを渡すのではなく、一度に1つずつ渡すと考えてください。

3
Devon_C_Miller

見つかりました この興味深い投稿 キューサイズとガベージコレクションによるパフォーマンスの問題について。

1
JRL

充填プロセスについて何も知らずに何が起こるかを言うのは難しいです。

addMyMessageの呼び出し頻度が低い場合(おそらくアプリケーションのまったく異なる部分でのパフォーマンスの問題が原因)、takeメソッドは待機する必要があります。

そうすれば、takeが原因のように見えますが、実際にはそれがアプリケーションの重要な部分です。

1
Daniel Rikowski

ブロッキングキューからオブジェクトを出し入れするための生のパフォーマンスオーバーヘッドがボトルネックである場合( 遅い生産者/消費者問題 ではない)、オブジェクトのバッチ処理でパフォーマンスを大幅に向上させることができます。たとえば、代わりにきめの細かいオブジェクトを配置または取得する場合は、オブジェクトの粗粒度のリストを配置または取得します。コードスニペットは次のとおりです。

ArrayBlockingQueue<List<Object>> Q = new ArrayBlockingQueue<List<Object>>();

// producer side
List<Object> l = new ArrayList<Object>();
for (int i=0; i<100; i++) {
    l.add(i); // your initialization here
}
Q.put(l);

// consumer side
List<Object> l2 = Q.take();
// do something 

バッチ処理により、パフォーマンスが1桁向上します。

0
mac7

確かに何も言えません。ただし、(実験と同じように)BlockingQueueの実装を変更してみることができます。

初期容量を50kに設定し、LinkedBlockingQueueを使用します。同じ容量でArrayBlockingQueueを試してください。また、fairパラメーターで遊ぶこともできます。

0
Roman

アプリケーションがJava 6のロック関連の変更、特に「バイアスロック」機能の影響を受ける可能性があります。

-XX:-UseBiasedLockingスイッチを使用して無効にしてみて、違いが生じるかどうかを確認してください。

詳細については、これを参照してください: http://Java.Sun.com/performance/reference/whitepapers/6_performance.html

0
Daniel Rikowski