Java ThreadLocal 变量的性能

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/609826/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 16:49:40  来源:igfitidea点击:

Performance of ThreadLocal variable

javamultithreadingperformancethread-local

提问by Sarmun

How much is read from ThreadLocalvariable slower than from regular field?

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

More concretely is simple object creation faster or slower than access to ThreadLocalvariable?

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

I assume that it is fast enough so that having ThreadLocal<MessageDigest>instance is much faster then creating instance of MessageDigestevery time. But does that also apply for byte[10] or byte[1000] for example?

我认为它足够快,因此拥有ThreadLocal<MessageDigest>实例比MessageDigest每次创建实例都要快得多。但这也适用于 byte[10] 或 byte[1000] 吗?

Edit: Question is what is really going on when calling ThreadLocal's get? If that is just a field, like any other, then answer would be "it's always fastest", right?

编辑:问题是调用ThreadLocal's get时到底发生了什么?如果这只是一个领域,就像其他领域一样,那么答案将是“它总是最快的”,对吗?

采纳答案by Tom Hawtin - tackline

Running unpublished benchmarks, ThreadLocal.gettakes around 35 cycle per iteration on my machine. Not a great deal. In Sun's implementation a custom linear probing hash map in Threadmaps ThreadLocals to values. Because it is only ever accessed by a single thread, it can be very fast.

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

Allocation of small objects take a similar number of cycles, although because of cache exhaustion you may get somewhat lower figures in a tight loop.

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

Construction of MessageDigestis likely to be relatively expensive. It has a fair amount of state and construction goes through the ProviderSPI mechanism. You may be able to optimise by, for instance, cloning or providing the Provider.

的建设MessageDigest可能是相对昂贵的。它有相当数量的状态和构造通过ProviderSPI 机制。例如,您可以通过克隆或提供Provider.

Just because it may be faster to cache in a ThreadLocalrather than create does not necessarily mean that the system performance will increase. You will have additional overheads related to GC which slows everything down.

仅仅因为在 aThreadLocal而不是 create 中缓存可能更快并不一定意味着系统性能会提高。您将有与 GC 相关的额外开销,这会减慢一切。

Unless your application very heavily uses MessageDigestyou might want to consider using a conventional thread-safe cache instead.

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

回答by Pete Kirkham

Build it and measure it.

建造它并测量它。

Also, you only need one threadlocal if you encapsulate your message digesting behaviour into an object. If you need a local MessageDigest and a local byte[1000] for some purpose, create an object with a messageDigest and a byte[] field and put that object into the ThreadLocal rather than both individually.

此外,如果您将消息消化行为封装到一个对象中,则只需要一个本地线程。如果出于某种目的需要本地 MessageDigest 和本地 byte[1000],请创建一个带有 messageDigest 和 byte[] 字段的对象,并将该对象放入 ThreadLocal 而不是单独放置。

回答by Gareth Davis

@Pete is correct test before you optimise.

@Pete 是优化之前的正确测试。

I would be very surprised if constructing a MessageDigest has any serious overhead when compared to actaully using it.

如果与实际使用它相比,构造 MessageDigest 有任何严重的开销,我会感到非常惊讶。

Miss using ThreadLocal can be a source of leaks and dangling references, that don't have a clear life cycle, generally I don't ever use ThreadLocal without a very clear plan of when a particular resource will be removed.

错过使用 ThreadLocal 可能是泄漏和悬空引用的来源,它们没有明确的生命周期,通常我不会在没有非常明确的何时删除特定资源的计划的情况下使用 ThreadLocal。

回答by Bill Michell

In 2009, some JVMs implemented ThreadLocal using an unsynchronised HashMap in the Thread.currentThread() object. This made it extremely fast (though not nearly as fast as using a regular field access, of course), as well as ensuring that the ThreadLocal object got tidied up when the Thread died. Updating this answer in 2016, it seems most (all?) newer JVMs use a ThreadLocalMap with linear probing. I am uncertain about the performance of those – but I cannot imagine it is significantly worse than the earlier implementation.

2009 年,一些 JVM 使用 Thread.currentThread() 对象中的未同步 HashMap 实现了 ThreadLocal。这使得它非常快(当然,虽然不如使用常规字段访问快),并确保在 Thread 死亡时 ThreadLocal 对象得到整理。在 2016 年更新这个答案,似乎大多数(全部?)较新的 JVM 使用带有线性探测的 ThreadLocalMap。我不确定它们的性能——但我无法想象它会比早期的实现更糟糕。

