ThreadLocalvariables的性能
从常规字段中读取ThreadLocal
variables的速度要慢多less?
更具体地说,简单的对象创build比访问ThreadLocal
variables更快或更慢?
我假设它足够快,以便使ThreadLocal<MessageDigest>
实例每次都创buildThreadLocal<MessageDigest>
实例的速度要快得多。 但是这也适用于字节[10]或字节[1000]例如?
编辑:问题是什么时,调用ThreadLocal
的get? 如果这只是一个领域,那么答案就是“总是最快”,对吧?
运行未发布的基准testing, ThreadLocal.get
在我的机器上每次循环需要大约35个周期。 不是很多。 在Sun的实现中, Thread
的自定义线性探测哈希映射将ThreadLocal
映射为值。 因为它只能被一个线程访问,所以速度可能非常快。
小对象的分配需要相似的循环次数,但由于caching耗尽,在紧缩循环中可能会稍微降低数字。
MessageDigest
构build可能相对昂贵。 它有相当数量的状态,并通过Provider
SPI机制进行构build。 例如,您可以通过克隆或提供提供Provider
来进行优化。
只是因为在ThreadLocal
caching而不是创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中,而不是单独使用。