从ThreadLocal
变量读取的速度比从常规字段读取的速度慢多少?
更具体地说,简单对象创建比访问ThreadLocal
变量快还是慢?
我认为它足够快,因此ThreadLocal<MessageDigest>
与MessageDigest
每次创建实例相比,拥有实例要快得多。但这是否也适用于字节[10]或字节[1000]?
编辑:问题是调用ThreadLocal
get时真正发生了什么?如果那只是一个领域,就像其他领域一样,那么答案将是“它总是最快的”,对吗?
从ThreadLocal
变量读取的速度比从常规字段读取的速度慢多少?
更具体地说,简单对象创建比访问ThreadLocal
变量快还是慢?
我认为它足够快,因此ThreadLocal<MessageDigest>
与MessageDigest
每次创建实例相比,拥有实例要快得多。但这是否也适用于字节[10]或字节[1000]?
编辑:问题是调用ThreadLocal
get时真正发生了什么?如果那只是一个领域,就像其他领域一样,那么答案将是“它总是最快的”,对吗?
Thread
包含一个(不同步的)哈希图,其中的键是当前ThreadLocal
对象
Answers:
运行未发布的基准测试,ThreadLocal.get
我的计算机上每次迭代大约需要35个周期。没什么。在Sun的实现中,Thread
将ThreadLocal
s映射为值的自定义线性探测哈希图。由于只能通过单个线程访问它,因此它可能非常快。
小对象的分配需要类似的周期数,尽管由于缓存耗尽,您可能会在紧密循环中得到较低的数字。
的建造MessageDigest
可能相对昂贵。它具有相当数量的状态,并且通过Provider
SPI机制进行构造。您可以通过克隆或提供来进行优化Provider
。
仅仅因为缓存ThreadLocal
而不是创建缓存可能更快,并不一定意味着系统性能会提高。您将有与GC相关的额外开销,这会降低一切。
除非您的应用程序使用率很高,否则您MessageDigest
可能需要考虑使用常规的线程安全缓存。
new org.bouncycastle.crypto.digests.SHA1Digest()
。我很确定没有缓存能胜过它。
在2009年,一些JVM使用Thread.currentThread()对象中的未同步HashMap实现了ThreadLocal。这使其速度非常快(尽管当然不及使用常规字段访问的速度快),并且确保Thread死亡时对ThreadLocal对象进行了整理。在2016年更新此答案时,似乎大多数(全部?)较新的JVM使用带有线性探测的ThreadLocalMap。我不确定这些服务器的性能如何,但是我无法想象它比早期的实施要差得多。
当然,如今新的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。
这些是数字:
规格: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实现以及具有处理器数量的这些体系结构上,它似乎也可以很好地扩展。
"!"
会在第一种方法中从内存中读取(永远不会进行写操作)-第一种方法实际上等效于子类化Thread
并为其提供一个自定义字段。该基准测试测量了一种极端情况,其中整个计算包括读取局部变量/线程-实际应用程序可能不会受到其访问模式的影响,但在最坏的情况下,它们的行为将如上所述。
在这里,它进行了另一项测试。结果表明,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端字段样本:4842
2-运行线程本地样本
2端线程本地样本:5835
3-运行现场样本
三端场样本:4674
3-运行线程本地样本
3端螺纹局部样本:5287
4-运行现场样本
4端野外采样:4849
4-运行线程本地样本
4端线程本地样本:5309
5运行领域样本
5端字段样本:4781
5-运行线程本地样本
5端螺纹局部样本:5330
6运行现场样本
6端字段样本:5294
6运行线程本地样本
6端线程本地样本:5511
7运行字段样本
7端字段样本:5119
7运行线程本地样本
7端线程本地样本:5793
8-运行现场样本
8端野外采样:4977
8-运行线程本地样本
8端线程本地样本:6374
9运行字段样本
9端字段样本:4841
9运行线程本地样本
9端线程本地样本:5471
场平均:5051
线程本地平均:5664
信封:
openjdk版本“ 1.8.0_131”
英特尔®酷睿™i7-7500U CPU @ 2.70GHz×4
Ubuntu 16.04 LTS
Int.toString)
与测试相比,这是非常昂贵的。B)您在每次迭代中都要进行两次地图操作,这也是完全无关且昂贵的。尝试改为从ThreadLocal增加一个原始int。C)使用System.nanoTime
代替System.currentTimeMillis
,前者用于配置文件,后者用于用户日期时间目的,可以根据需要进行更改。D)您应该完全避免分配,包括“示例”类的顶级分配
在您优化之前,@ Pete是正确的测试。
如果与实际使用它相比,构造一个MessageDigest有任何严重的开销,我将感到非常惊讶。
缺少使用ThreadLocal的小姐可能会导致泄漏和悬而未决的引用,它们没有明确的生命周期,通常我永远不会在没有非常明确的计划何时删除特定资源的情况下使用ThreadLocal。
构建它并对其进行度量。
另外,如果将消息摘要行为封装到一个对象中,则只需要一个threadlocal。如果出于某些目的需要本地MessageDigest和本地byte [1000],请创建一个具有messageDigest和byte []字段的对象,然后将该对象放入ThreadLocal中,而不是将二者分别放置。