Проблема производительности Java с LinkedBlockingQueue
Это мой первый пост в stackoverflow... Я надеюсь, что кто-то может мне помочь
У меня большая регрессия производительности с Java 6 LinkedBlockingQueue
.
В первом потоке я генерирую некоторые объекты, которые я вставляю в очередь
Во втором потоке я вытаскиваю эти объекты. Регрессия производительности возникает, когда часто используется метод take()
LinkedBlockingQueue
.
Я отслеживал всю программу, а метод take()
требовал больше всего времени.
И пропускная способность идет от ~ 58Mb/s до 0.9Mb/s...
очередь поп и взять методы ar, вызванные статическим методом из этого класса
public class C_myMessageQueue {
private static final LinkedBlockingQueue<C_myMessageObject> x_queue = new LinkedBlockingQueue<C_myMessageObject>( 50000 );
/**
* @param message
* @throws InterruptedException
* @throws NullPointerException
*/
public static void addMyMessage( C_myMessageObject message )
throws InterruptedException, NullPointerException {
x_queue.put( message );
}
/**
* @return Die erste message der MesseageQueue
* @throws InterruptedException
*/
public static C_myMessageObject getMyMessage() throws InterruptedException {
return x_queue.take();
}
}
как я могу настроить метод take()
для достижения не менее 25 Мбит/с, или есть другой класс, который я могу использовать, который будет блокироваться, когда "очередь" заполнена или пуста.
С уважением
Барта
P.S.: Извините за мой плохой английский, я из Германии;)
Ответы
Ответ 1
Ваш поток производителя просто помещает больше элементов, чем потребитель потребляет, поэтому очередь в конечном итоге достигает предела емкости, поэтому продюсер ждет.
Консолидация моего первоначального ответа, так как теперь у нас есть в основном полная картина:
- Вы попадаете в собственный предел пропускной способности
LinkedBlockingQueue
(каждая очередь имеет один), делая чрезвычайно быстрые put()
s, где даже непрерывный take()s
с нулевой дополнительной обработкой не может идти в ногу. (Кстати, это показывает, что в этой структуре, на вашей JVM и машине все равно put() s по крайней мере немного дороже, чем чтение).
- Поскольку существует определенная блокировка, которую блокируют потребители, добавление большего количества потребительских потоков не помогло бы (если бы ваш потребитель фактически выполнял некоторую обработку, и это ограничивало пропускную способность, то добавление большего количества потребителей помогло бы. сценарий с несколькими потребителями (или производителями), вы можете попробовать
SynchronousQueue
, ConcurrentLinkedQueue
и предстоящий TransferQueue
jsr166y).
Некоторые предложения:
- Попробуйте сделать более крупнозернистые объекты, чтобы накладные расходы на очереди были сбалансированы с фактической работой, которая выгружается из производственного потока (в вашем случае кажется, что вы создаете большие служебные накладные расходы для объектов, которые представляют незначительные суммы работы)
- Вы также можете заставить производителя помочь потребителю, выгрузив часть потребляющей работы (не так много ожиданий, когда нужно выполнить работу).
/обновленный после того, как Джон У. справедливо указал, что мой первоначальный ответ был введен в заблуждение
Ответ 2
Я бы вообще не рекомендовал использовать LinkedBlockingQueue в области, чувствительной к производительности, использовать ArrayBlockingQueue. Это даст более приятный профиль сбора мусора и будет более дружественным к кешу, чем LinkedBlockingQueue.
Попробуйте ArrayBlockingQueue и измерьте производительность.
Единственное преимущество LinkedBlockingQueue в том, что оно может быть неограниченным, однако это редко то, что вы на самом деле хотите. Если у вас есть случай, когда потребитель терпит неудачу, и очереди начинают резервное копирование, наличие ограниченных очередей позволяет системе грамотно деградировать, а скорее подвергать опасности OutOfMemoryErrors, которые могут возникнуть, если очереди не ограничены.
Ответ 3
Вот несколько вещей, чтобы попробовать:
Замените LinkedBlockingQueue
на ArrayBlockingQueue
. У этого нет никаких болтающихся ссылок, и поэтому лучше вести себя, когда очередь заполняется. В частности, учитывая 1.6 реализацию LinkedBlockingQueue, полный GC элементов не будет происходить, пока очередь фактически не станет пустой.
Если сторона-производитель постоянно выходит за пределы потребительской стороны, рассмотрите возможность использования drain
или drainTo
для выполнения операции "массового".
В качестве альтернативы, нужно, чтобы очереди принимали массивы или списки объектов сообщений. Производитель заполняет список или массив объектами сообщений, и каждый поместить или принять перемещает несколько сообщений с одинаковыми накладными расходами. Подумайте об этом как о секретаре, который передал вам стопку "Пока вы были", и передал их вам по одному.
Ответ 4
Трудно сказать, что происходит, не зная о процессе наполнения.
Если addMyMessage
вызывается реже - возможно, из-за проблемы с производительностью во всей другой части вашего приложения - метод take
должен ждать.
Таким образом, похоже, что take
является виновником, но на самом деле это заполняющая часть вашего приложения.
Ответ 5
Нашел этот интересный пост о проблемах с производительностью из-за размера очереди и сбора мусора.
Ответ 6
Возможно, что на ваше приложение влияют связанные с блокировкой изменения в Java 6, особенно функция "смещенная блокировка".
Попробуйте отключить его с помощью переключателя -XX:-UseBiasedLocking
и посмотрите, не изменилось ли это.
См. дополнительную информацию: http://java.sun.com/performance/reference/whitepapers/6_performance.html
Ответ 7
Не могу сказать ничего точно. Но вы можете попытаться изменить реализацию BlockingQueue
(как эксперимент).
Вы устанавливаете начальную емкость 50k и используете LinkedBlockingQueue
. Попробуйте ArrayBlockingQueue
с той же емкостью, вы также можете играть с параметром fair
.
Ответ 8
Если исходные затраты на создание и удаление объекта из ваших блокирующих объектов - это ваше узкое место (а не проблема медленного производителя/потребителя), вы можете получить огромные улучшения производительности при пакетной обработке объектов: например, вместо того, чтобы помещать или принимать мелкозернистые объекты, вы кладете или берете крупнозернистые списки объектов. Вот фрагмент кода:
ArrayBlockingQueue<List<Object>> Q = new ArrayBlockingQueue<List<Object>>();
// producer side
List<Object> l = new ArrayList<Object>();
for (int i=0; i<100; i++) {
l.add(i); // your initialization here
}
Q.put(l);
// consumer side
List<Object> l2 = Q.take();
// do something
Пакетирование может повысить производительность на порядок.