Как определить оптимальное количество потоков для сетевых запросов с высокой задержкой?

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

Чистый эффект этого заключается в том, что компьютер не привязан к IO, привязан к файловой системе или связан с ЦП, он связан только с задержкой ответов.

Это похоже на, но не то же самое, что Есть ли способ определить идеальное количество потоков? и Java лучший способ определить оптимальное количество потоков [duplicate]... Основное различие заключается в том, что я связан только с задержкой.

Я использую объект ExecutorService для запуска потоков и Queue<Future<Integer>> для отслеживания потоков, требующих получения результатов:

ExecutorService executorService = Executors.newFixedThreadPool(threadPoolSize);
Queue<Future<Integer>> futures = new LinkedList<Future<Integer>>();

for (int quad3 = 0 ; quad3 < 256 ; ++quad3) {
    for (int quad4 = 0 ; quad4 < 256 ; ++quad4) {
        byte[] quads = { quad1, quad2, (byte)quad3, (byte)quad4 };
        futures.add(executorService.submit(new RetrieverCallable(quads)));
    }
}

... Затем я удаляю все элементы в очереди и помещаю результаты в требуемую структуру данных:

int[] result = int[65536]
while(!futures.isEmpty()) {
    try {
        results[i] = futures.remove().get();
    } catch (Exception e) {
        addresses[i] = -1;
    }
}

Мой первый вопрос: это разумный способ отслеживать все потоки? Если поток X займет некоторое время, многие другие потоки могут завершиться до выполнения X. Будет ли пул потоков исчерпать себя, ожидая открытых слотов, или будет ли объект ExecutorService управлять пулом таким образом, чтобы потоки, которые были завершены, но еще не обработаны, были удалены из доступных слотов, чтобы начать другие потоки?

Мой второй вопрос - какие рекомендации я могу использовать для поиска оптимального количества потоков для выполнения этих вызовов? Я даже не знаю руководства по порядку величины. Я знаю, что он отлично работает с 256 потоками, но, похоже, занимает примерно одинаковое общее время с 1024 потоками. Загрузка процессора колеблется около 5%, так что это не проблема. С этим большим количеством потоков, каковы все показатели, на которые я должен смотреть, сравнивать разные числа? Очевидно, общее время для обработки партии, среднее время на поток... что еще? Здесь проблема памяти?

Ответ 1

Это вас шокирует, но вам не нужны нити для ввода-вывода (количественно это означает 0 потоков). Хорошо, что вы изучили, что многопоточность не увеличивает вашу пропускную способность сети. Теперь пришло время узнать, что потоки выполняют вычисления. Они не выполняют (с высокой задержкой) связь. Связь выполняется сетевым адаптером, который является еще одним процессом, работающим по-настоящему параллельно с процессором. Глупо выделять поток (см. какие ресурсы выделены этими джентльменами, которые утверждают, что вам нужен 1 поток) просто для сна, пока сетевой адаптер не завершит свою работу. Вам не нужны потоки для ввода/вывода = вам нужны 0 потоков.

Имеет смысл выделять потоки для вычисления параллельно с запросами (ями) ввода-вывода. Количество потоков будет зависеть от отношения вычислений к связи и ограниченного количеством ядра в вашем процессоре.

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

Ответ 2

Как упоминалось в одном из связанных ответов, на который вы ссылаетесь, Брайан Гетц наглядно показал это в статья.

Он, по-видимому, подразумевает, что в вашей ситуации вам рекомендуется собирать метрики, прежде чем совершать подсчет потоков.

Настройка размера пула

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

Оптимальный размер пула потоков зависит от количества доступных процессоров и характера задач в рабочей очереди....

Для задач, которые могут дождаться завершения ввода-вывода - например, задачи, которая считывает HTTP-запрос из сокета, - вы хотите увеличить размер пула за пределы количества доступных процессоров, поскольку не все потоки будет работать во все времена. Используя профилирование, вы можете оценить отношение времени ожидания (WT) к времени обслуживания (ST) для типичного запроса. Если мы будем называть это отношение WT/ST для N-процессорной системы, вы должны иметь потоки N * (1 + WT/ST), чтобы полностью использовать процессоры.

