Занятый поворот, чтобы уменьшить задержку переключения контекста (java)

В моем приложении есть несколько служб, которые обрабатывают информацию в своем потоке, когда они сделаны, они отправляют сообщение следующей службе, которая затем продолжает выполнять свою работу в своем потоке. Передача сообщений осуществляется через LinkedBlockingQueue. Обычно передача обслуживания занимает 50-80 секунд (от ввода сообщения в очередь до тех пор, пока потребитель не начнет обрабатывать сообщение). Чтобы ускорить передачу обслуживания в наиболее важных службах, я хотел использовать занятый спин вместо блокирующего подхода (у меня есть 12 процессорных ядер и вы хотите посвятить 3 этим важным сервисам). Итак, я изменил LinkedBlockingQueue на ConcurrentLinkedQueue

и сделал

for(;;)
{
 Message m = queue.poll();
 if( m != null )
  ....
}

Теперь... результат состоит в том, что первое сообщение проходит 1 us, но тогда латентность увеличивается в течение следующих 25 передач до тех пор, пока не достигнет 500 us, а затем латентность внезапно возвращается к 1 us и начинает увеличиваться. У меня есть латентные циклы с 25 итерациями, где латентность начинается с 1 us и заканчивается на 500 us. (сообщение передается приблизительно 100 раз в секунду)

со средней задержкой в ​​250 баллов это не совсем то, что я искал.

Я также пытался использовать кольцевой буфера LMAX Disruptor вместо ConcurrentLinkedQueue. У этой фреймворка есть своя собственная сборка в занятой спиновой реализации и совсем другая реализация очереди, но результат был тот же. Так что я совершенно уверен, что это не вина в очереди или меня что-то злоупотребляю..

Вопрос.. Что здесь происходит? Почему я вижу эти странные латентные циклы?

Ура!!

Ответ 1

Насколько я знаю, планировщик потоков может преднамеренно приостанавливать поток в течение более длительного времени, если обнаруживает, что этот поток использует CPU довольно интенсивно - распределять время процессора между разными потоками. Попробуйте добавить LockSupport.park() в пользователя после того, как очередь пуста и LockSupport.unpark() в продюсере после добавления сообщения - это может сделать задержку менее переменной; будет ли это на самом деле лучше сравнивать с блокировкой очереди является большой вопрос, хотя.

Ответ 2

Если вам действительно нужно делать работу так, как вы описали (а не то, как ответил Андрей Нудко в 5 января в 13:22), то вам определенно нужно искать проблему и с других точек зрения.

Только некоторые подсказки:

Ответ 3

Это просто дикая спекуляция (поскольку, как отмечали другие, вы не собираете никакой информации о длине очереди, неудачных опросах, нулевых опросах и т.д.):

Я использовал силу и прочитал источник ConcurrentLinkedQueue или, скорее, кратко просмотрел его через минуту или две. Опрос - не совсем ваша тривиальная операция O (1). Возможно, вы пройдете более чем несколько узлов, которые стали устаревшими, оставив нуль; и могут быть дополнительные переходные состояния, связанные узлами, связывающими себя как следующий node как указание на стойкость/удаление из очереди. Возможно, очередь запускает сбор мусора из-за планирования потоков. Попробуйте выполнить ссылки на абстрактный алгоритм, упомянутый в коде:

Простая, быстрая и практичная неблокирующая и блокирующая параллельная очередь от Maged M. Michael и Michael L Скотт (ссылка имеет PDF и псевдокод).

Ответ 4

Вот мои 2 цента. Если вы работаете в системах на основе linux/unix, существует способ посвятить определенный процессор определенному потоку. По сути, вы можете заставить ОС игнорировать этот процессор для любого планирования. Оформить уровни изоляции для процессора