问题 最小化Java线程上下文切换开销
我有一个在Sun 1.6 32位VM / Solaris 10(x86)/ Nahelem 8核(每个核心2个线程)上运行的Java应用程序。
应用程序中的特定用例是响应某些外部消息。在我的性能测试环境中,当我准备并在接收外部输入的同一线程中发送响应时,我获得了大约50美元的优势,而不是将消息传递给单独的线程来发送响应。我用了一个 ThreadPoolExecutor 用一个 SynchronousQueue 做切换。
在你的经历中是什么 接受 在将一个任务安排到一个线程池之间的预期延迟以及它被选中执行?过去有什么想法可以改善这一点?
7887
2018-05-28 05:57
起源
答案:
“可接受的延迟”完全取决于您的申请。如果您有非常严格的延迟要求,处理同一线程上的所有内容确实会有所帮助。幸运的是,大多数应用程序没有那么严格的要求。
当然,如果只有一个线程能够 接收 请求,然后绑定该线程以计算响应将意味着您不能接受任何其他请求。根据您正在做的事情,您可以使用异步IO(etc)来避免“每个请求的线程”模型,但它会更加困难IMO,并且仍然最终会进行线程上下文切换。
有时候排队请求是合适的,以避免过多的线程处理它们:如果你的处理是CPU限制的,那么拥有数百个线程没有多大意义 - 最好有一个生产者/消费者的任务队列并在每个核心大约一个线程。那基本上是什么 ThreadPoolExecutor 如果你正确地设置它会做的。如果您的请求花费大量时间等待外部服务(包括磁盘,但主要是其他网络服务),那么这也不会有效......此时您需要使用异步执行模型,无论何时您可能会创建一个使用阻塞调用进行核心空闲,或者你采用线程上下文切换命中并拥有大量线程,依靠线程调度程序使其运行良好。
最重要的是延迟要求可能很难 - 根据我的经验,它们比吞吐量要求更加困难,因为它们更难以扩展。它确实取决于上下文。
12
2018-05-28 06:08
谢谢!我的意思是,“预期”而不是“可接受”。我想知道50 us是否太大或是否可以降低。是的,我想尽快释放接收线程,以便能够接收下一条消息。此外,我的请求处理是CPU绑定的。 - Binil Thomas
答案:
“可接受的延迟”完全取决于您的申请。如果您有非常严格的延迟要求,处理同一线程上的所有内容确实会有所帮助。幸运的是,大多数应用程序没有那么严格的要求。
当然,如果只有一个线程能够 接收 请求,然后绑定该线程以计算响应将意味着您不能接受任何其他请求。根据您正在做的事情,您可以使用异步IO(etc)来避免“每个请求的线程”模型,但它会更加困难IMO,并且仍然最终会进行线程上下文切换。
有时候排队请求是合适的,以避免过多的线程处理它们:如果你的处理是CPU限制的,那么拥有数百个线程没有多大意义 - 最好有一个生产者/消费者的任务队列并在每个核心大约一个线程。那基本上是什么 ThreadPoolExecutor 如果你正确地设置它会做的。如果您的请求花费大量时间等待外部服务(包括磁盘,但主要是其他网络服务),那么这也不会有效......此时您需要使用异步执行模型,无论何时您可能会创建一个使用阻塞调用进行核心空闲,或者你采用线程上下文切换命中并拥有大量线程,依靠线程调度程序使其运行良好。
最重要的是延迟要求可能很难 - 根据我的经验,它们比吞吐量要求更加困难,因为它们更难以扩展。它确实取决于上下文。
12
2018-05-28 06:08
谢谢!我的意思是,“预期”而不是“可接受”。我想知道50 us是否太大或是否可以降低。是的,我想尽快释放接收线程,以便能够接收下一条消息。此外,我的请求处理是CPU绑定的。 - Binil Thomas
50us听起来有点高,IME(Solaris 10 / Opteron)LBQ通常在30-35us范围内,而LTQ(LinkedTransferQueue)比那快5us。如其他回复中所述 SynchronousQueue 可能会略微变慢,因为在另一个线程采取之前,报价不会返回。
根据我的结果,Solaris 10在这方面明显慢于Linux,其时间<10us。
在峰值负载下,它真的取决于一些事情
你服务的每秒请求数是多少?
处理请求通常需要多长时间?
如果您知道这些Q的答案,那么在性能方面,它应该是相当清楚的,您是应该处理接收线程还是切换到处理线程。
2
2018-06-10 07:45
有没有理由不使用 LinkedBlockingQueue 所以你的制作人可以排队几个项而不是一个 SynchronousQueue?至少有一个包含1个项目的队列,这样你就可以获得更好的并行性。
“准备”过程的速度与“响应”的速度是多少?您是否可以使用线程池让多个线程处理响应,如果它们太昂贵?
1
2018-06-02 22:12
不是同一个任务,但是“是” - 队列通常用于时间关键任务。我们集中精力避免同步处理事件。查看以下提示
不要使用同步容器(数组,列表,映射......)。想想每个线程的容器。
我们使用了循环线程池。此池由预先分配的线程组成,并且(!)只有一个侦听事件没有任何队列。当事件引发时,线程从循环中删除,另一个成为监听器。处理完成后,线程返回池中。
0
2018-05-28 06:07
我不认为为每个传入的事件创建一个新线程是最好的方法。我宁愿有一个固定的线程池(甚至可能是扩展)来处理由一个或多个侦听器线程放入列表的传入事件 - RecursiveExceptionException
“......每个传入事件的新线程是最好的方式” - 你在我的帖子中看到了这一点,而我写道:“......循环线程”? - Dewfy
哦。我误解了那个。只看到“删除线程”并思考:“是的,那不好”:p - RecursiveExceptionException