Представление ThreadLocal переменной


сколько читается из ThreadLocal переменная медленнее, чем из обычного поля?

более конкретно-это просто создание объекта быстрее или медленнее, чем доступ к ThreadLocal переменной?

Я предполагаю, что это достаточно быстро, так что ThreadLocal<MessageDigest> экземпляр намного быстрее, чем создание экземпляра MessageDigest каждый раз. Но это также относится к байту[10] или байту[1000], например?

Edit: вопрос в том, что на самом деле происходит при вызове ThreadLocal'ы сделать? Если что это просто поле, как и любое другое, тогда ответ будет "это всегда быстрее", верно?

6 80

6 ответов:

запуск неопубликованных ориентиры, ThreadLocal.get занимает около 35 циклов за итерацию на моей машине. Не очень много. В реализации Sun пользовательская линейная зондирующая хэш-карта в Thread карты ThreadLocals к значениям. Поскольку он доступен только одному потоку, он может быть очень быстрым.

выделение небольших объектов принимать такое же количество циклов, хотя из-за исчерпания кэша, вы можете получить несколько более низкие цифры в непрерывном цикле.

строительство MessageDigest вероятно, это будет относительно дорого. Он имеет изрядное количество государства и строительство идет через Provider механизм SPI. Вы можете быть в состоянии оптимизировать, например, клонирование или предоставления Provider.

просто потому, что это может быть быстрее, чтобы кэш в ThreadLocal, а не создавать не обязательно означает, что производительность системы будет увеличиваться. У вас будут дополнительные накладные расходы, связанные с GC, который замедляет все.

если ваше приложение не очень сильно использует MessageDigest вы можете рассмотреть возможность использования обычного потокобезопасного кэша вместо этого.

в 2009 году некоторые JVMs реализовали ThreadLocal, используя несинхронизированную хэш-карту в потоке.currentThread (объекта). Это сделало его чрезвычайно быстрым (хотя и не так быстро, как при использовании обычного доступа к полю, конечно), а также гарантировало, что объект ThreadLocal был убран, когда поток умер. Обновление этого ответа в 2016 году, кажется, больше всего (все?) новые виртуальные машины использовать так называемые с линейного пробирования. Я не уверен в производительности этих-но я не могу себе представить, что это так значительно хуже, чем предыдущая реализация.

конечно, новый объект () также очень быстр в эти дни, и сборщики мусора также очень хороши в восстановлении недолговечных объектов.

Если вы не уверены, что создание объекта будет дорогостоящим, или вам нужно сохранить какое-то состояние в потоке по потоку, вам лучше пойти на более простое выделение, когда это необходимо, и только переключиться на реализацию 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 и четырехъядерном i7 с гиперпоточностью (2.67 GHz).

вот эти цифры:

i7

технические характеристики: Процессор Intel i7 с 2х четырехъядерный @ 2.67 ГГц Тест: скала.нити.ParallelTests

имя теста: loop_heap_read

нить num.: 1 Всего тестов: 200

время выполнения: (показывает последние 5) 9.0069 9.0036 9.0017 9.0084 9.0074 (в среднем = 9.1034 мин = 8.9986 Макс = 21.0306 )

нить num.: 2 Всего тестов: 200

время выполнения: (показывает последние 5) 4.5563 4.7128 4.5663 4.5617 4.5724 (в среднем = 4.6337 min = 4.5509 max = 13.9476)

нить num.: 4 Всего тестов: 200

время выполнения: (показывает последние 5) 2.3946 2.3979 2.3934 2.3937 2.3964 (в среднем = 2.5113 мин = 2.3884 Макс = 13.5496)

нить num.: 8 Всего тестов: 200

время выполнения: (показывает последние 5) 2.4479 2.4362 2.4323 2.4472 2.4383 (в среднем = 2.5562 мин = 2.4166 Макс = 10.3726 )

имя теста: threadlocal

нить num.: 1 Всего тестов: 200

