什么原因导致了性能下降?

11

我正在使用Disruptor框架对一些数据执行快速的Reed-Solomon纠错。这是我的设置:

          RS Decoder 1
        /             \
Producer-     ...     - Consumer
        \             /
          RS Decoder 8 
  • 生产者从磁盘读取2064字节大小的块到缓冲区。
  • 8个RS解码器消费者同时进行Reed-Solomon纠错。
  • 消费者将文件写入磁盘。

在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,一旦我添加一个消费者,即使它写入到 /dev/null 中,或者甚至不写入,但被声明为一个依赖的消费者,性能下降到50-65 M/s。
我用Oracle Mission Control进行了分析,这是CPU使用率图显示的内容:
没有额外消费者: Without an additional consumer 有额外的消费者: With additional consumer 这个图表中的灰色部分是什么?它是从哪里来的?我想这可能与线程同步有关,但我在Mission Control中找不到任何其他统计数据来指示任何这样的延迟或争用。

这取决于您使用的工具。它假定打开文件描述符或在那条线上,这意味着应用程序+内核。 - SMA
@SMA您可以详细说明一下吗?您是说打开的文件描述符正在使用高达20%的CPU吗? - Zoltán
1
我不熟悉这个框架,但是这可能是由于.then()方法轮询以查看工作是否完成引起的吗? - fge
2个回答

3

你的假设是正确的,这是一个线程同步问题。

根据EventHandlerGroup<T>.then API文档(我强调了一下):

设置批处理程序来消费环形缓冲区中的事件。这些处理程序只会在该组中每个EventProcessor处理完事件后才处理事件。

此方法通常作为链的一部分使用。例如,如果处理程序A必须在处理程序B之前处理事件:

这必然会降低吞吐量。把它想象成漏斗:

Event Funnel

在通过瓶颈之前,消费者必须等待每个EventProcessor完成。


问题在于,尽管我似乎有8个RS解码器处理程序,但实际上只有一个处理程序处理一个事件,其他处理程序只是将它们传递。这就是我实现并行处理的方式。我按照此处https://github.com/LMAX-Exchange/disruptor/wiki/Frequently-Asked-Questions中“如何安排具有多个消费者的Disruptor,以便每个事件仅被消费一次?”的答案进行操作。 - Zoltán
@Zoltán 没关系,当它将事件从一个处理器传递到另一个处理器时,仍然会有一个块等待。 - durron597
我的怀疑是Consumer速度较慢,因此RB正在填充并导致生产者等待向RB写入。虽然消费者在等待每个EventProcessor完成,但从您的解释中我不清楚消费者等待如何减慢EventProcessors。 - jasonk

2
我可以看到两种可能性,基于你所展示的内容。你可能会受到其中一种或两种影响,我建议测试一下两种情况。 1)IO处理瓶颈。 2)多个线程写入缓冲区时的争用。
IO处理
从所展示的数据来看,你已经说明了当你启用IO组件后,吞吐量就会降低,内核时间增加。这很可能是在消费者线程写入时的IO等待时间。执行write()调用的上下文切换比什么都不做要显著地更加昂贵。你的解码器现在被限制为消费者的最大速度。为了测试这个假设,你可以删除write()调用。换句话说,打开输出文件,准备好输出字符串,然后只是不发出写入调用。
建议
- 尝试在消费者中删除write()调用,看看是否减少了内核时间。 - 如果没有,尝试将其顺序写入单个平面文件。 - 你是否使用了智能批处理(即:缓冲直到endOfBatch标志,然后在单个批处理中写入),以确保IO尽可能有效地捆绑在一起?
多个作者的争议
根据您的描述,我怀疑您的解码器正在从 disruptor 中读取,然后写回同一个缓冲区。这将导致多个作者即 CPU 写入内存时出现问题。我建议使用两个 disruptor 环:
1. 生产者写入 #1 2. 解码器从 #1 读取,执行 RS 解码并将结果写入 #2 3. 消费者从 #2 读取,并写入磁盘
假设您的 RB 足够大,这应该会产生良好的内存访问效果。
关键在于不让解码器线程(可能在不同的核心上运行)写入刚刚由生产者拥有的相同内存。只要有两个核心这样做,除非磁盘速度是瓶颈,否则您可能会看到提高吞吐量的效果。
我在这里有一篇博客文章,其中详细描述了如何实现此目标,包括示例代码。http://fasterjava.blogspot.com.au/2013/04/disruptor-example-udp-echo-service-with.html 其他想法:
- 了解您正在使用的 WaitStrategy,机器中有多少物理 CPU 等信息也会很有帮助。 - 假设您的最大延迟是 IO 写入,通过切换到不同的 WaitStrategy,您应该能够显著减少 CPU 利用率。 - 假设您使用的是相当新的硬件,只有这种设置才能使 IO 设备饱和。 - 您还需要确保文件位于不同的物理设备上,以实现合理的性能。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接