Of course, new Object() is also very fast these days, and the Garbage Collectors are also very good at reclaiming short-lived objects.

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

Unless you are certain that object creation is going to be expensive, or you need to persist some state on a thread by thread basis, you are better off going for the simpler allocate when needed solution, and only switching over to a ThreadLocal implementation when a profiler tells you that you need to.

除非您确定创建对象会很昂贵,或者您需要在一个线程一个线程的基础上持久化某些状态,否则最好在需要时使用更简单的分配解决方案,并且仅在以下情况下切换到 ThreadLocal 实现探查器告诉您,您需要这样做。

回答by axel22

Good question, I've been asking myself that recently. To give you definite numbers, the benchmarks below (in Scala, compiled to virtually the same bytecodes as the equivalent Java code):

好问题,我最近一直在问自己。为了给你确定的数字,下面的基准测试(在 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)
}

available here, were performed on an AMD 4x 2.8 GHz dual-cores and a quad-core i7 with hyperthreading (2.67 GHz).

此处可用,在 AMD 4x 2.8 GHz 双核和具有超线程 (2.67 GHz) 的四核 i7 上执行。

These are the numbers:

这些是数字:

i7

i7

Specs: Intel i7 2x quad-core @ 2.67 GHz Test: scala.threads.ParallelTests

规格:Intel i7 2x 四核 @ 2.67 GHz 测试:scala.threads.ParallelTests

Test name: loop_heap_read

测试名称:loop_heap_read

Thread num.: 1 Total tests: 200

线程数:1 测试总数:200

Run times: (showing last 5) 9.0069 9.0036 9.0017 9.0084 9.0074 (avg = 9.1034 min = 8.9986 max = 21.0306 )

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

Thread num.: 2 Total tests: 200

线程数:2 测试总数:200

Run times: (showing last 5) 4.5563 4.7128 4.5663 4.5617 4.5724 (avg = 4.6337 min = 4.5509 max = 13.9476 )

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

Thread num.: 4 Total tests: 200

线程数:4 测试总数:200

Run times: (showing last 5) 2.3946 2.3979 2.3934 2.3937 2.3964 (avg = 2.5113 min = 2.3884 max = 13.5496 )

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

Thread num.: 8 Total tests: 200

线程数:8 测试总数:200

Run times: (showing last 5) 2.4479 2.4362 2.4323 2.4472 2.4383 (avg = 2.5562 min = 2.4166 max = 10.3726 )

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

Test name: threadlocal

测试名称:threadlocal

Thread num.: 1 Total tests: 200

线程数:1 测试总数:200

Run times: (showing last 5) 91.1741 90.8978 90.6181 90.6200 90.6113 (avg = 91.0291 min = 90.6000 max = 129.7501 )

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

Thread num.: 2 Total tests: 200

线程数:2 测试总数:200

Run times: (showing last 5) 45.3838 45.3858 45.6676 45.3772 45.3839 (avg = 46.0555 min = 45.3726 max = 90.7108 )

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

Thread num.: 4 Total tests: 200

线程数:4 测试总数:200

Run times: (showing last 5) 22.8118 22.8135 59.1753 22.8229 22.8172 (avg = 23.9752 min = 22.7951 max = 59.1753 )

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

Thread num.: 8 Total tests: 200

线程数:8 测试总数:200

Run times: (showing last 5) 22.2965 22.2415 22.3438 22.3109 22.4460 (avg = 23.2676 min = 22.2346 max = 50.3583 )

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

AMD

AMD

Specs: AMD 8220 4x dual-core @ 2.8 GHz Test: scala.threads.ParallelTests

规格:AMD 8220 4x 双核 @ 2.8 GHz 测试:scala.threads.ParallelTests

Test name: loop_heap_read

测试名称:loop_heap_read

Total work: 20000000 Thread num.: 1 Total tests: 200

总工作量:20000000 线程数:1 总测试数:200

Run times: (showing last 5) 12.625 12.631 12.634 12.632 12.628 (avg = 12.7333 min = 12.619 max = 26.698 )

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

Test name: loop_heap_read Total work: 20000000

测试名称:loop_heap_read 总工作量:20000000

Run times: (showing last 5) 6.412 6.424 6.408 6.397 6.43 (avg = 6.5367 min = 6.393 max = 19.716 )

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

Thread num.: 4 Total tests: 200

线程数:4 测试总数:200

