Накладные расходы на создание streamов Java

Обычная мудрость говорит нам о том, что приложения большого объема предприятия Java должны использовать пул streamов, предпочитая создавать новые рабочие streamи. Использование java.util.concurrent делает это простым.

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

Теперь есть пути вокруг этого (локаторы streamов могут быть явно переданы), но это не всегда правильно или практично. Самое простое решение – создать новые рабочие streamи по требованию и позволить InheritableThreadLocal выполнять свою работу.

Это возвращает нас к вопросу – если у меня есть сайт с большим объемом, где streamи пользовательского запроса порождают полдюжины рабочих streamов каждый (т. Е. Не используют пул streamов), будет ли это проблемой для JVM? Мы потенциально говорим о создании нескольких сотен новых streamов каждую секунду, каждая из которых длится менее секунды. Могут ли современные JVM оптимизировать это? Я помню дни, когда объединение объектов было желательно в Java, потому что создание объекта было дорогостоящим. С тех пор это стало ненужным. Мне интересно, если это относится к пулу streamов.

Я бы оценил это, если бы знал, что измерить, но я боюсь, что проблемы могут быть более тонкими, чем можно измерить с помощью профилировщика.

Примечание: мудрость использования локаторов streamов здесь не проблема, поэтому, пожалуйста, не предлагайте, чтобы я их не использовал.

Вот пример микробизнеса:

 public class ThreadSpawningPerformanceTest { static long test(final int threadCount, final int workAmountPerThread) throws InterruptedException { Thread[] tt = new Thread[threadCount]; final int[] aa = new int[tt.length]; System.out.print("Creating "+tt.length+" Thread objects... "); long t0 = System.nanoTime(), t00 = t0; for (int i = 0; i < tt.length; i++) { final int j = i; tt[i] = new Thread() { public void run() { int k = j; for (int l = 0; l < workAmountPerThread; l++) { k += k*k+l; } aa[j] = k; } }; } System.out.println(" Done in "+(System.nanoTime()-t0)*1E-6+" ms."); System.out.print("Starting "+tt.length+" threads with "+workAmountPerThread+" steps of work per thread... "); t0 = System.nanoTime(); for (int i = 0; i < tt.length; i++) { tt[i].start(); } System.out.println(" Done in "+(System.nanoTime()-t0)*1E-6+" ms."); System.out.print("Joining "+tt.length+" threads... "); t0 = System.nanoTime(); for (int i = 0; i < tt.length; i++) { tt[i].join(); } System.out.println(" Done in "+(System.nanoTime()-t0)*1E-6+" ms."); long totalTime = System.nanoTime()-t00; int checkSum = 0; //display checksum in order to give the JVM no chance to optimize out the contents of the run() method and possibly even thread creation for (int a : aa) { checkSum += a; } System.out.println("Checksum: "+checkSum); System.out.println("Total time: "+totalTime*1E-6+" ms"); System.out.println(); return totalTime; } public static void main(String[] kr) throws InterruptedException { int workAmount = 100000000; int[] threadCount = new int[]{1, 2, 10, 100, 1000, 10000, 100000}; int trialCount = 2; long[][] time = new long[threadCount.length][trialCount]; for (int j = 0; j < trialCount; j++) { for (int i = 0; i < threadCount.length; i++) { time[i][j] = test(threadCount[i], workAmount/threadCount[i]); } } System.out.print("Number of threads "); for (long t : threadCount) { System.out.print("\t"+t); } System.out.println(); for (int j = 0; j < trialCount; j++) { System.out.print((j+1)+". trial time (ms)"); for (int i = 0; i < threadCount.length; i++) { System.out.print("\t"+Math.round(time[i][j]*1E-6)); } System.out.println(); } } } 

Результаты на 64-битной Windows 7 с 32-разрядной клиентской виртуальной машиной Java 1.6.0_21 Sun на Intel Core2 Duo E6400 @ 2.13 ГГц следующие:

 Number of threads 1 2 10 100 1000 10000 100000 1. trial time (ms) 346 181 179 191 286 1229 11308 2. trial time (ms) 346 181 187 189 281 1224 10651 

Выводы. Два streamа выполняют работу почти в два раза быстрее, чем один, как и ожидалось, поскольку мой компьютер имеет два ядра. Мой компьютер может генерировать почти 10000 streamов в секунду, то есть накладные расходы на создание streamов составляют 0,1 миллисекунды . Следовательно, на такой машине несколько сотен новых streamов в секунду создают незначительные накладные расходы (что также можно увидеть, сравнивая числа в столбцах для 2 и 100 streamов).

Прежде всего, это, конечно же, будет зависеть от того, какую именно JVM вы используете. ОС также будет играть важную роль. Предполагая, что Sun JVM (Hm, мы все еще называем это?):

Одним из основных факторов является память стека, выделенная для каждого streamа, которую вы можете настроить с помощью параметра -Xssn JVM – вы захотите использовать самое низкое значение, с которым вы можете избавиться.

И это всего лишь предположение, но я думаю, что «несколько сотен новых streamов каждую секунду» определенно выше того, что JVM предназначен для удобного управления. Я подозреваю, что простой тест быстро выявит довольно непростые проблемы.

  • для вашего теста вы можете использовать JMeter + профилировщик, который должен дать вам прямой обзор поведения в такой сильно загруженной среде. Просто позвольте ему работать в течение часа и следить за памятью, процессором и т. Д. Если ничего не сломается, и CPU (ы) не перегреваются, все в порядке 🙂

  • возможно, вы можете получить пул streamов или настроить (продлить) тот, который вы используете, добавив некоторый код, чтобы каждый раз, когда Thread был получен из пула streamов, у вас есть соответствующий InheritableThreadLocal s. Каждая Thread имеет эти частные свойства:

     /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; /* * InheritableThreadLocal values pertaining to this thread. This map is * maintained by the InheritableThreadLocal class. */ ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; 

    Вы можете использовать их (ну, с reflectionм) в сочетании с Thread.currentThread() чтобы иметь желаемое поведение. Однако это немного ад-скакалка, и, кроме того, я не могу сказать, не отразится ли это (с reflectionм) даже большие накладные расходы, чем просто создание streamов.

Мне интересно, нужно ли создавать новые streamи для каждого пользовательского запроса, если их типичный жизненный цикл короче секунды. Не могли бы вы использовать какую-то очередь Notify / Wait, в которой вы порождаете заданное количество streamов (daemon), и все они ждут, пока не будет решена задача. Если очередь задач становится длинной, вы создаете дополнительные streamи, но не в соотношении 1-1. Скорее всего, это будет лучше, чем порождать сотни новых streamов, жизненные циклы которых настолько коротки.

  • Насколько дорогим является RTTI?
  • В .NET, какой цикл работает быстрее, «for» или «foreach»?
  • MySQL: самый быстрый способ подсчета количества строк
  • Почему автореферат особенно опасен / дорого для приложений iPhone?
  • Является ли Java очень медленным?
  • Храните PostgreSQL от выбора плохого плана запроса
  • смешение cout и printf для более быстрого вывода
  • Что такое урожайность Скалы?
  • C # самый быстрый способ смены массива
  • Заменить значения в списке с помощью Python
  • Предотвратите сборку мусора .NET за короткий промежуток времени
  • Давайте будем гением компьютера.