Мой акцент.

Ответ 3

Считаете ли вы использование Актеры?

Лучшие практики.

  • Актеры должны быть похожими на хороших сотрудников: эффективно выполнять свою работу без лишнего беспокойства, и не избегать свиней Ресурсы. Перевод на программирование означает обработку событий и генерировать ответы (или больше запросов) в зависимости от событий. Актеры не должны блокироваться (т.е. Пассивно ждать при занятии Thread) на каком-то внешнем объекте, который может быть блокировкой, сетевым сокетом, и т.д. - если это не неизбежно; в последнем случае см. ниже.

Извините, я не могу уточнить, потому что не очень использовал это.

UPDATE

Ответ в Хороший вариант использования Akka может оказаться полезным.
Scala: Почему актеры легкие?

Ответ 4

Довольно точно в описанных обстоятельствах оптимальное количество потоков равно 1. На самом деле это на удивление часто является ответом на любой вопрос о форме "сколько потоков я должен использовать"?

Каждый дополнительный поток добавляет дополнительные накладные расходы в терминах стека (и связанных корней GC), переключения контекста и блокировки. Это может быть или не поддаваться измерению: эффект, чтобы осмысленно измерить его во всех целевых средах, является нетривиальным. В свою очередь, мало возможностей для предоставления любого преимущества, так как обработка не является ни CPU, ни io-bound.

Так меньше всегда лучше, если только по причинам снижения риска. И у вас не может быть менее 1.

Ответ 5

В наших высокопроизводительных системах мы используем актерскую модель, описанную @Andri Chaschev.

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

  • Если у вас только 1 процесс, используйте общие ядра ЦП - 2.
  • Если у вас несколько процессов, проверьте свою структуру процессора. Мы обнаружили, что это хорошо. нитей = нет. ядер в одном процессоре - например, если у вас есть 4-процессорный сервер, каждый сервер имеет 4 ядра, то использование 4 потоков на JVM дает вам лучшую производительность. После этого всегда оставляйте по крайней мере 1 ядро ​​для вашей ОС.

Ответ 6

Я предполагаю, что желаемая оптимизация - это время для обработки всех запросов. Вы сказали, что количество запросов - "тысячи". Очевидно, что самый быстрый способ - выдать все запросы одновременно, но это может привести к переполнению сетевого уровня. Вы должны определить, сколько одновременных подключений может поддерживать сетевой уровень, и сделать этот номер параметром для вашей программы.

Затем для трассировки потока для каждого запроса требуется много памяти. Вы можете избежать этого, используя неблокирующие сокеты. В Java есть два варианта: NIO1 с селекторами и NIO2 с асинхронными каналами. NIO1 сложный, поэтому лучше найти готовую библиотеку и повторно использовать ее. NIO2 прост, но доступен только с JDK1.7.

Обработка ответов должна выполняться в пуле потоков. Я не думаю, что количество потоков в пуле потоков сильно влияет на общую производительность вашего дела. Просто настройте размер пула потоков от 1 до количества доступных процессоров.

Ответ 7

Частичный ответ, но я надеюсь, что это поможет. Да, память может быть проблемой: Java резервирует по 1 МБ стека потоков по умолчанию (по крайней мере, на Linux amd64). Таким образом, с несколькими ГБ оперативной памяти в вашем ящике, это ограничивает количество ваших потоков до нескольких тысяч.

Вы можете настроить этот флажок, например -XX:ThreadStackSize=64. Это даст вам 64 kB, что в большинстве случаев много.

Вы также можете полностью отказаться от потоковой передачи и использовать epoll для ответа на входящие ответы. Это гораздо более масштабируемо, но у меня нет практического опыта работы с этим в Java.