减小Java线程上下文切换开销

11

我有一个Java应用运行在Sun 1.6 32位VM/Solaris 10 (x86)/Nahelem 8核(每核2线程)上。

应用中的一个具体用例是响应一些外部消息。在我的性能测试环境中,当我在接收外部输入的同一线程中准备并发送响应时,比将消息移交给单独的线程来发送响应要快约50微秒。我使用了ThreadPoolExecutorSynchronousQueue来完成移交。

根据您的经验,在将任务调度到线程池并被执行之间预期的延迟是多少?过去,您尝试过哪些方法来改善这种情况?

4个回答

13

"可接受的延迟"完全取决于您的应用程序。如果您有非常严格的延迟要求,那么在同一个线程上处理所有内容确实可以帮助。幸运的是,大多数应用程序的要求并没有那么严格。

当然,如果只有一个线程能够接收请求,那么将该线程绑定以计算响应意味着您无法接受任何其他请求。根据您所做的工作,您可以使用异步IO等技术来避免"每个请求一个线程"的模型,但我认为这要难得多,并且仍然涉及线程上下文切换。

有时候,为了避免有太多线程处理请求,排队请求是合适的选择:如果您的处理过程是CPU密集型的,那么拥有数百个线程就没有太多意义——最好是有一个生产者/消费者任务队列,大约分配一个任务给每个核心的线程。当然,如果正确设置了ThreadPoolExecutor,那么它基本上就是这样做的。如果您的请求大量时间都在等待外部服务(包括磁盘,但主要是其他网络服务)...此时,您需要在潜在的阻塞调用中使用异步执行模型,或者承担线程上下文切换的负担并拥有大量线程,依靠线程调度程序使其足够高效。

总之,延迟要求可能很困难-根据我的经验,它们比吞吐量要求更难以扩展。但这确实取决于具体情况。


谢谢!我的意思是,“期望”而不是“可接受”。我想知道50微秒是否太大,或者它可以降低。是的,我想尽快释放接收线程,以便能够接收下一条消息。此外,我的请求处理受CPU限制。 - Binil Thomas

2

在我的经验中(Solaris 10/Opteron),50微秒对于移交来说有点高,LBQ通常在30-35微秒的范围内,而LTQ(LinkedTransferQueue)比这还要快大约5微秒。正如其他回复所述,SynchronousQueue可能会稍慢一些,因为提供不会返回直到另一个线程已经取走。

根据我的结果,在这方面Solaris 10明显比Linux慢,Linux的时间少于10微秒。

这实际上取决于几件事,在峰值负载下:

  • 您每秒服务多少个请求?
  • 处理请求通常需要多长时间?

如果您知道这些问题的答案,那么基于性能考虑,您应该清楚是否应该在接收线程中处理或移交给处理线程。


1

你为什么不使用LinkedBlockingQueue让生产者可以排队几个项目,而不是使用SynchronousQueue?至少有一个包含1个项目的队列,这样您可以获得更好的并行性。

"准备"过程与"响应"过程的速度如何?如果它们太昂贵,您可以使用线程池来处理多个线程的响应吗?


0

不是同一个任务,但是 "是" - 队列过于普遍而不能用于时间关键任务。我们已经集中精力避免同步以处理所有事件。请查看以下提示

  • 不要使用同步容器(数组,列表,映射等)。考虑每个线程的容器。
  • 我们使用了循环池线程。这个池由预先分配的线程组成,并且(!)正好有一个监听事件出现而没有任何队列。当事件被触发时,线程从轮询中删除,另一个线程成为侦听器。处理完成后,线程返回池。

我认为为每个传入事件创建一个新线程并不是最好的方式。我更愿意有一个固定的线程池(甚至可以进行扩展),它可以处理由一个或多个监听器线程放入列表中的传入事件。 - RecursiveExceptionException
“为每个传入事件创建一个新线程是最好的方式”——你在我的帖子中看到了这个吗?我写的是:“循环线程池”? - Dewfy
哦,我看错了。只看到“删除线程”这个词,就想:“是啊,这不好” :p - RecursiveExceptionException

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