время выполнения: (показывает последние 5) 91.1741 90.8978 90.6181 90.6200 90.6113 (avg = 91.0291 min = 90.6000 max = 129.7501 )

нить num.: 2 Всего тестов: 200

время выполнения: (показывает последние 5) 45.3838 45.3858 45.6676 45.3772 45.3839 (в среднем = 46.0555 мин = 45.3726 Макс = 90.7108 )

нить num.: 4 Всего тестов: 200

время выполнения: (показывает последние 5) 22.8118 22.8135 59.1753 22.8229 22.8172 (avg = 23.9752 min = 22.7951 max = 59.1753)

нить num.: 8 Всего тестов: 200

время выполнения: (показывает последние 5) 22.2965 22.2415 22.3438 22.3109 22.4460 (в среднем = 23.2676 мин = 22.2346 Макс = 50.3583)

AMD

спецификации: AMD 8220 4х двухъядерный с частотой 2,8 ГГц Тест: скала.нити.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 )

нить num.: 4 Всего тестов: 200

время выполнения: (показывает последние 5) 3.385 3.385 4.298 9.7 6.535 (СР = 5.6079 мин = 3.354 Макс = 21.603 )

нить num.: 8 Всего тестов: 200

время выполнения: (показывает последние 5) 5.389 5.795 10.818 3.823 3.824 (СР = 5.5810 мин = 2.405 Макс = 19.755 )

имя теста: threadlocal

нить num.: 1 Всего тестов: 200

время выполнения: (показывает последние 5) 200.217 207.335 200.241 207.342 200.23 (в среднем = 202.2424 мин = 200.184 Макс = 245.369 )

нить num.: 2 Всего тестов: 200

время выполнения: (показывает последние 5) 100.208 100.199 100.211 103.781 100.215 (в среднем = 102.2238 мин = 100.192 Макс = 129.505)

нить num.: 4 Всего тестов: 200

время выполнения: (показывает последние 5) 62.101 67.629 62.087 52.021 55.766 (в среднем = 65.6361 мин = 50.282 Макс = 167.433 )

нить num.: 8 Всего тестов: 200

время выполнения: (показывает последние 5) 40.672 74.301 34.434 41.549 28.119 (в среднем = 54.7701 мин = 28.119 Макс = 94.424 )

резюме

локальный поток составляет около 10-20x, что из кучи чтения. Он также, кажется, хорошо масштабируется на этой реализации JVM и этих архитектурах с количеством процессоров.

здесь идет еще один тест. Результатов показывает, что 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);
    }
}
}'

выход:

0-запущенный образец поля

0-конец образца поля: 6044

0-запуск локального потока образца

0-конец потока локальный образец:6015

1-Запуск образца поля

1-конец образца поля: 5095

1-запуск локального потока образец

1-конец потока локальный образец: 5720

2-Запуск образца поля

2-Концевой образец поля: 4842

2-запуск потока локального образца

2-Концевой поток локальный образец: 5835

3-Запуск образца поля

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

поле avg: 5051

ThreadLocal avg: 5664

Env:

openjdk версия "1.8.0_131"

процессор Intel ® Core™ i7-7500U @ 2.70 GHz × 4

Ubuntu 16.04 LTS

@Pete является правильным тестом, прежде чем оптимизировать.

Я был бы очень удивлен, если построение MessageDigest имеет какие-либо серьезные накладные расходы по сравнению с actaully его использованием.

Мисс с помощью ThreadLocal может быть источником утечек и висячих ссылок, которые не имеют четкого жизненного цикла, как правило, я никогда не использую ThreadLocal без очень четкого плана, когда конкретный ресурс будет удален.

построить его и измерить его.

кроме того, вам нужен только один threadlocal, если вы инкапсулируете свое поведение переваривания сообщений в объект. Если вам нужен локальный MessageDigest и локальный байт[1000] для какой-либо цели, создайте объект с полем messageDigest и byte [] и поместите этот объект в ThreadLocal, а не по отдельности.