5

ある側面で大量のデータ収集コードを同期する必要があることに気づきましたが、パフォーマンスが非常に重要です。パフォーマンスが大幅に低下すると、ツールが破棄されます。intとlongを個別に、さまざまな配列、ArrayLists、Mapsに書き込みます。私の側面で取り上げられる関数呼び出しを行うアプリケーションの複数のスレッドがあります。パフォーマンスに悪影響を与えるので、どのようなことに注意する必要がありますか?どのコードパターンがより効率的ですか?

特に、他の多くのデータ記録メソッドを呼び出すメソッドがあります。

void foo() {
    bar();
    woz();
    ...
}

メソッドは主にアスペクトフィールドの増分を追加します

void bar() {
    f++; // f is a field of the aspect
    for (int i = 0; i < ary.length; i++) {
        // get some values from aspect point cut
        if (some condiction) {
            ary[i] += someValue; // ary a field of the aspect
        }
     }
 }

foo、またはbar、wozなどを個別に同期する必要がありますか、それともbar、wozなどのすべてのコードをfooに移動して、同期する必要がありますか?this特別に作成された同期オブジェクトで、で同期する必要があります。

private final Object syncObject = new Object();

この投稿を参照)、またはメソッド内の個々のデータ要素について:

ArrayList<Integer> a = new ArrayList<Integer>();

void bar() {    
    synchronize(a) {
        // synchronized code
    }
}
4

5 に答える 5

9

並行性は非常に注意が必要です。それを間違えるのは非常に簡単で、正しくするのは非常に難しいです。この時点では、パフォーマンスについてそれほど心配する必要はありません。私の最初のそして最も重要な関心事は、並行コードを安全に動作させることです(デッドロックや競合状態はありません)。

しかし、パフォーマンスの問題については、疑問がある場合はプロファイルを作成してください。さまざまな同期スキームがパフォーマンスにどのように影響するかを正確に言うのは難しいです。私たちがあなたに提案をすることはさらに難しいです。本当に役立つ答えを得るには、コードをもっと見て、アプリケーションが何をするのかをもっと深く理解する必要があります。対照的に、プロファイリングは、あるアプローチが別のアプローチよりも遅いかどうかについての確かな証拠を提供します。減速がどこにあるかを特定するのにも役立ちます。

最近、Java用の優れたプロファイリングツールがたくさんあります。NetbeansとEclipseプロファイラーは優れています。

また、生の同期から完全に離れることをお勧めします。java.util.concurrencyパッケージ内のいくつかのクラスを使用してみてください。並行コードの記述がはるかに簡単になり、エラーが発生しにくくなります。

また、BrianGoetz他によるJavaConcurrencyinPracticeを読むことをお勧めします。それは非常によく書かれていて、多くの分野をカバーしています。

于 2009-05-16T03:20:58.680 に答える
4

経験則では、同期しないでください。thisほとんどの場合、パフォーマンスが低下します。すべてのメソッドが1つのオブジェクトで同期されます。

ロックの使用を検討してください。ロックは非常に優れた抽象化であり、一定期間ロックを試みてからあきらめるなど、多くの優れた機能があります。

if(commandsLock.tryLock(100, TimeUnit.MILLISECONDS)){
     try { 
         //Do something
     }finally{
          commandsLock.unlock();
     }
}else{
     //couldnt acquire lock for 100 ms
}   

の使用についてセカンドオピニオンjava.util.concurrent。同期のレベルを2つ作成します

  • コレクションアクセスを同期する(必要な場合)
  • フィールドアクセスを同期する

コレクションへのアクセス

コレクションがread-only削除されない場合、つまり要素が削除されない場合(ただし、要素は変更される可能性があります)、同期されたコレクションを使用する必要があり(ただし、これは必要ない場合があります...)、反復を同期しないでください。

読み取り専用:

for (int i = 0; i < ary.length; i++) {
    // get some values from aspect point cut
    if (some condiction) {
        ary += someValue; // ary a field of the aspect
    }
 }

aryは。によって取得されたインスタンスCollections.synchronizedListです。

読み書き

synchronized(ary){
    for (int i = 0; i < ary.length; i++) {
        // get some values from aspect point cut
        if (some condiction) {
            ary += someValue; // ary a field of the aspect
        }
     }
 }

または、本質的に安全ないくつかの同時コレクション(CopyOnWriteArrayListなど)を使用します。

主な違いは、最初の読み取り専用のバージョンでは、任意の数のスレッドがこのコレクションを反復処理でき、次に、一度に1つだけが反復処理できることです。どちらの場合も、一度に1つのtheradのみが任意のフィールドをインクリメントする必要があります。

