web-dev-qa-db-ja.com

ThreadLocal変数のパフォーマンス

ThreadLocal変数からの読み取りは、通常のフィールドからの読み取りよりも遅くなりますか?

具体的には、単純なオブジェクトの作成は、ThreadLocal変数へのアクセスよりも速いですか、遅いですか?

私はそれが十分に速いと仮定して、ThreadLocal<MessageDigest>インスタンスは、毎回MessageDigestのインスタンスを作成するよりもはるかに高速です。しかし、これはたとえばbyte [10]やbyte [1000]にも当てはまりますか?

編集:質問は、ThreadLocal 's getを呼び出すときに実際に何が起こっているのですか?それが他のフィールドと同様に単なるフィールドである場合、答えは「常に最速です」でしょうか?

82
Sarmun

未公開のベンチマークの実行、ThreadLocal.getは、マシン上で反復ごとに約35サイクルかかります。大したことではない。 Sunの実装では、Threadのカスタム線形プローブハッシュマップがThreadLocalsを値にマップします。単一のスレッドによってのみアクセスされるため、非常に高速です。

小さなオブジェクトの割り当てには、同様のサイクル数がかかりますが、キャッシュが使い果たされるため、タイトなループでは多少低い数値になる場合があります。

MessageDigestの構築は、比較的高価になる可能性があります。かなりの量の状態があり、構築はProvider SPIメカニズムを通過します。たとえば、Providerを複製または提供することにより、最適化できる場合があります。

作成するよりもThreadLocalでキャッシュする方が速い場合があるからといって、必ずしもシステムのパフォーマンスが向上するわけではありません。 GCに関連する追加のオーバーヘッドが発生し、すべてが遅くなります。

アプリケーションでMessageDigestを非常に頻繁に使用しない限り、代わりに従来のスレッドセーフキャッシュを使用することを検討してください。

39

2009年に、一部のJVMはThread.currentThread()オブジェクトの非同期HashMapを使用してThreadLocalを実装しました。これにより、非常に高速になりました(もちろん、通常のフィールドアクセスを使用した場合ほど高速ではありません)。 2016年にこの回答を更新すると、ほとんどの(すべての?)新しいJVMが線形探査でThreadLocalMapを使用しているようです。それらのパフォーマンスについては不確かですが、以前の実装よりも著しく悪いとは想像できません。

もちろん、最近では新しいObject()も非常に高速であり、ガベージコレクターは短命のオブジェクトの再生にも非常に優れています。

オブジェクトの作成に費用がかかることを確信していない場合、またはスレッドごとに状態を永続化する必要がある場合を除き、必要な場合は単純な割り当てを行い、プロファイラーは、あなたがする必要があることを伝えます。

55
Bill Michell

良い質問です、私は最近そのことを自問しています。明確な数値を得るために、以下のベンチマーク(Scalaでは、同等のJavaコード)と実質的に同じバイトコードにコンパイルされています):

var cnt: String = ""
val tlocal = new Java.lang.ThreadLocal[String] {
  override def initialValue = ""
}

def loop_heap_write = {                                                                                                                           
  var i = 0                                                                                                                                       
  val until = totalwork / threadnum                                                                                                               
  while (i < until) {                                                                                                                             
    if (cnt ne "") cnt = "!"                                                                                                                      
    i += 1                                                                                                                                        
  }                                                                                                                                               
  cnt                                                                                                                                          
} 

def threadlocal = {
  var i = 0
  val until = totalwork / threadnum
  while (i < until) {
    if (tlocal.get eq null) i = until + i + 1
    i += 1
  }
  if (i > until) println("thread local value was null " + i)
}

利用可能 こちら 、ハイパースレッディング(2.67 GHz)を備えたAMD 4x 2.8 GHzデュアルコアおよびクアッドコアi7で実行されました。

これらは数字です:

i7

仕様:Intel i7 2xクアッドコア@ 2.67 GHzテスト:scala.threads.ParallelTests

テスト名:loop_heap_read

スレッド数:1合計テスト:200

