给定一个同步程序(Minecraft),该程序需要在世界生成时处理x个照明更新。一种提高性能的技术是修补Minecraft,使其排入照明更新队列,然后异步处理它们,从队列中删除所有重复的计划照明更新。
这将提供一些重复删除,并在接下来的几个滴答周期内分配世界范围内的负载。
该队列当前是无限制的,并且在重复位置检查之前,可能会由于队列增长到可笑的大小而导致性能不佳或内存泄漏。
如果队列增长快于正在处理的队列,您该怎么办才能解决内存泄漏的问题?是否有技术可以迫使线程获得更多时间,或者阻塞直到队列的长度不足?我们无法提供背压,因为这会导致丢失灯光更新,而不必重新安排。在有希望的处理之前潜在地具有最大排队时间也可能是理想的。
我本可以提供代码,以更抽象的术语询问,但我不确定具体细节。
最佳答案
您不能像这样放慢线程的速度,除非将sleep语句放入其中。您可以使用线程优先级,但这只是更改了线程调度的顺序。这可能会减慢线程的速度,但前提是只有优先级更高的线程占用了所有运行时间。
您需要的是某种工作量调节器,而不是工作率调节器。也就是说,尝试仅执行底层计算机可以执行的工作。您可能要看一下ZeroMQ:这将是一个巨大的体系结构更改(一开始是异步的!),但请稍等一下。
用ZeroMQ编程是Actor模型;线程通过套接字进行通信,套接字就像消息队列一样。这样做的好处是,除了简单的点对点链接外,还有更多的方式可以使用这些套接字。也有模式。例如,您可能有8个线程进行照明。您可以通过一个PUSH/PULL socket 向他们发送照明请求;一个可用的消息将接收并处理该消息。您还将配置该套接字,以便如果达到高水位标记(照明线程不跟上),则新的照明请求消息将取代队列中的旧消息(照明线程仅处理实际能够处理的尽可能多的消息,有偏差朝着最新的方向发展)。
套接字也可以通过网络连接。您几乎可以在没有任何代码更改的情况下将其分发到多台计算机上。
我的这个建议存在一些问题:您将大量地序列化和反序列化对象。您将要拆除现有的体系结构。但是您将利用一个可传播,扩展并已经以一种有用的方式进行负载平衡和队列管理的通信框架。尝试会很有趣!
编辑以回复评论
我能想到的一件事是在照明请求中添加一个提交时间字段,并让照明线程计算从每个请求的初始提交到它们完成处理之间的时间。该执行时间度量可用于更新所有照明线程的平均完成时间值(将其放入互斥量保护的共享内存中,或类似的东西)。
然后,提交照明请求的对象可以引用该平均值,并且如果该数量增加,则应该降低速率。同样,如果平均处理时间减少,则可以提高处理速度。如果计算机本身开始忙于在后台执行其他操作,则这将适应执行时间的短缺。
实际上,这是在应用程序中构建照明处理的配置文件,将其反馈给提交者,并在执行过程中了解每秒可以提交多少个照明请求,而不会落在后面。
这将产生确保队列本身不会太满并且无限期增长的副作用。它并没有将队列限制为任何特定的长度,但是它限制了队列的前部和后部之间有多少时间。因此,在速度较快的计算机上,队列平均会更长,而在速度较慢的计算机上,队列会更短。
CPU中的热管理
当然,如今,“快速”和“慢速”计算机的定义有些复杂。如果现代Intel/AMD CPU(以及许多ARM)也确定需要大量的运行时间,则它将提高其时钟频率。因此,随着CPU确定需要Turbo模式,提高时钟速率,事情运行得更快,可以维持的照明请求速率将不断提高。因此,通过适应在达到时间目标的同时可以保持的速率(即,使平均完成时间保持合理),您的代码将利用CPU的提高的速度来适应您要求的工作负载做。
不过,您将需要小心一点。改变时钟速度不是瞬时的-我已经看到300ms的延迟,在此期间,整台机器上的任何地方实际上什么都没发生-当它们发生时,看起来一切突然变长了300ms。这就是为什么需要“平均”完成时间的原因-它可以消除测量中的扰动。
获取平均长度的长度也很重要,否则,您将陷入代码对CPU的需求和CPU时钟速率波动的情况:
此外,即使平均完成时间已达到稳定,您也将要增加工作量。如果可能的话,这样做会插入CPU提升其时钟频率,并且可以执行更多的照明请求,而不会影响完成时间。
基本上,您希望增加工作量,并且仅在延迟确实增加的情况下才减少工作量,但是这样做的时间范围要比CPU自身的热管理/时钟管理周期慢。
关于java - 慢速线程处理的缓解技术,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45993685/