【问题标题】:(Array|Linked)BlockingQueue has good `offer` performance, but why is `take` way too slow?(Array|Linked)BlockingQueue 的 `offer` 性能不错,但为什么 `take` 太慢了?
【发布时间】:2012-09-13 14:31:27
【问题描述】:

我有一个 Clojure 网络应用,基本结构是这样的:

  • 服务器有一个 LinkedBlockingQueue 或 ArrayBlockingQueue(我都试过了)
  • 多个线程接受网络连接,offer 工作到队列中
  • 队列中的一个线程 take 在无限循环中处理每个获取的项目

我注意到take 调用存在严重的性能问题:

  • 线程以非常快的速度offering 到队列中,并且队列很快将它们全部带走
  • 一个工作线程take从队列中以非常慢的速度(比offer的速度慢200多倍)
  • CPU 使用率非常低 - 所以工作人员根本不忙

在不使用队列的情况下,在基准测试情况下,相同的工作负载能够最大限度地利用 CPU 并以令人满意的速度完成。

那么在这种情况下,最好的排队技术是什么?

这是我的代码(少于 100 行);

https://github.com/HouzuoGuo/Aurinko/blob/master/src/Aurinko/core.clj

编辑,我的观察细节:

  • 我对请求处理速度进行了基准测试,它在不使用队列的情况下以每秒大约 8,000 个请求的速度工作。
  • 我让服务器程序在排队请求时打印一条调试消息,并在完成处理请求时打印另一条消息。
  • 我制作了一个简单的客户端程序,每秒向服务器发送大约 1,000 个请求。
  • 服务器及时将所有请求排队,队列变成数千个元素。
  • 根据调试消息,Worker(请求处理器)似乎仅以每秒大约 150 个请求的速度工作。

编辑:

感谢大家的帮助。我已经确认阻塞队列不是导致性能问题的原因。虽然我没有发现我的应用程序的性能瓶颈,但一定有一个地方。

最终编辑:

谢谢大家。性能瓶颈是由网络 IO 而非阻塞队列引起的。

【问题讨论】:

  • 我不确定您是如何测量延迟的,因为我无法阅读 clojure 但 take() 会根据需要等待。当队列确实为空时,这似乎会使 take() 变慢。
  • 谢谢彼得。我知道.take 会阻塞直到元素可用。在我的场景中,有数千个请求排队(那些阻塞队列在排队请求时效果很好),但是与我在不使用队列的情况下处理负载的基准相比,.take 的工作速度太慢了。
  • 当你不使用队列时,这是唯一的改变还是你也在改变诸如如何使用线程之类的东西?
  • 我需要两个特性:移除队列头部并阻塞直到元素可用;可以安全地从多个线程中删除并添加到队列中。我认为blockingqueue满足这两个要求,但如果我要使用java.util.queue,我需要手动管理它们
  • 您能否演示一个简单的示例,其中使用队列会导致延迟,甚至是您所看到的延迟的一小部分,因为我不能,所以我不知道您为什么认为这是导致问题。

标签: java clojure


【解决方案1】:

您说:“CPU 使用率非常低 - 所以工作人员根本不忙”。您还说:“我已经确认阻塞队列不是导致性能问题的原因。虽然我没有发现我的应用程序中的性能瓶颈,但一定有一个地方。”

如果这两个陈述都是正确的,则可能是您的工作线程花费了大量时间等待 I/O。如果是这样,有一个简单的解决方案:运行多个工作线程!

或者可能是存在一些其他并发瓶颈(不是工作队列)。

您为什么不执行以下操作:编写一个小测试程序,将大约 1,000 个项目推送到工作队列中,然后开始运行在工作线程上运行的相同代码。当队列为空时,它应该退出。分析该程序。 (你的开发机器上设置了探查器吗?我喜欢使用 JIP。)

【讨论】:

  • 非常感谢。那天晚些时候,我做了一些其他的基准测试并使用 VisualVM 对其进行了分析,然后确认你是对的——我没有意识到网络连接是瓶颈。我只从一个客户端连接对服务器程序进行基准测试,在尝试使用 100 个并发客户端连接对其进行基准测试后,我得到了非常令人满意的吞吐量。经验教训。
  • 很高兴这对您有所帮助...如果网络延迟是瓶颈,您应该会看到运行更多工作线程带来的 BIG 性能提升! (顺便说一句...+1 将不胜感激。)
【解决方案2】:

您看到的最可能的解释是队列为空,导致take() 等待。如果队列不为空,它会非常快。

我假设在 clojure 中的性能类似于在 Java 中的性能。

public static void main(String... args) throws InterruptedException {
    int runs = 20000;
    BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(runs + 1);
    BlockingQueue<Integer> queue2 = new LinkedBlockingQueue<Integer>(runs + 1);
    for (int i = 0; i < 10; i++) {
        testQueue(runs, queue);
        testTake(runs, queue);
        testQueue(runs, queue2);
        testTake(runs, queue2);
    }
}

private static void testQueue(int runs, BlockingQueue<Integer> queue) {
    long start = System.nanoTime();
    for (int i = 0; i < runs; i++)
        queue.offer(1);
    long time = System.nanoTime() - start;
    System.out.printf(queue.getClass().getSimpleName() + ": Average time to offer was %,d ns%n", time / runs);
}

private static void testTake(int runs, BlockingQueue<Integer> queue) throws InterruptedException {
    long start = System.nanoTime();
    for (int i = 0; i < runs; i++)
        queue.take();
    long time = System.nanoTime() - start;
    System.out.printf(queue.getClass().getSimpleName() + ": Average time to take was %,d ns%n", time / runs);
}

终于打印出来了

ArrayBlockingQueue: Average time to offer was 34 ns
ArrayBlockingQueue: Average time to take was 39 ns
LinkedBlockingQueue: Average time to offer was 78 ns
LinkedBlockingQueue: Average time to take was 54 ns

【讨论】:

  • 非常感谢您提供的代码示例。在我的场景中,我以大约2,000 个元素/秒,我观察到 .take 加上处理元素的工作速度约为。 150 个元素/秒。我的基准测试显示我的应用程序每秒能够处理超过 8,000 个元素。所以我仍然想知道是否是.take 减慢了我的应用程序,如果是,为什么...
  • take 将使您的应用程序减慢大约 100 纳秒。但是,要使程序成为多线程,您通常必须进行其他更改,例如使数据线程安全或在以前不是线程之间共享。这可能会产生比其价值更多的开销。你速度变慢的原因就像你所做的其他改变一样。我会使用 CPU 分析器来确定根本原因。
【解决方案3】:

你确定 LinkedBlocingQueue 和 ArrayBlockingQueue 的结果是一样的吗?根据这两种数据结构的不同,从队列中提供和获取元素的效率是不同的。

【讨论】:

  • 结果相似。 LinkedBlockingQueue.take 总是有效但速度极慢,ArrayBlockingQueue.take 在开始时的工作速度比链接快,在获取数千个元素后完全停止工作,只有在极长的延迟(几分钟)后才能再次工作
  • 你可以添加一些调试代码来确保调用take()时队列不为空,因为调用take()时如果队列为空,程序会被阻塞。
  • 谢谢。是的,我确认队列有数千个可用元素,但 .take 只是需要太长时间才能返回它们。
  • 好的,那么您可以使用一些分析器工具来检查在 take() 方法中哪个语句占用了执行时间。 Java VisualVM 是一种选择
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-21
  • 2014-08-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-16
相关资源
最近更新 更多