Производительность переменной ThreadLocal
-
03-07-2019 - |
Вопрос
Сколько считывается из ThreadLocal
переменная медленнее, чем из обычного поля?
Более конкретно, является ли простое создание объекта более быстрым или медленным, чем доступ к ThreadLocal
переменная?
Я предполагаю, что это достаточно быстро, чтобы, имея ThreadLocal<MessageDigest>
создание экземпляра происходит намного быстрее, чем создание экземпляра MessageDigest
каждый раз.Но применимо ли это также, например, к байту [10] или байту [1000]?
Редактировать:Вопрос в том, что на самом деле происходит при вызове ThreadLocal
доберемся?Если это просто поле, как и любое другое, то ответом будет "это всегда быстрее всего", верно?
Решение
Запуск неопубликованных тестов, ThreadLocal.get
занимает около 35 циклов на итерацию на моем компьютере. Не очень много. В реализации Sun пользовательская карта хеша линейного зондирования в Thread
отображает ThreadLocal
значения. Поскольку доступ к нему возможен только из одного потока, он может быть очень быстрым.
Распределение мелких объектов занимает одинаковое количество циклов, хотя из-за исчерпания кеша вы можете получить несколько меньшие цифры в узком цикле.
Строительство MessageDigest
скорее всего будет относительно дорогим. Он имеет достаточное количество состояния, и строительство проходит через механизм Provider
SPI. Вы можете оптимизировать, например, путем клонирования или предоставления <=>.
Тот факт, что кеширование в <=>, а не при создании, может быть быстрее, не обязательно означает, что производительность системы увеличится. У вас будут дополнительные накладные расходы, связанные с GC, который все замедляет.
Если ваше приложение не использует слишком интенсивно <=>, вы можете использовать вместо этого обычный поточно-ориентированный кеш.
Другие советы
В 2009 году некоторые JVM реализовали ThreadLocal, используя несинхронизированный HashMap в объекте Thread.currentThread (). Это сделало его чрезвычайно быстрым (хотя и не таким быстрым, как при использовании обычного доступа к полю, конечно), а также гарантировало, что объект ThreadLocal был приведен в порядок после смерти Thread. Обновляя этот ответ в 2016 году, кажется, что большинство (все?) Более новых JVM используют ThreadLocalMap с линейным зондированием. Я не уверен в производительности этих & # 8211; но я не могу представить, что это значительно хуже, чем предыдущая реализация.
Конечно, new Object () также очень быстр в наши дни, и сборщики мусора также очень хороши в восстановлении недолговечных объектов.
Если вы не уверены, что создание объекта будет дорогостоящим, или вам не нужно сохранять какое-то состояние в потоке за потоком, лучше перейти к более простому распределению, когда это необходимо, и переключиться только на 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 ГГц и четырехъядерном процессоре i7 с гиперпоточностью (2,67 ГГц).
Это цифры:
i7
Характеристики: четырехъядерный процессор Intel i7 2x2,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 с частотой 4,8 ГГц и частотой 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,950).
Номер темы: 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, и для этих архитектур с числом процессоров.
Вот и начинается еще одно испытание.Результаты показывают, что 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
Среднее значение поля: 5051
Среднее значение параметра ThreadLocal: 5664
Env:
версия openjdk "1.8.0_131"
Процессор Intel® Core™ i7-7500U с частотой 2,70 ГГц × 4
Ubuntu 16.04 LTS
@Pete - это правильный тест перед оптимизацией.
Я был бы очень удивлен, если создание MessageDigest имеет серьезные накладные расходы по сравнению с его активным использованием.
Мисс при использовании ThreadLocal может стать источником утечек и висячих ссылок, у которых нет четкого жизненного цикла, как правило, я никогда не использую ThreadLocal без очень четкого плана, когда определенный ресурс будет удален. р>
Постройте и измерьте его.
Кроме того, вам нужен только один локальный поток, если вы инкапсулируете свое поведение переваривания сообщений в объект. Если для каких-то целей вам нужен локальный MessageDigest и локальный байт [1000], создайте объект с полем messageDigest и байтом [] и поместите этот объект в ThreadLocal, а не по отдельности.