Почему разрушитель медленнее с меньшим кольцевым буфером?

Следуя Руководству по началу работы с Disruptor, я создал минимальный разрушитель с одним производителем и одним потребителем.

Режиссер

import com.lmax.disruptor.RingBuffer;

public class LongEventProducer
{
    private final RingBuffer<LongEvent> ringBuffer;

    public LongEventProducer(RingBuffer<LongEvent> ringBuffer)
    {
        this.ringBuffer = ringBuffer;
    }

    public void onData()
    {
        long sequence = ringBuffer.next();
        try
        {
            LongEvent event = ringBuffer.get(sequence);
        }
        finally
        {
            ringBuffer.publish(sequence);
        }
    }
}

Потребитель (Обратите внимание, что потребитель ничего не делает onEvent)

import com.lmax.disruptor.EventHandler;

public class LongEventHandler implements EventHandler<LongEvent>
{
    public void onEvent(LongEvent event, long sequence, boolean endOfBatch)
    {}
}

Моя цель состояла в том, чтобы протестировать производительность обхода большого кольцевого буфера один раз вместо обхода меньшего кольца несколько раз. В каждом случае общее количество операций (bufferSize Икс rotations) та же. Я обнаружил, что скорость операций в секунду резко упала, когда кольцевой буфер стал меньше.

RingBuffer Size |  Revolutions  | Total Ops   |   Mops/sec

    1048576     |      1        |  1048576    |     50-60

       1024     |      1024     |  1048576    |     8-16

        64      |      16384    |  1048576    |    0.5-0.7

        8       |      131072   |  1048576    |    0.12-0.14

Вопрос: что является причиной значительного снижения производительности, когда размер кольцевого буфера уменьшается, но общее количество итераций фиксировано? Эта тенденция не зависит от WaitStrategy а также Single vs MultiProducer - пропускная способность снижается, но тенденция та же.

Главная (уведомление SingleProducer а также BusySpinWaitStrategy)

import com.lmax.disruptor.BusySpinWaitStrategy;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.dsl.ProducerType;

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

public class LongEventMainJava{
        static double ONEMILLION = 1000000.0;
        static double ONEBILLION = 1000000000.0;

    public static void main(String[] args) throws Exception {
            // Executor that will be used to construct new threads for consumers
            Executor executor = Executors.newCachedThreadPool();    

            // TUNABLE PARAMS
            int ringBufferSize = 1048576; // 1024, 64, 8
            int rotations = 1; // 1024, 16384, 131702

            // Construct the Disruptor
            Disruptor disruptor = new Disruptor<>(new LongEventFactory(), ringBufferSize, executor, ProducerType.SINGLE, new BusySpinWaitStrategy());

            // Connect the handler
            disruptor.handleEventsWith(new LongEventHandler());

            // Start the Disruptor, starts all threads running
            disruptor.start();

            // Get the ring buffer from the Disruptor to be used for publishing.
            RingBuffer<LongEvent> ringBuffer = disruptor.getRingBuffer();
            LongEventProducer producer = new LongEventProducer(ringBuffer);

            long start = System.nanoTime();
            long totalIterations = rotations * ringBufferSize;
            for (long i = 0; i < totalIterations; i++) {
                producer.onData();
            }
            double duration = (System.nanoTime()-start)/ONEBILLION;
            System.out.println(String.format("Buffersize: %s, rotations: %s, total iterations = %s, duration: %.2f seconds, rate: %.2f Mops/s",
                    ringBufferSize, rotations, totalIterations, duration, totalIterations/(ONEMILLION * duration)));
        }
}

И для запуска вам понадобится тривиальный код Factory

import com.lmax.disruptor.EventFactory;

public class LongEventFactory implements EventFactory<LongEvent>
{
    public LongEvent newInstance()
    {
        return new LongEvent();
    }
}

Работает на ядре i5-2400, 12 ГБ оперативной памяти, Windows 7

Пример вывода

Buffersize: 1048576, rotations: 1, total iterations = 1048576, duration: 0.02 seconds, rate: 59.03 Mops/s

Buffersize: 64, rotations: 16384, total iterations = 1048576, duration: 2.01 seconds, rate: 0.52 Mops/s

2 ответа

Решение

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

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

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

Похоже, проблема заключается в этом блоке кода в lmax\disruptor\SingleProducerSequencer

if (wrapPoint > cachedGatingSequence || cachedGatingSequence > nextValue)
        {
            cursor.setVolatile(nextValue);  // StoreLoad fence

            long minSequence;
            while (wrapPoint > (minSequence = Util.getMinimumSequence(gatingSequences, nextValue)))
            {
                waitStrategy.signalAllWhenBlocking();
                LockSupport.parkNanos(1L); // TODO: Use waitStrategy to spin?
            }

            this.cachedValue = minSequence;
        }

В частности, призыв к LockSupport.parkNanos(1L), Это может занять до 15 мс в Windows. Когда производитель достигает конца буфера и ожидает от потребителя, это вызывается.

Во-вторых, когда буфер мал, вероятно, происходит ложное совместное использование RingBuffer. Я предполагаю, что оба эти эффекта находятся в игре.

Наконец, я смог ускорить код с помощью JIT с миллионом вызовов onData() до бенчмаркинга. Это получило лучшее дело > 80Mops/sec, но не удаляла деградацию с усадкой буфера.

Другие вопросы по тегам