2013-10-24 5 views
10

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

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

Это похоже, но не такой же, как и There is a way to determine the ideal number of threads?Java best way to determine the optimal number of threads [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))); 
    } 
} 

... Я тогда Dequeue всех элементов в очереди и поместить результаты в необходимой структуре данных:

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%, так что это не проблема. С этим большим количеством потоков, каковы все показатели, на которые я должен смотреть, сравнивать разные числа? Очевидно, общее время для обработки партии, среднее время на поток ... что еще? Здесь проблема памяти?

ответ

7

Это вас шокирует, но you do not need any threads for I/O (количественно это означает 0 потоков). Хорошо, что вы изучили, что многопоточность не увеличивает вашу пропускную способность сети. Теперь пришло время узнать, что потоки выполняют вычисления. Они не выполняют (с высокой задержкой) связь. Связь выполняется сетевым адаптером, который является еще одним процессом, работающим по-настоящему параллельно с процессором. Глупо выделять нить (см. which resources allocated are listed by this gentlemen who claims that you need 1 thread) просто спать, пока сетевой адаптер не завершит свою работу. Вам не нужны потоки для ввода/вывода = вам нужны 0 потоков.

Имеет смысл распределить потоки для вычислений параллельно с запросами (вводами) ввода-вывода. Количество потоков будет зависеть от computation-to-communication ratio и limited by the number of cores in your CPU.

К сожалению, я должен был сказать, что, несмотря на то, что вы, несомненно, подразумевали приверженность блокированию ввода-вывода, многие люди не понимают эту основную вещь. Возьмите the advise, use asynchronous I/O, и вы увидите, что проблема не существует.

+0

Это правильно. Единственная проблема заключается в том, что требуется создать структуру поверх NIO2. –

+0

@ Андрей, я вижу. Другое дело, создание фейерверка поверх актеров. Это не требует создания рамки поверх Актеров. Могу я это сказать? – Val

+1

Ну, это упрямая тема. И я считаю, что потоки более интуитивно понятны для этого типа работы. А актеры - зеленые, легкие нитки. –

0

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

Это можно с -XX:ThreadStackSize=64. Это даст вам 64 kB, что много в большинстве ситуаций.

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

+1

Проблема заключается в том, что вы можете устанавливать размер стека потоков только глобально, а для основных потоков приложения - 64 КБ, может быть очень неадекватным - просто посмотрите на стековые следы типичного приложения Spring или EJB. –

+0

К сожалению, я работаю в среде, где я ограничен 4 ГБ общей оперативной памяти, поэтому, даже если я увеличиваю размер стека нитей, я не могу увеличить общую доступность потоков. – seawolf

3

Рассматривали ли вы использование Actors?

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

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

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

UPDATE

Ответ в Good use case for Akka может быть полезным.
Scala: Why are Actors lightweight?

+0

Отлично, я прочитаю об этом и посмотрю, поможет ли это. – seawolf

5

Как уже упоминалось в одном из связанных ответов вы ссылаетесь, Brian Goetz покрыла это хорошо в его article.

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

Tuning бассейн размер

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

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

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

Мой акцент.

+0

Я пропустил эту ссылку, и я рад, что вы указали мне на это ... много хорошего. Я определенно в лагере, который выходит за рамки формулы, которую вы предоставили, N * (1 + WT/ST) - такое большое количество, что у меня заканчивается память, если я использую его. Кажется, что на моем компьютере связано около 1200-1300 потоков, прежде чем исчерпание ресурсов блокирует меня, поэтому мне нужно будет профилировать запущенные процессы с большим количеством потоков, чтобы найти оптимальный диапазон. – seawolf

2

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

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

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

+1

В этом случае это неверно. Если я запускаю один поток и предполагаю 3-секундный поворот для каждого запроса (они составляют около 3, со стандартным отклонением около 1 секунды), что делает 65 000 запросов занятыми примерно 54 * часа *. Запуск моей программы с 1024 потоками занимает около 20 минут. Меньше абсолютно * не * лучше в описанных условиях. – seawolf

+0

Я перефразирую это, чтобы сказать, что ответ «вводит в заблуждение *. У меня есть доказуемый случай, когда увеличение количества потоков радикально сокращает общее время завершения работы. Другим подходом будет использование асинхронного ввода-вывода, которое (если я правильно понимаю) может иметь сходное или большее влияние на производительность.Однако с точки зрения простоты потоки быстрее запускаются и работают, и во многих случаях подходят для работы такого типа, но меньшего масштаба. – seawolf

+0

Хорошо, я достаточно скромен, чтобы сказать, что неправильно понял ваш пост и согласен с тем, что вы получаете. В будущем это поможет, если вы включите пример стратегии замены. Как указано в [этом ответе] (http://stackoverflow.com/a/19565450/1886109), актеры представляют собой концептуально легкие потоки, и если я смогу использовать эту или какую-то аналогичную модель параллелизма, то соглашусь, истинные потоки переполнены. «Концептуально» - ключевое слово здесь, мне нужен какой-то параллелизм, а потоки - один разумный способ достичь этого, просто не в том масштабе, который мне нужен. – seawolf

1

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

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

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

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

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

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

+0

Я только начал читать о NIO и селекторах, и похоже, что это, вероятно, правильный путь для того, что я делаю. Благодаря! Какие-либо конкретные готовые библиотеки, с которыми вы работали, и можете рекомендовать расследование? – seawolf

+0

Я не сталкивался с библиотекой, которую хотел бы, поэтому я начал свой собственный по адресу https://github.com/rfqu/pipeline/tree/master/pipeline-nio. По крайней мере, он компактен и понятен. –

Смежные вопросы