Минимизация накладных расходов на переключение контекста Java-потока - PullRequest
11 голосов
/ 28 мая 2010

У меня есть приложение Java, работающее на Sun 1.6 32-битная VM / Solaris 10 (x86) / Nahelem 8-core (2 потока на ядро).

Конкретный вариант использования в приложении - ответить на какое-то внешнее сообщение. В моей среде тестирования производительности, когда я готовлю и отправляю ответ в том же потоке, который получает внешний вход, я получаю преимущество примерно в 50 раз по сравнению с тем, когда я передаю сообщение в отдельный поток для отправки ответа. Я использую ThreadPoolExecutor с SynchronousQueue для передачи обслуживания.

По вашему опыту, какова приемлемая ожидаемая задержка между планированием задачи в пул потоков и ее получением для выполнения? Какие идеи работали для вас в прошлом, чтобы попытаться улучшить это?

Ответы [ 4 ]

12 голосов
/ 28 мая 2010

«Приемлемая задержка» полностью зависит от вашего приложения. Работа со всем в одном потоке действительно может помочь, если у вас очень строгие требования к задержке. К счастью, большинство приложений не предъявляют столь строгих требований.

Конечно, если только один поток может получать запросов, то связывание этого потока для вычисления ответа будет означать, что вы не сможете принимать другие запросы. В зависимости от того, что вы делаете, вы можете использовать асинхронный ввод-вывод (и т. Д.), Чтобы избежать модели «поток на запрос», но это значительно сложнее IMO и все равно заканчивается переключением контекста потока.

Иногда целесообразно ставить запросы в очередь, чтобы избежать обработки слишком большим количеством потоков: если ваша обработка связана с ЦП, нет смысла иметь сотни потоков - лучше иметь очередь задач производителя и потребителя и распределите их примерно по одному потоку на ядро. Это в основном то, что ThreadPoolExecutor сделает, если вы, конечно, настроите его правильно. Это не сработает, если ваши запросы тратят много времени на ожидание внешних служб (включая диски, но в первую очередь другие сетевые службы) ... в этот момент вам нужно либо использовать модели асинхронного выполнения всякий раз, когда вы потенциально можете сделать ядро простаивает с блокирующим вызовом, или вы берете удар по переключению контекста потока и получаете множество потоков, полагаясь на планировщик потока, чтобы он работал достаточно хорошо.

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

2 голосов
/ 10 июня 2010

50us звучит несколько выше для передачи обслуживания, IME (Solaris 10 / Opteron) LBQ обычно находится в диапазоне 30-35us, в то время как LTQ (LinkedTransferQueue) примерно на 5us быстрее, чем это. Как указано в других ответах SynchronousQueue может иметь тенденцию быть немного медленнее, потому что предложение не возвращается, пока другой поток не принял.

Согласно моим результатам, Solaris 10 заметно медленнее, чем Linux, который видит время <10us. </p>

Это действительно зависит от нескольких вещей при пиковой нагрузке

  • сколько запросов в секунду вы обслуживаете?
  • сколько времени обычно занимает обработка запроса?

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

1 голос
/ 03 июня 2010

Есть ли причина, по которой вы не используете LinkedBlockingQueue, чтобы ваш продюсер мог поставить в очередь пару элементов вместо SynchronousQueue? По крайней мере, есть очередь с 1 элементом, чтобы вы могли получить лучший параллелизм.

Какова скорость процесса «подготовки» по сравнению с «ответом»? Можно ли использовать пул потоков, чтобы несколько потоков обрабатывали ответы, если они слишком дороги?

0 голосов
/ 28 мая 2010

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

  • Не используйте синхронизированные контейнеры (массивы, списки, карты ...). Подумайте о контейнере для потока.
  • Мы использовали циклический пул потоков. Этот пул состоит из предварительно выделенных потоков и (!) Ровно одного события прослушивания появляется без какой-либо очереди. При возникновении события поток удаляется из циклического перебора, а другой становится слушателем. Когда обработка завершена, поток возвращается в пул.
...