Производительность переменной ThreadLocal - PullRequest
82 голосов
/ 04 марта 2009

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

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

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

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

Ответы [ 6 ]

55 голосов
/ 04 марта 2009

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

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

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

38 голосов
/ 04 марта 2009

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

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

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

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

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

34 голосов
/ 21 января 2011

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

Вот цифры:

1010 * i7 * Характеристики: Intel i7 2x четырехъядерный с тактовой частотой 2,67 ГГц Тест: 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,5553 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,5552 мин = 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

Технические характеристики: двухъядерный процессор 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 (среднее = 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,304 макс = 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, и для этих архитектур с числом процессоров.

5 голосов
/ 07 сентября 2017

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

2-конец резьбы локального образца: 5835

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

3-конец поля образца: 4674

3-ходовой поток локального образца

3-конец резьбы локального образца: 5287

4-беговое поле образца

4-конец поля образца: 4849

4-ходовая резьба локального образца

4-конец резьбы локального образца: 5309

5-беговое поле образца

5-Конечный образец поля: 4781

5-Running thread local sample

5-Конечная резьба локального образца: 5330

6-беговое поле образца

6-конец поля образца: 5294

6-Running thread local sample

6-Конечная резьба локального образца: 5511

7-Бегущий образец поля

7-конец поля образца: 5119

7-Running thread local sample

7-Конечная резьба локального образца: 5793

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

8-конец поля образца: 4977

8-Running thread local sample

8-Конечная резьба локального образца: 6374

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

9-Конечный образец поля: 4841

9-Running thread local sample

9-Конечная резьба локального образца: 5471

Поле, среднее значение: 5051

ThreadLocal avg: 5664

ENV:

openjdk версия "1.8.0_131"

Процессор Intel® Core ™ i7-7500U @ 2,70 ГГц × 4

Ubuntu 16.04 LTS

3 голосов
/ 04 марта 2009

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

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

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

0 голосов
/ 04 марта 2009

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

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

...