当前位置:  开发笔记 > 编程语言 > 正文

ThreadLocal变量的性能

如何解决《ThreadLocal变量的性能》经验,为你挑选了4个好方法。

ThreadLocal变量读取多少比常规字段慢?

更具体地说,简单的对象创建比访问ThreadLocal变量更快还是更慢?

我认为它足够快,因此ThreadLocal实例比MessageDigest每次创建实例要快得多.但这也适用于字节[10]或字节[1000]吗?

编辑:问题是在调用ThreadLocalget 时真正发生的事情?如果那只是一个领域,就像任何其他领域一样,那么答案就是"它总是最快",对吧?



1> Bill Michell..:

在2009年,一些JVM使用Thread.currentThread()对象中的非同步HashMap实现了ThreadLocal.这使得它非常快(当然,并不像使用常规字段访问那么快),以及确保在线程死亡时ThreadLocal对象得到了整理.在2016年更新这个答案,似乎大多数(所有?)较新的JVM使用线性探测的ThreadLocalMap.我不确定那些的表现 - 但我无法想象它比早期的实施更糟糕.

当然,新的Object()现在也非常快,垃圾收集器也非常擅长回收短期对象.

除非您确定对象创建会很昂贵,或者您需要逐个线程地保持某个状态,所以最好在需要的解决方案时进行更简单的分配,并且只在切换到ThreadLocal实现时探查器告诉你,你需要.


+1是实际解决问题的唯一答案.

2> Tom Hawtin -..:

运行未发布的基准测试,ThreadLocal.get在我的机器上每次迭代需要大约35个周期.没什么大不了的.在Sun的实现自定义的线性探测哈希表的Thread映射ThreadLocals到值.因为它只能由单个线程访问,所以它可以非常快.

小对象的分配需要相似数量的周期,尽管由于高速缓存耗尽,您可能会在紧密循环中获得稍低的数字.

建设MessageDigest可能相对昂贵.它有相当数量的州和建设通过ProviderSPI机制.您可以通过克隆或提供来优化Provider.

仅仅因为在一个ThreadLocal而不是创建中缓存可能更快并不一定意味着系统性能会提高.您将获得与GC相关的额外开销,这会减慢所有速度.

除非您的应用程序使用频繁,否则您MessageDigest可能需要考虑使用传统的线程安全缓存.


恕我直言,最快的方法是忽略SPI并使用类似`new org.bouncycastle.crypto.digests.SHA1Digest()`的东西.我很确定没有缓存可以击败它.

3> axel22..:

好问题,我最近一直在问自己.为了给出明确的数字,下面的基准测试(在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(avg = 4.6337 min = 4.5509 max = 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(avg = 23.2676 min = 22.2346 max = 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 min = 6.393 max = 19.716)

螺纹数:4总测试:200

运行时间:(显示最后5个)3.385 4.298 9.7 6.535 3.385(avg = 5.6079 min = 3.354 max = 21.603)

线程数:8总测试:200

运行时间:(显示最后5)5.389 5.795 10.818 3.823 3.824(平均值= 5.5810 min = 2.405 max = 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实现和这些具有处理器数量的架构上也能很好地扩展.


+1是唯一一个给出定量结果的人.我有点怀疑,因为这些测试都在Scala中,但就像你说的那样,Java字节码应该类似......

4> 小智..:

这是另一个测试.结果显示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 map = new HashMap();

    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> myThreadLocal = new ThreadLocal>() {
        @Override protected Map initialValue() {
            return new HashMap();
        }
    };

    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端螺纹局部样品:5720

2运行场样本

2-End野外样本:4842

2-运行线程本地样本

2端线程本地样本:5835

3运行场样本

3-End野外样本:4674

3-运行线程本地样本

3端线程本地样本:5287

4运行场样本

4-End野外样本:4849

4-运行线程本地样本

4端线程本地样本:5309

5运行场样本

5-End野外样本:4781

5-运行线程本地样本

5端线程本地样本:5330

6运行场样本

6-End野外样本:5294

6-运行线程本地样本

6端线程本地样本:5511

7运行场样本

7-End野外样本:5119

7-运行线程本地样本

7-End thread本地样本:5793

8-运行场样本

8-End野外样本:4977

8-运行线程本地样本

8端线程本地样本:6374

9-运行场样本

9-End野外样本:4841

9-运行线程本地样本

9端线程本地样本:5471

Field avg:5051

ThreadLocal平均值:5664

ENV:

openjdk版本"1.8.0_131"

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

Ubuntu 16.04 LTS

推荐阅读
ERIK又
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有