フィールドアクセス

反復の同期とは別に、フィールドの増分を同期します。

お気に入り:

  Integer foo = ary.get(ii); 
  synchronized(foo){
      foo++;
  }

同期を取り除く

  1. 同時コレクションを使用します(java.util.concurrent`Collections.synchronizedXXX'からではなく-から、後者はトラバーサルで同期する必要があります)。
  2. これを使用java.util.atomicして、フィールドをアトミ​​ックに増加させることができます。

あなたが見なければならない何か:

Javaメモリモデル-JAVAでの同期とデータアラインメントがどのように機能するかについて非常によく理解できる講演です。

于 2009-05-16T13:42:49.263 に答える
3

Upadte:以下を書いたので、質問を少し更新したようです。私の無知を許してください-私は「アスペクト」が何であるかわかりません-しかしあなたが投稿したサンプルコードから、アトミック/並行コレクション(例えばAtomicInteger、AtomicIntegerArray)またはアトミックフィールドアップデーターの使用を検討することもできます。ただし、これはコードのかなりのリファクタリングを意味する可能性があります。(デュアルプロシージャハイパースレッディングXeon上のJava 5では、AtomicIntegerArrayのスループットは同期配列よりも大幅に優れています。申し訳ありませんが、まだより多くのプロシージャ/後のJVMバージョンでテストを繰り返すことはできません。それ以来、「同期」は改善されました。)

特定のプログラムに関するより具体的な情報や指標がなければ、できる最善のことは、優れたプログラム設計に従うことです。JVMの同期ロックのパフォーマンスと最適化は、過去数年間で最も多くの研究と注目を集めてきた領域の1つ(そうでない場合はその領域)になっていることは注目に値します。したがって、最新バージョンのJVMでは、それほど悪くはありません。

したがって、一般的には、 「気が狂う」ことなく最小限の同期を行うと思います。「最小限に」とは、ロックをできるだけ短時間保持し、その特定のロックを使用する必要のある部分だけがその特定のロックを使用するようにすることを意味します。ただし、変更が簡単で、プログラムがまだ正しいことを証明するのが簡単な場合に限ります。たとえば、これを行う代わりに:

synchronized (a) {
  doSomethingWith(a);
  longMethodNothingToDoWithA();
  doSomethingWith(a);
}

プログラムがまだ正しい場合にのみ、これを行うことを検討してください。

synchronized (a) {
  doSomethingWith(a);
}
longMethodNothingToDoWithA();
synchronized (a) {
  doSomethingWith(a);
}

ただし、不必要に保持されたロックを使用した奇妙な単純なフィールド更新は、おそらく大きな違いをもたらさず、実際にパフォーマンスを向上させる可能性があることを忘れないでください。場合によっては、ロックをもう少し長く保持し、ロックの「ハウスキーピング」を少なくすることが有益な場合があります。ただし、JVMはこれらの決定の一部を行うことができるため、あまりにも偏執的である必要はありません。一般的に賢明なことを行うだけで、問題はありません。

一般に、「独立したプロセス」を形成するメソッド/アクセスのセットごとに個別のロックを試してみてください。それ以外に、別のロックオブジェクトを持つことは、それが使用されるクラス内にロックをカプセル化するための良い方法です(つまり、予期しない方法で外部の呼び出し元によってロックが使用されるのを防ぐ)が、おそらくパフォーマンスの違いはありません2つのオブジェクトがまったく同じように使用される場合、それ自体が1つのオブジェクトを別のオブジェクトにロックとして使用すること(たとえば、インスタンス自体と、そのクラス内でロックとして宣言されたプライベートオブジェクトを使用すること)。

于 2009-05-16T04:13:31.270 に答える
3

組み込みの言語構造とライブラリの間にはパフォーマンスの違いがあるはずですが、経験から、パフォーマンスに関しては推測しないようになっています。

于 2011-10-26T08:16:20.720 に答える
1

アスペクトをアプリケーションにコンパイルすると、基本的にパフォーマンスへの影響はありません。実行時に実行すると(ロードタイプのウィービング)、パフォーマンスへの影響が見られます。

各側面をインスタンスごとに設定すると、同期の必要性が減る可能性があります。

問題を減らすために、できるだけ短時間で、できるだけ同期を少なくする必要があります。

可能であれば、デッドロックの問題を減らすために、スレッド間でできるだけ少ない状態を共有し、できるだけ多くのローカルを維持することをお勧めします。

より多くの情報は、ところでより良い答えにつながるでしょう。:)

于 2009-05-16T04:28:42.887 に答える