ThreadLocal
変数からの読み取りは、通常のフィールドからの読み取りよりも遅くなりますか?
具体的には、単純なオブジェクトの作成は、ThreadLocal
変数へのアクセスよりも速いですか、遅いですか?
私はそれが十分に速いと仮定して、ThreadLocal<MessageDigest>
インスタンスは、毎回MessageDigest
のインスタンスを作成するよりもはるかに高速です。しかし、これはたとえばbyte [10]やbyte [1000]にも当てはまりますか?
編集:質問は、ThreadLocal
's getを呼び出すときに実際に何が起こっているのですか?それが他のフィールドと同様に単なるフィールドである場合、答えは「常に最速です」でしょうか?
未公開のベンチマークの実行、ThreadLocal.get
は、マシン上で反復ごとに約35サイクルかかります。大したことではない。 Sunの実装では、Thread
のカスタム線形プローブハッシュマップがThreadLocal
sを値にマップします。単一のスレッドによってのみアクセスされるため、非常に高速です。
小さなオブジェクトの割り当てには、同様のサイクル数がかかりますが、キャッシュが使い果たされるため、タイトなループでは多少低い数値になる場合があります。
MessageDigest
の構築は、比較的高価になる可能性があります。かなりの量の状態があり、構築はProvider
SPIメカニズムを通過します。たとえば、Provider
を複製または提供することにより、最適化できる場合があります。
作成するよりもThreadLocal
でキャッシュする方が速い場合があるからといって、必ずしもシステムのパフォーマンスが向上するわけではありません。 GCに関連する追加のオーバーヘッドが発生し、すべてが遅くなります。
アプリケーションでMessageDigest
を非常に頻繁に使用しない限り、代わりに従来のスレッドセーフキャッシュを使用することを検討してください。
2009年に、一部のJVMはThread.currentThread()オブジェクトの非同期HashMapを使用してThreadLocalを実装しました。これにより、非常に高速になりました(もちろん、通常のフィールドアクセスを使用した場合ほど高速ではありません)。 2016年にこの回答を更新すると、ほとんどの(すべての?)新しいJVMが線形探査でThreadLocalMapを使用しているようです。それらのパフォーマンスについては不確かですが、以前の実装よりも著しく悪いとは想像できません。
もちろん、最近では新しいObject()も非常に高速であり、ガベージコレクターは短命のオブジェクトの再生にも非常に優れています。
オブジェクトの作成に費用がかかることを確信していない場合、またはスレッドごとに状態を永続化する必要がある場合を除き、必要な場合は単純な割り当てを行い、プロファイラーは、あなたがする必要があることを伝えます。
良い質問です、私は最近そのことを自問しています。明確な数値を得るために、以下のベンチマーク(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で実行されました。
これらは数字です:
仕様: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 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終了フィールドのサンプル: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
@Peteは最適化する前の正しいテストです。
MessageDigestを構築することと、実際に使用することとを比較すると、深刻なオーバーヘッドがある場合、私は非常に驚くでしょう。
ThreadLocalの使用ミスは、リークやぶら下がり参照の原因になる可能性があり、明確なライフサイクルがありません。一般に、特定のリソースがいつ削除されるかについて非常に明確な計画がない限り、ThreadLocalを使用しません。
ビルドして測定します。
また、メッセージダイジェストの動作をオブジェクトにカプセル化する場合、必要なスレッドローカルは1つだけです。何らかの目的でローカルのMessageDigestとローカルのbyte [1000]が必要な場合は、messageDigestとbyte []フィールドを持つオブジェクトを作成し、そのオブジェクトを両方ではなくThreadLocalに入れます。