Проблема производительности 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 

Пакетирование может повысить производительность на порядок.