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

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

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:

Ответ 1

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

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

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

Amadhal law

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

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

PS: Я предлагаю, перейдя в раздел Performance из книги - Java Concurrency на практике Брайана Гетца. Он подробно рассматривает такие вопросы.

Ответ 2

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

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

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

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

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

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

Ответ 3

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