質問

どこから読まれるか ThreadLocal 変数は通常のフィールドからの変数よりも遅いですか?

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

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

編集:問題は、電話をかけるときに実際に何が起こっているのかということです ThreadLocalもらえるの?それが他のフィールドと同様に単なるフィールドであれば、答えは「常に最速です」になりますよね?

役に立ちましたか?

解決

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

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

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

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

アプリケーションで<=>を非常に頻繁に使用しない限り、代わりに従来のスレッドセーフキャッシュの使用を検討することをお勧めします。

他のヒント

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

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

オブジェクトの作成にコストがかかることを確信していない場合、またはスレッドごとに状態を永続化する必要がある場合を除き、必要な場合はより単純な割り当てを行い、ThreadLocalに切り替えることをお勧めしますプロファイラーから必要があることが通知された場合の実装。

良い質問です、私は最近そのことを自問してきました。明確な数値を得るために、以下のベンチマーク(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)
}

利用可能なこちら 、AMD 4x 2.8 GHzデュアルコアおよびハイパースレッディング(2.67 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実装とこれらのアーキテクチャで、プロセッサの数に応じて適切に拡張できるようです。

別のテストに進みます。結果は、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-Endフィールドのサンプル:6044

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

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

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

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

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

1-end thread local sample:5720

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

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

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

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

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

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

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

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

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

4-Endフィールドサンプル:4849

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

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

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

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

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

5-end thread local sample:5330

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

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

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

6-end thread local sample:5511

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

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

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

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

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

8-Endフィールドサンプル:4977

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

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

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

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

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

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

フィールド平均:5051

ThreadLocal avg:5664

環境:

openjdkバージョン<!> quot; 1.8.0_131 <!> quot;

Intel <!>#174;コア<!>#8482; i7-7500U CPU @ 2.70GHz <!>#215; 4

Ubuntu 16.04 LTS

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

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

ThreadLocal を使用しないと、明確なライフサイクルがないリークや未解決参照の原因となる可能性があります。通常、私は特定のリソースがいつ削除されるかについて明確な計画がなければ ThreadLocal を使用しません。

ビルドして測定します。

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

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top