実行時間:(最後の5を表示)9.0069 9.0036 9.0017 9.0084 9.0074(平均= 9.1034分= 8.9986最大= 21.0306)

スレッド数:2合計テスト:200

実行時間:(最後の5つを表示)4.5563 4.7128 4.5663 4.5617 4.5724(平均= 4.6337分= 4.5509最大= 13.9476)

スレッド数:4合計テスト:200

実行時間:(最後の5つを表示)2.3946 2.3979 2.3934 2.3937 2.3964(平均= 2.5113最小= 2.3884最大= 13.5496)

スレッド番号:8合計テスト:200

実行時間:(最後の5つを表示)2.4479 2.4362 2.4323 2.4472 2.4383(平均= 2.5562分= 2.4166最大= 10.3726)

テスト名:threadlocal

スレッド数:1合計テスト:200

実行時間:(最後の5つを表示)91.1741 90.8978 90.6181 90.6200 90.6113(平均= 91.0291分= 90.6000最大= 129.7501)

スレッド数:2合計テスト:200

実行時間:(最後の5を表示)45.3838 45.3858 45.6676 45.3772 45.3839(平均= 46.0555最小= 45.3726最大= 90.7108)

スレッド数:4合計テスト:200

実行時間:(最後の5つを表示)22.8118 22.8135 59.1753 22.8229 22.8172(平均= 23.9752分= 22.7951最大= 59.1753)

スレッド番号:8合計テスト:200

実行時間:(最後の5つを表示)22.2965 22.2415 22.3438 22.3109 22.4460(平均= 23.2676分= 22.2346最大= 50.3583)

AMD

仕様:AMD 8220 4xデュアルコア@ 2.8 GHzテスト:scala.threads.ParallelTests

テスト名:loop_heap_read

総作業数:20000000スレッド数:1総テスト数:200

実行時間:(最後の5つを表示)12.625 12.631 12.634 12.632 12.628(平均= 12.7333分= 12.619最大= 26.698)

テスト名:loop_heap_read総作業量:20000000

実行時間:(最後の5つを表示)6.412 6.424 6.408 6.397 6.43(平均= 6.5367分= 6.393最大= 19.716)

スレッド数:4合計テスト:200

実行時間:(最後の5つを表示)3.385 4.298 9.7 6.535 3.385(平均= 5.6079分= 3.354最大= 21.603)

スレッド番号:8合計テスト:200

実行時間:(最後の5つを表示)5.389 5.795 10.818 3.823 3.824(平均= 5.5810分= 2.405最大= 19.755)

テスト名:threadlocal

スレッド数:1合計テスト:200

実行時間:(最後の5つを表示)200.217 207.335 200.241 207.342 200.23(平均= 202.2424最小= 200.184最大= 245.369)

スレッド数:2合計テスト:200

実行時間:(最後の5つを表示)100.208 100.199 100.211 103.781 100.215(平均= 102.2238分= 100.192最大= 129.505)

スレッド数:4合計テスト:200

実行時間:(最後の5つを表示)62.101 67.629 62.087 52.021 55.766(平均= 65.6361分= 50.282最大= 167.433)

スレッド番号:8合計テスト:200

実行時間:(最後の5つを表示)40.672 74.301 34.434 41.549 28.119(平均= 54.7701分= 28.119最大= 94.424)

概要

スレッドローカルは、読み込まれたヒープの約10〜20倍です。また、このJVM実装およびこれらのアーキテクチャで、プロセッサの数に応じて適切に拡張されるようです。

34
axel22

ここで別のテストに進みます。結果は、ThreadLocalが通常のフィールドよりも少し遅いが、同じ順序であることを示しています。 Aprox 12%遅くなります

public class Test {
private static final int N = 100000000;
private static int fieldExecTime = 0;
private static int threadLocalExecTime = 0;

public static void main(String[] args) throws InterruptedException {
    int execs = 10;
    for (int i = 0; i < execs; i++) {
        new FieldExample().run(i);
        new ThreadLocaldExample().run(i);
    }
    System.out.println("Field avg:"+(fieldExecTime / execs));
    System.out.println("ThreadLocal avg:"+(threadLocalExecTime / execs));
}

private static class FieldExample {
    private Map<String,String> map = new HashMap<String, String>();

