Установка идеального размера пула потоков

В чем разница между-

newSingleThreadExecutor vs newFixedThreadPool(20)

с точки зрения операционной системы и точки зрения программирования.

Всякий раз, когда я запускаю свою программу, используя newSingleThreadExecutor моя программа работает очень хорошо, и приходит конец сквозной задержки (95-й процентиль) 5ms,

Но как только я запускаю свою программу, используя

newFixedThreadPool(20)

производительность моей программы снижается, и я начинаю видеть сквозную задержку как 37ms,

Итак, теперь я пытаюсь понять с точки зрения архитектуры, что означает число потоков здесь? И как решить, какое оптимальное количество нитей мне выбрать?

И если я использую большее количество потоков, что произойдет?

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

Моя конфигурация машины - я запускаю программу с машины Linux

processor       : 0
vendor_id       : GenuineIntel
cpu family      : 6
model           : 45
model name      : Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz
stepping        : 7
cpu MHz         : 2599.999
cache size      : 20480 KB
fpu             : yes
fpu_exception   : yes
cpuid level     : 13
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology tsc_reliable nonstop_tsc aperfmperf pni pclmulqdq ssse3 cx16 sse4_1 sse4_2 popcnt aes hypervisor lahf_lm arat pln pts
bogomips        : 5199.99
clflush size    : 64
cache_alignment : 64
address sizes   : 40 bits physical, 48 bits virtual
power management:

processor       : 1
vendor_id       : GenuineIntel
cpu family      : 6
model           : 45
model name      : Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz
stepping        : 7
cpu MHz         : 2599.999
cache size      : 20480 KB
fpu             : yes
fpu_exception   : yes
cpuid level     : 13
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology tsc_reliable nonstop_tsc aperfmperf pni pclmulqdq ssse3 cx16 sse4_1 sse4_2 popcnt aes hypervisor lahf_lm arat pln pts
bogomips        : 5199.99
clflush size    : 64
cache_alignment : 64
address sizes   : 40 bits physical, 48 bits virtual
power management:

3 ответа

Решение

Хорошо. В идеале предполагая, что ваши потоки не имеют блокировки, так что они не блокируют друг друга (независимо друг от друга), и вы можете предположить, что рабочая нагрузка (обработка) одинакова, тогда получается, что размер пула равен Runtime.getRuntime().availableProcessors() или же availableProcessors() + 1 дает лучшие результаты.

Но, скажем, если потоки мешают друг другу или имеют входы / выходы, то закон Амадала довольно хорошо объясняет. Из вики,

Закон Амдала гласит, что если P - это доля программы, которую можно сделать параллельной (то есть, извлечь выгоду из распараллеливания), а (1 - P) - это пропорция, которую нельзя распараллелить (остается последовательной), то максимальное ускорение, которое может быть достигается с помощью N процессоров

Амадальский закон

В вашем случае, исходя из количества доступных ядер и того, какую работу они точно выполняют (чистые вычисления? Блокировка ввода / вывода? Блокировка для какого-либо ресурса? И т. Д.), Вам нужно найти решение, основанное на приведенном выше параметры.

Например: несколько месяцев назад я занимался сбором данных с цифровых веб-сайтов. Моя машина была 4-ядерная, и у меня был размер пула 4, Но потому что операция была чисто I/O и моя чистая скорость была приличной, я понял, что у меня была лучшая производительность с размером пула 7, И это потому, что потоки боролись не за вычислительную мощность, а за ввод-вывод. Так что я мог бы использовать тот факт, что больше потоков могут бороться за ядро ​​положительно.

PS: предлагаю пройтись по главе "Производительность из книги - параллелизм Java на практике" Брайана Гетца. Здесь подробно рассматриваются такие вопросы.

Итак, теперь я пытаюсь понять с точки зрения архитектуры, что означает число потоков здесь?

Каждый поток имеет свою собственную стековую память, счетчик программ (например, указатель на то, какая инструкция выполняется далее) и другие локальные ресурсы. Замена их вредит латентности для одной задачи. Преимущество заключается в том, что пока один поток простаивает (обычно при ожидании ввода / вывода), другой поток может выполнить свою работу. Также, если доступно несколько процессоров, они могут работать параллельно, если между задачами нет ресурса и / или блокировки блокировки.

И как решить, какое оптимальное количество нитей мне выбрать?

Компромисс между ценой свопа и возможностью избежать простоя зависит от мелких деталей того, как выглядит ваша задача (какой объем ввода / вывода и когда, сколько работы между вводом / выводом, сколько памяти используется для полный). Экспериментирование всегда является ключом.

И если я использую большее количество потоков, что произойдет?

Обычно сначала происходит линейный рост пропускной способности, затем относительно плоская часть, а затем падение (которое может быть довольно крутым). Каждая система отличается.

Глядя на закон Амдала, это хорошо, особенно если вы точно знаете, насколько велики P и N. Поскольку этого никогда не произойдет, вы можете следить за производительностью (что вы должны делать в любом случае) и увеличивать / уменьшать размер пула потоков, чтобы оптимизировать любые показатели производительности, которые важны для вас.

Другие вопросы по тегам