Run times: (showing last 5) 3.385 4.298 9.7 6.535 3.385 (avg = 5.6079 min = 3.354 max = 21.603 )

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

Thread num.: 8 Total tests: 200

线程数:8 测试总数:200

Run times: (showing last 5) 5.389 5.795 10.818 3.823 3.824 (avg = 5.5810 min = 2.405 max = 19.755 )

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

Test name: threadlocal

测试名称:threadlocal

Thread num.: 1 Total tests: 200

线程数:1 测试总数:200

Run times: (showing last 5) 200.217 207.335 200.241 207.342 200.23 (avg = 202.2424 min = 200.184 max = 245.369 )

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

Thread num.: 2 Total tests: 200

线程数:2 测试总数:200

Run times: (showing last 5) 100.208 100.199 100.211 103.781 100.215 (avg = 102.2238 min = 100.192 max = 129.505 )

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

Thread num.: 4 Total tests: 200

线程数:4 测试总数:200

Run times: (showing last 5) 62.101 67.629 62.087 52.021 55.766 (avg = 65.6361 min = 50.282 max = 167.433 )

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

Thread num.: 8 Total tests: 200

线程数:8 测试总数:200

Run times: (showing last 5) 40.672 74.301 34.434 41.549 28.119 (avg = 54.7701 min = 28.119 max = 94.424 )

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

Summary

概括

A thread local is around 10-20x that of the heap read. It also seems to scale well on this JVM implementation and these architectures with the number of processors.

本地线程大约是堆读取的 10-20 倍。它似乎也可以很好地扩展这个 JVM 实现和这些具有处理器数量的架构。

回答by jpereira

Here it goes another test. The results shows that ThreadLocal is a bit slower than a regular field, but in the same order. Aprox 12% slower

这是另一个测试。结果显示 ThreadLocal 比常规字段慢一点,但顺序相同。大约慢 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);
    }
}
}'

Output:

输出:

0-Running field sample

0-跑场样例

0-End field sample:6044

0-结束字段样本:6044

0-Running thread local sample

0-运行线程本地示例

0-End thread local sample:6015

0-End 线程本地样本:6015

1-Running field sample

1-运行现场示例

1-End field sample:5095

1-端场样本:5095

1-Running thread local sample

1-运行线程本地示例

1-End thread local sample:5720

1-End 线程局部样本:5720

2-Running field sample

2-运行现场示例

2-End field sample:4842

2-端场样本:4842

2-Running thread local sample

2-运行线程本地示例

2-End thread local sample:5835

2端线程局部样本:5835

3-Running field sample

3-运行现场示例

3-End field sample:4674

3-端场样本:4674

3-Running thread local sample

3-运行线程本地示例

3-End thread local sample:5287

3端线程局部样本:5287

4-Running field sample

4-运行现场示例

4-End field sample:4849

4-端场样本:4849

4-Running thread local sample

4-运行线程本地示例

4-End thread local sample:5309

4端线程局部样本:5309

5-Running field sample

5-运行现场示例

5-End field sample:4781

5-端场样本:4781

5-Running thread local sample

5-运行线程本地示例

5-End thread local sample:5330

5端线程局部样本:5330

6-Running field sample

6-运行现场示例

6-End field sample:5294

6-端场样本:5294

6-Running thread local sample

6-运行线程本地示例

6-End thread local sample:5511

6端线程局部样本:5511

7-Running field sample

7-运行现场示例

7-End field sample:5119

7-结束字段样本:5119

7-Running thread local sample

7-运行线程本地示例

7-End thread local sample:5793

7-端线程局部样本:5793

8-Running field sample

8-运行现场示例

8-End field sample:4977

8-端场样本:4977

8-Running thread local sample

8-运行线程本地示例

8-End thread local sample:6374

8端线程局部样本:6374

9-Running field sample

9-运行现场示例

9-End field sample:4841

9-结束字段样本:4841

9-Running thread local sample

9-运行线程本地示例

9-End thread local sample:5471

9-端线程局部样本:5471

Field avg:5051

场均:5051

ThreadLocal avg:5664

线程本地平均值:5664

Env:

环境:

openjdk version "1.8.0_131"

openjdk 版本“1.8.0_131”

Intel? Core? i7-7500U CPU @ 2.70GHz × 4

英特尔?核?i7-7500U CPU @ 2.70GHz × 4

Ubuntu 16.04 LTS

Ubuntu 16.04 LTS