ThreadLocalvariables的性能

从常规字段中读取ThreadLocalvariables的速度要慢多less?

更具体地说,简单的对象创build比访问ThreadLocalvariables更快或更慢?

我假设它足够快,以便使ThreadLocal<MessageDigest>实例每次都创buildThreadLocal<MessageDigest>实例的速度要快得多。 但是这也适用于字节[10]或字节[1000]例如?

编辑:问题是什么时,调用ThreadLocal的get? 如果这只是一个领域,那么答案就是“总是最快”,对吧?

运行未发布的基准testing, ThreadLocal.get在我的机器上每次循环需要大约35个周期。 不是很多。 在Sun的实现中, Thread的自定义线性探测哈希映射将ThreadLocal映射为值。 因为它只能被一个线程访问,所以速度可能非常快。

小对象的分配需要相似的循环次数,但由于caching耗尽,在紧缩循环中可能会稍微降低数字。

MessageDigest构build可能相对昂贵。 它有相当数量的状态,并通过Provider SPI机制进行构build。 例如,您可以通过克隆或提供提供Provider来进行优化。

只是因为在ThreadLocalcaching而不是创build并不一定意味着系统性能会提高。 您将有额外的GC相关的开销,减慢了一切。

除非您的应用程序使用MessageDigest否则您可能需要考虑使用传统的线程安全caching。

在2009年,一些JVM使用Thread.currentThread()对象中的非同步HashMap实现了ThreadLocal。 这使得它非常快速(尽pipe不像使用常规字段访问一样快),以及确保在线程死亡时ThreadLocal对象被整理。 在2016年更新这个答案,似乎大多数(所有?)更新的JVM使用线性探测的ThreadLocalMap。 我对这些performance不确定 – 但我无法想象它比以前的实施情况差得多。

当然,新的Object()也是非常快的,垃圾收集器也非常善于回收短暂的对象。

除非你确定对象的创build将会很昂贵,或者你需要在线程的基础上保存一些状态,否则最好是在需要的时候进行更简单的分配,而只需要切换到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) } 

在这里可以看到 ,它们是在带有超线程(2.67 GHz)的AMD 4x 2.8 GHz双核和四核i7上进行的。

这些是数字:

I7

规格:英特尔i7 2x四核@ 2.67 GHztesting:scala.threads.ParallelTests

testing名称:loop_heap_read

主题数:1总testing:200

运行时间:(显示最后5)9.0069 9.0036 9.0017 9.0084 9.0074(平均= 9.1034分钟= 8.9986最大= 21.0306)

主题:2总testing:200

运行时间:(显示最后5)4.5563 4.7128 4.5663 4.5617 4.5724(平均= 4.6337分钟= 4.5509最大= 13.9476)

主题数:4总testing:200

运行时间:(显示最后5)2.3946 2.3979 2.3934 2.3937 2.3964(平均= 2.5113分钟= 2.3884最大= 13.5496)

主题数:8总testing:200

运行时间:(显示最后5)2.4479 2.4362 2.4323 2.4472 2.4383(平均= 2.5562分钟= 2.4166最大= 10.3726)

testing名称:threadlocal

主题数:1总testing:200

运行时间:(显示最后5)91.1741 90.8978 90.6181 90.6200 90.6113(平均= 91.0291分钟= 90.6000最大= 129.7501)

主题:2总testing:200

运行时间:(显示最后5)45.3838 45.3858 45.6676 45.3772 45.3839(平均= 46.0555分钟= 45.3726最大= 90.7108)

主题数:4总testing:200

运行时间:(显示最后5)22.8118 22.8135 59.1753 22.8229 22.8172(平均= 23.9752分钟= 22.7951最大= 59.1753)

主题数:8总testing:200

运行时间:(显示最后5)22.2965 22.2415 22.3438 22.3109 22.4460(平均= 23.2676分钟= 22.2346最大= 50.3583)

AMD

规格:AMD 8220 4x双核@ 2.8 GHztesting:scala.threads.ParallelTests

testing名称:loop_heap_read

总工作:20000000主题:1总testing:200

运行时间:(显示最后5)12.625 12.631 12.634 12.632 12.628(平均= 12.7333分钟= 12.619最大= 26.698)

testing名称:loop_heap_read总工作量:20000000

运行时间:(显示最后5)6.412 6.424 6.408 6.397 6.43(平均= 6.5367分钟= 6.393最大= 19.716)

主题数:4总testing:200

运行时间:(显示最后5)3.385 4.298 9.7 6.535 3.385(平均= 5.6079分钟= 3.354最大= 21.603)

主题数:8总testing:200

运行时间:(显示最后5个)5.389 5.795 10.818 3.823 3.824(平均= 5.5810分钟= 2.405最大= 19.755)

testing名称:threadlocal

主题数:1总testing:200

运行时间:(显示最后5)200.217 207.335 200.241 207.342 200.23(平均= 202.2424分钟= 200.184最大= 245.369)

主题:2总testing:200

运行时间:(显示最后5)100.208 100.199 100.211 103.781 100.215(平均= 102.2238分钟= 100.192最大= 129.505)

主题数:4总testing:200

运行时间:(显示最后5)62.101 67.629 62.087 52.021 55.766(平均= 65.6361分钟= 50.282最大= 167.433)

主题数:8总testing:200

运行时间:(显示最后5)40.672 74.301 34.434 41.549 28.119(平均= 54.7701分钟= 28.119最大= 94.424)

概要

当地的线程大约是堆读取的10-20倍。 在这个JVM实现和这些处理器数量的架构上,它似乎也能很好地扩展。

在你优化之前,@Pete是正确的testing。

如果构build一个MessageDigest与执行它相比有任何严重的开销,我会感到非常惊讶。

使用ThreadLocal的小姐可能是一个泄漏和悬挂引用的来源,没有一个明确的生命周期,通常我从来没有使用ThreadLocal没有一个非常明确的计划,当一个特定的资源将被删除。

这里又是一个testing。 结果显示,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-End字段样本:5095

1-正在运行线程本地样本

1-end线程局部样本:5720

2-运行现场样品

2-End字段样本:4842

2-正在运行线程本地样本

2端螺纹局部样品:5835

3-运行现场样品

三端场样本:4674

3 – 运行线程本地样例

三端螺纹局部样品:5287

4-运行现场样品

4端场样本:4849

4 – 正在运行线程本地样本

4端螺纹局部样本:5309

5-运行现场样品

5-End字段样本:4781

5-正在运行的线程本地样例

5端螺纹局部样品:5330

6-运行现场样品

6端场样本:5294

6-运行线程本地样本

6端螺纹局部样品:5511

7-运行现场示例

7-End字段样本:5119

7-正在运行的线程本地样例

7-end线程局部样本:5793

8-运行现场样品

8-End字段样本:4977

8-执行线程本地样本

8端线程局部样本:6374

9 – 正在运行的现场示例

9-End字段样本:4841

9 – 正在运行的线程本地样例

9-end线程局部样本:5471

字段平均值:5051

ThreadLocal avg:5664

ENV:

openjdk版本“1.8.0_131”

英特尔®酷睿™i7-7500U CPU @ 2.70GHz×4

Ubuntu 16.04 LTS

build立并测量它。

另外,如果将消息摘要行为封装到对象中,则只需要一个threadlocal。 如果您为了某种目的需要本地MessageDigest和本地字节[1000],请创build一个带有messageDigest和byte []字段的对象,并将该对象放入ThreadLocal中,而不是单独使用。