是什么导致了这种性能下降?
What causes this performance drop?
我正在使用 Disruptor 框架对某些数据执行快速 Reed-Solomon 纠错。这是我的设置:
RS Decoder 1
/ \
Producer- ... - Consumer
\ /
RS Decoder 8
- 生产者从磁盘读取 2064 字节的块到字节缓冲区。
- 8 个 RS 解码器消费者并行执行 Reed-Solomon 纠错。
- 消费者将文件写入磁盘。
在破坏者 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-65M/s.
我已经使用 Oracle Mission Control 对它进行了概要分析,CPU 使用情况图显示了以下内容:
没有额外的消费者:
多一个消费者:
图中这个灰色部分是什么,它来自哪里?我想这与线程同步有关,但我在 Mission Control 中找不到任何其他统计数据来指示任何此类延迟或争用。
你的假设是正确的,是线程同步问题。
来自API Documentation for EventHandlerGroup<T>.then
(强调我的)
Set up batch handlers to consume events from the ring buffer. These handlers will only process events after every EventProcessor
in this group has processed the event.
This method is generally used as part of a chain. For example if the handler A must process events before handler B:
这必然会降低吞吐量。把它想象成一个漏斗:
消费者必须 wait 每 EventProcessor
完成一次,才能继续通过瓶颈。
根据您展示的内容,我在这里看到两种可能性。您可能会受到其中之一或两者的影响,我建议您对两者都进行测试。
1)IO处理瓶颈。
2) 争用多个线程写入缓冲区。
IO处理
根据显示的数据,您已经声明一旦启用 IO 组件,您的吞吐量就会降低,内核时间会增加。这很容易成为您的消费者线程正在写入时的 IO 等待时间。执行 write()
调用的上下文切换比什么都不做要昂贵得多。您的 Decoder
现在以消费者的最大速度为上限。要检验这个假设,您可以删除 write()
调用。换句话说,打开输出文件,准备输出字符串,然后不发出写入调用。
建议
- 尝试删除 Consumer 中的
write()
调用,看看它是否减少了内核时间。
- 您是否按顺序写入单个平面文件 - 如果不是,试试这个
- 您是否正在使用智能批处理(即:缓冲直到
endOfBatch
标志,然后在单个批中写入)以确保尽可能有效地捆绑 IO?
多个作者争用
根据你的描述,我怀疑你的 Decoder
s 正在从干扰器读取,然后写回同一个缓冲区。这将引起多个作者的问题,也就是 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 等信息也会有所帮助。
- 考虑到最大的延迟将是 IO 写入,您应该能够通过移动到不同的
WaitStrategy
来显着降低 CPU 利用率。
- 假设您使用的是相当新的硬件,您应该能够仅使用此设置使 IO 设备饱和。
- 您还需要确保文件位于不同的物理设备上以实现合理的性能。
我正在使用 Disruptor 框架对某些数据执行快速 Reed-Solomon 纠错。这是我的设置:
RS Decoder 1
/ \
Producer- ... - Consumer
\ /
RS Decoder 8
- 生产者从磁盘读取 2064 字节的块到字节缓冲区。
- 8 个 RS 解码器消费者并行执行 Reed-Solomon 纠错。
- 消费者将文件写入磁盘。
在破坏者 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-65M/s.
我已经使用 Oracle Mission Control 对它进行了概要分析,CPU 使用情况图显示了以下内容:
没有额外的消费者:
多一个消费者:
图中这个灰色部分是什么,它来自哪里?我想这与线程同步有关,但我在 Mission Control 中找不到任何其他统计数据来指示任何此类延迟或争用。
你的假设是正确的,是线程同步问题。
来自API Documentation for EventHandlerGroup<T>.then
(强调我的)
Set up batch handlers to consume events from the ring buffer. These handlers will only process events after every
EventProcessor
in this group has processed the event.This method is generally used as part of a chain. For example if the handler A must process events before handler B:
这必然会降低吞吐量。把它想象成一个漏斗:
消费者必须 wait 每 EventProcessor
完成一次,才能继续通过瓶颈。
根据您展示的内容,我在这里看到两种可能性。您可能会受到其中之一或两者的影响,我建议您对两者都进行测试。 1)IO处理瓶颈。 2) 争用多个线程写入缓冲区。
IO处理
根据显示的数据,您已经声明一旦启用 IO 组件,您的吞吐量就会降低,内核时间会增加。这很容易成为您的消费者线程正在写入时的 IO 等待时间。执行 write()
调用的上下文切换比什么都不做要昂贵得多。您的 Decoder
现在以消费者的最大速度为上限。要检验这个假设,您可以删除 write()
调用。换句话说,打开输出文件,准备输出字符串,然后不发出写入调用。
建议
- 尝试删除 Consumer 中的
write()
调用,看看它是否减少了内核时间。 - 您是否按顺序写入单个平面文件 - 如果不是,试试这个
- 您是否正在使用智能批处理(即:缓冲直到
endOfBatch
标志,然后在单个批中写入)以确保尽可能有效地捆绑 IO?
多个作者争用
根据你的描述,我怀疑你的 Decoder
s 正在从干扰器读取,然后写回同一个缓冲区。这将引起多个作者的问题,也就是 CPU 写入内存的争用。我建议的一件事是有两个干扰环:
Producer
写入 #1Decoder
从#1读取,执行RS解码并将结果写入#2Consumer
从#2 读取,并写入磁盘
假设您的 RB 足够大,这应该可以很好地干净地遍历内存。
这里的关键是不让 Decoder
线程(可能 运行 在不同的核心上)写入刚刚由 Producer
拥有的相同内存。只有 2 个内核执行此操作,除非磁盘速度是瓶颈,否则您可能会看到吞吐量有所提高。
我在这里有一篇博客文章,其中更详细地描述了如何实现这一点,包括示例代码。 http://fasterjava.blogspot.com.au/2013/04/disruptor-example-udp-echo-service-with.html
其他想法
- 了解您正在使用的
WaitStrategy
、机器中有多少物理 CPU 等信息也会有所帮助。 - 考虑到最大的延迟将是 IO 写入,您应该能够通过移动到不同的
WaitStrategy
来显着降低 CPU 利用率。 - 假设您使用的是相当新的硬件,您应该能够仅使用此设置使 IO 设备饱和。
- 您还需要确保文件位于不同的物理设备上以实现合理的性能。