我使用Disruptor框架对某些数据执行快速Reed-Solomon纠错。这是我的设置:
RS Decoder 1
/ \
Producer- ... - Consumer
\ /
RS Decoder 8
在disruptor DSL术语中,设置如下:
RsFrameEventHandler[] rsWorkers = new RsFrameEventHandler[numRsWorkers];
for (int i = 0; i < numRsWorkers; i++) {
rsWorkers[i] = new RsFrameEventHandler(numRsWorkers, i);
}
disruptor.handleEventsWith(rsWorkers)
.then(writerHandler);
当我没有磁盘输出消费者(没有.then(writerHandler)
部分)时,一旦添加消费者,测量的吞吐量就是80 M / s,即使它写入{{1或者甚至不写,但它被声明为依赖消费者,性能下降到50-65 M / s。
我已经使用Oracle Mission Control对其进行了分析,这就是CPU使用率图表所示:
没有额外的消费者:
增加一位消费者:
图中的灰色部分是什么?它来自哪里?我想它与线程同步有关,但我无法在Mission Control中找到任何其他统计信息来指示任何此类延迟或争用。
答案 0 :(得分:3)
您的假设是正确的,这是一个线程同步问题。
来自API Documentation for EventHandlerGroup<T>.then
(强调我的)
设置批处理程序以使用环形缓冲区中的事件。这些处理程序只会在此组中的每个
EventProcessor
处理完事件后处理事件。此方法通常用作链的一部分。例如,如果处理程序A必须在处理程序B之前处理事件:
这必然会降低吞吐量。想想它就像一个漏斗:
消费者必须wait才能完成每个EventProcessor
,然后才能完成瓶颈。
答案 1 :(得分:2)
根据你所展示的内容,我可以在这看到两种可能性。您可能会受到其中一个或两个的影响,我建议您同时测试它们。 1)IO处理瓶颈。 2)写入缓冲区的多个线程的争用。
IO处理
根据显示的数据,您已声明只要启用IO组件,吞吐量就会降低,内核时间也会增加。在您的消费者线程正在编写时,这很容易成为IO等待时间。执行write()
调用的上下文切换比什么都不做要贵得多。您的Decoder
现在的上限是消费者的最高速度。要测试此假设,您可以删除write()
来电。换句话说,打开输出文件,准备输出字符串,然后不发出写调用。
建议
write()
调用,看看它是否缩短了内核时间。endOfBatch
标志,然后一次性写入)以确保尽可能高效地捆绑IO?多个作家的争用
根据你的描述,我怀疑你的Decoder
正在从破坏者那里读取,然后写回到同一个缓冲区。这将导致多个写入器的问题,即写入内存的CPU上的争用。我建议的一件事是有两个破坏者戒指:
Producer
写入#1 Decoder
从#1读取,执行RS解码并将结果写入#2 Consumer
从#2读取,并写入磁盘假设您的RB足够大,这将导致通过内存的良好清洁行走。
这里的关键是没有Decoder
个线程(可能在不同的核心上运行)写入Producer
所拥有的同一个内存。只有2个内核执行此操作,除非磁盘速度是瓶颈,否则您可能会看到吞吐量提高。
我在这里有一篇博客文章,它更详细地描述了如何实现这一目标,包括示例代码。 http://fasterjava.blogspot.com.au/2013/04/disruptor-example-udp-echo-service-with.html
其他想法
WaitStrategy
,机器中有多少物理CPU等也是有帮助的。WaitStrategy
来显着降低CPU利用率。