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

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

Более конкретным является создание простого объекта быстрее или медленнее, чем доступ к переменной ThreadLocal?

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

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

Ответ 1

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

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

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

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

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

Ответ 2

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

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

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

Ответ 3

Хороший вопрос, я спрашивал себя об этом недавно. Чтобы дать вам определенные номера, приведенные ниже тесты (в 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 ГГц и четырехъядерном i7 с гиперпотоком (2,67 ГГц).

Это числа:

i7

Характеристики: Intel i7 2x quad-core @2,67 ГГц Тест: scala.threads.ParallelTests

Имя теста: loop_heap_read

Номер темы: 1 Всего тестов: 200

Время выполнения: (показывается 5 последних)  9,0069 9,0036 9,0017 9,0084 9,0074 (среднее value = 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 (среднее value = 2,5113 мин = 2,3884 макс = 13,5496)

Номер темы: 8 Всего тестов: 200

Время выполнения: (показывается 5 последних)  2,4479 2,4362 2,4323 2,4472 2,4383 (avg = 2,5562 мин = 2,4166 максимум = 10,3726)

Имя теста: threadlocal

Номер темы: 1 Всего тестов: 200

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

Номер темы: 2 Всего тестов: 200

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

Номер темы: 4 Всего тестов: 200

Время выполнения: (показывается 5 последних)  22,8118 22,8135 59,1775 22,8229 22,8172 (среднее value = 23,9752 мин = 22,7951 макс = 59,1773)

Номер темы: 8 Всего тестов: 200

Время выполнения: (показывается 5 последних)  22,2965 22,2415 22,3438 22,3109 22,4460 (среднее value = 23,2676 мин = 22,2346 максимум = 50,3583)

AMD

Технические характеристики: AMD 8220 4x двухъядерный процессор 2,8 ГГц Тест: scala.threads.ParallelTests

Имя теста: loop_heap_read

Общая работа: 20000000 Номер темы: 1 Всего тестов: 200

Время выполнения: (показывается 5 последних)  12,625 12,631 12,634 12,632 12,628 (avg = 12,7333 мин = 12,619 макс = 26,698)

Имя теста: loop_heap_read Общая работа: 20000000

Время выполнения: (показывается 5 последних)  6,412 6,424 6,408 6,397 6,43 (avg = 6,5367 мин = 6,393 макс = 19,716)

Номер темы: 4 Всего тестов: 200

Время выполнения: (показывается 5 последних)  3,385 4,298 9,7 6,535 3,385 (avg = 5,6079 мин = 3,354 макс = 21,603)

Номер темы: 8 Всего тестов: 200

Время выполнения: (показывается 5 последних)  5,389 5,795 10,818 3,823 3,824 (avg = 5,5810 мин = 2,405 макс = 19,755)

Имя теста: threadlocal

Номер темы: 1 Всего тестов: 200

Время выполнения: (показывается 5 последних)  200,217 207,335 200,241 207,342 200,23 (среднее value = 202,2424 мин = 200,184 макс = 245,369)

Номер темы: 2 Всего тестов: 200

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

Номер темы: 4 Всего тестов: 200

Время выполнения: (показывается 5 последних)  62,101 67,629 62,087 52,021 55,766 (среднее value = 65,6361 мин = 50,282 макс = 167,433)

Номер темы: 8 Всего тестов: 200

Время выполнения: (показывается 5 последних)  40,672 74,301 34,434 41,549 28,119 (avg = 54,7701 мин = 28,119 макс = 94,424)

Резюме

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

Ответ 4

@Pete - это правильный тест перед оптимизацией.

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

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

Ответ 5

Здесь идет еще один тест. Результаты показывают, что 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-End: 6044

0-Локальный образец рабочей нити

0-Конечная локальная выборка: 6015

1-Образец рабочего пробега

Пример поля 1-End: 5095

1-Запуск локальной выборки потока

Локальная выборка с 1 концом: 5720

2-Пробег образца поля

Пример поля 2-End: 4842

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

Локальная выборка с двумя концами: 5835

3-Пробег образца поля

Пример поля 3-End: 4674

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

Локальная выборка с тремя концами: 5287

4-Пробежающий образец поля

Пример поля 4-End: 4849

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

Локальная выборка с четырьмя концами: 5309

5-Запуск полевого образца

Пример поля 5-End: 4781

5-Запуск локальной выборки потока

5-End локальная выборка: 5330

6-Образец пробного пробега

Пример поля 6-End: 5294

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

Локальный образец нити 6-End: 5511

7-Пример рабочего пробега

Пример поля 7-End: 5119

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

7-Конечная локальная выборка: 5793

8-Образец рабочего пробега

Пример поля 8-End: 4977

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

Локальный образец потока 8-End: 6374

9-Пробег образца поля

Пример поля 9-End: 4841

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

Локальная выборка с 9-концевой нитью: 5471

Поле avg: 5051

ThreadLocal avg: 5664

Env:

openjdk версия "1.8.0_131"

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

Ubuntu 16.04 LTS

Ответ 6

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

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