    public void run(int z) {
        System.out.println(z+"-Running  field sample");
        long start = System.currentTimeMillis();
        for (int i = 0; i < N; i++){
            String s = Integer.toString(i);
            map.put(s,"a");
            map.remove(s);
        }
        long end = System.currentTimeMillis();
        long t = (end - start);
        fieldExecTime += t;
        System.out.println(z+"-End field sample:"+t);
    }
}

private static class ThreadLocaldExample{
    private ThreadLocal<Map<String,String>> myThreadLocal = new ThreadLocal<Map<String,String>>() {
        @Override protected Map<String, String> initialValue() {
            return new HashMap<String, String>();
        }
    };

    public void run(int z) {
        System.out.println(z+"-Running thread local sample");
        long start = System.currentTimeMillis();
        for (int i = 0; i < N; i++){
            String s = Integer.toString(i);
            myThreadLocal.get().put(s, "a");
            myThreadLocal.get().remove(s);
        }
        long end = System.currentTimeMillis();
        long t = (end - start);
        threadLocalExecTime += t;
        System.out.println(z+"-End thread local sample:"+t);
    }
}
}'

出力:

0-ランニングフィールドのサンプル

0終了フィールドのサンプル:6044

0-実行中のスレッドローカルサンプル

0エンドスレッドローカルサンプル:6015

1ランニングフィールドサンプル

1端フィールドのサンプル:5095

1実行スレッドローカルサンプル

1エンドスレッドローカルサンプル:5720

2ランニングフィールドサンプル

2端フィールドのサンプル:4842

2実行スレッドローカルサンプル

2エンドスレッドローカルサンプル:5835

3-ランニングフィールドサンプル

3端フィールドのサンプル:4674

3-Runningスレッドローカルサンプル

3エンドスレッドローカルサンプル:5287

4-ランニングフィールドサンプル

4端フィールドのサンプル:4849

4-Runningスレッドローカルサンプル

4エンドスレッドローカルサンプル:5309

5-ランニングフィールドサンプル

5端フィールドのサンプル:4781

5実行スレッドローカルサンプル

5エンドスレッドローカルサンプル:5330

6ランニングフィールドサンプル

6端フィールドのサンプル:5294

6実行スレッドローカルサンプル

6エンドスレッドローカルサンプル:5511

7ランニングフィールドサンプル

7端フィールドのサンプル:5119

7実行スレッドローカルサンプル

7エンドスレッドローカルサンプル:5793

8ランニングフィールドサンプル

8端フィールドのサンプル:4977

8-Runningスレッドローカルサンプル

8エンドスレッドローカルサンプル:6374

9ランニングフィールドサンプル

9端フィールドのサンプル:4841

9-実行中のスレッドローカルサンプル

9エンドスレッドローカルサンプル:5471

フィールド平均:5051

ThreadLocal平均:5664

環境:

openjdkバージョン "1.8.0_131"

Intel®Core™i7-7500U CPU @ 2.70GHz×4

Ubuntu 16.04 LTS

5
jpereira

@Peteは最適化する前の正しいテストです。

MessageDigestを構築することと、実際に使用することとを比較すると、深刻なオーバーヘッドがある場合、私は非常に驚くでしょう。

ThreadLocalの使用ミスは、リークやぶら下がり参照の原因になる可能性があり、明確なライフサイクルがありません。一般に、特定のリソースがいつ削除されるかについて非常に明確な計画がない限り、ThreadLocalを使用しません。

3
Gareth Davis

ビルドして測定します。

また、メッセージダイジェストの動作をオブジェクトにカプセル化する場合、必要なスレッドローカルは1つだけです。何らかの目的でローカルのMessageDigestとローカルのbyte [1000]が必要な場合は、messageDigestとbyte []フィールドを持つオブジェクトを作成し、そのオブジェクトを両方ではなくThreadLocalに入れます。

0
Pete Kirkham