Насколько из ThreadLocal
переменной читается медленнее, чем из обычного поля?
Более конкретно, создание простого объекта быстрее или медленнее, чем доступ к ThreadLocal
переменной?
Я предполагаю, что это достаточно быстро, так что наличие ThreadLocal<MessageDigest>
экземпляра намного быстрее, чем создание экземпляра MessageDigest
каждый раз. Но применимо ли это также, например, к байту [10] или байту [1000]?
Изменить: вопрос в том, что на самом деле происходит при вызове ThreadLocal
get? Если это просто поле, как и любое другое, тогда ответ будет «это всегда самое быстрое», верно?
Thread
s содержат (несинхронизированную) хэш-карту, где ключом является текущийThreadLocal
объектОтветы:
Выполнение неопубликованных тестов
ThreadLocal.get
на моей машине занимает около 35 циклов на итерацию. Ничего особенного. В реализации Sun настраиваемая хэш-карта линейного зондирования вThread
сопоставленииThreadLocal
s со значениями. Поскольку к нему всегда обращается только один поток, он может быть очень быстрым.Выделение небольших объектов занимает примерно такое же количество циклов, хотя из-за исчерпания кеша вы можете получить несколько меньшие цифры в жестком цикле.
Строительство,
MessageDigest
вероятно, будет относительно дорогим. Он имеет изрядное количество состояний и строительство проходит черезProvider
механизм SPI. Вы можете оптимизировать, например, клонируя или предоставляяProvider
.Тот
ThreadLocal
факт, что кэширование в a может быть быстрее , чем создание, не обязательно означает, что производительность системы повысится. У вас будут дополнительные накладные расходы, связанные с сборщиком мусора, который все замедляет.Если ваше приложение не очень сильно использует,
MessageDigest
вы можете вместо этого рассмотреть возможность использования обычного поточно-безопасного кеша.источник
new org.bouncycastle.crypto.digests.SHA1Digest()
. Я совершенно уверен, что никакой кэш не сможет его победить.В 2009 году некоторые JVM реализовали ThreadLocal с использованием несинхронизированной HashMap в объекте Thread.currentThread (). Это сделало его чрезвычайно быстрым (хотя, конечно, не таким быстрым, как использование обычного доступа к полю), а также обеспечило приведение в порядок объекта ThreadLocal после смерти Thread. Обновляя этот ответ в 2016 году, кажется, что большинство (все?) Новые JVM используют ThreadLocalMap с линейным зондированием. Я не уверен в их производительности, но не могу представить, что это значительно хуже, чем в более ранней реализации.
Конечно, 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 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,5563 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,5562 мин. = 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, четырехъядерный процессор, 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,354 макс. = 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 и в этих архитектурах с количеством процессоров.
источник
"!"
никогда не происходит) в первом методе - первый метод фактически эквивалентенThread
созданию подкласса и предоставлению ему настраиваемого поля. Тест измеряет крайний крайний случай, когда все вычисления состоят из чтения локальной переменной / потока - реальные приложения могут не пострадать в зависимости от их шаблона доступа, но в худшем случае они будут вести себя, как указано выше.Вот и еще одно испытание. Результаты показывают, что 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-End образец поля: 6044
0-Запуск локального потока
0-конечный образец резьбы: 6015
1-бегущий полевой образец
Пример 1-конечного поля: 5095
Локальный образец 1-Running thread
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-End образец поля: 5294
Локальный образец 6-выполняющегося потока
6-концевая резьба, местный образец: 5511
7-беговой полевой образец
7-End образец поля: 5119
7-Запуск локального потока
7-концевая резьба, локальный образец: 5793
8-беговой полевой образец
8-End образец поля: 4977
8-выполняющийся локальный пример потока
8-концевая резьба, местный образец: 6374
9-беговой полевой образец
9-End образец поля: 4841
9-Локальный образец выполняющегося потока
9-концевая резьба, местный образец: 5471
Среднее поле: 5051
ThreadLocal в среднем: 5664
Env:
openjdk версия "1.8.0_131"
Процессор Intel® Core ™ i7-7500U @ 2,70 ГГц × 4
Ubuntu 16.04 LTS
источник
Int.toString)
что чрезвычайно дорого по сравнению с тем, что вы тестируете. B) вы выполняете две операции карты на каждой итерации, также совершенно несвязанные и дорогие. Попробуйте вместо этого увеличить примитивный int из ThreadLocal. C) ИспользованиеSystem.nanoTime
вместоSystem.currentTimeMillis
, бывшее для профилирования, последнее для пользовательских целей даты и времени и может изменяться под ногами. D) Вам следует полностью избегать аллоки, включая аллоки верхнего уровня для ваших «примерных» классов@Pete - правильный тест перед оптимизацией.
Я был бы очень удивлен, если бы создание MessageDigest сопряжено с серьезными накладными расходами по сравнению с его фактическим использованием.
Отсутствие использования ThreadLocal может быть источником утечек и висящих ссылок, у которых нет четкого жизненного цикла, обычно я никогда не использую ThreadLocal без очень четкого плана того, когда будет удален конкретный ресурс.
источник
Постройте и измерьте.
Кроме того, вам нужен только один threadlocal, если вы инкапсулируете поведение переваривания сообщения в объект. Если вам для какой-то цели нужны локальный MessageDigest и локальный байт [1000], создайте объект с полем messageDigest и byte [] и поместите этот объект в ThreadLocal, а не в оба по отдельности.
источник