在后台线程中运行的代码可以比在Delphi中的主VCL线程中更快吗?

时间:2011-07-19 03:54:42

标签: multithreading performance delphi vcl timing

如果有人在主VCL线程和后台线程上运行时间代码有很多经验,我想得到一个意见。我有一些代码在主线程上的Delphi 6应用程序中运行一些繁重的字符串处理。每次运行操作时,每个操作的时间在i5 Quad内核的单个线程上徘徊大约50 ms。让我真正怀疑的是,我在旧Pentium 4上运行的相同代码显示了同样的操作时间,因为通常我看到Pentium 4上运行的代码比Quad Core慢4倍。我开始怀疑代码是否可能消耗的时间远远少于50毫秒,但是主要的VCL线程,可能是Windows消息处理或执行Windows API调用,正在为操作创建一个人为的“底层”。注意,如果重要的话,操作由套接字上的传入请求触发,但是在完全接收数据之前不会进行时间测量。

在我开始将所有代码移到后台线程进行测试之前,我想知道是否有人对此领域有任何一般知识?您在主VCL线程上运行代码的经历是什么?注意,当测试期间绝对没有用户触发的活动时,正在进行定时测量。

我也想知道将线程的优先级提高到低于实时性是否会有所帮助。在尝试使用这些标志时,我的运行时间从未见过太多改进。

- roschler

5 个答案:

答案 0 :(得分:12)

鉴于所有线程具有与通常相同的优先级,因此存在差异,原因如下。如果你看到了不同之处,请重新评估代码(确保你在VCL和后台线程中运行相同的东西)并确保你正确地计时:

  • 编译器生成完全相同的代码,它不关心代码是在主线程还是后台线程中运行。实际上,您可以将整个代码放在一个过程中,并从工作线程的Execute()和主VCL线程中调用它。

  • 对于CPU,所有核心和所有线程都是相同的。除非它实际上是一个超线程CPU,并非所有核心都是真实的,但是请看下一个子弹。

  • 即使并非所有CPU核心都相同,您的线程也不太可能在同一核心上运行 ,操作系统可以随意移动它(实际上会安排你的线程在不同的时间在不同的核心上运行。)

  • 对于主VCL线程,消息传递开销并不重要,因为除非您手动调用Application.ProcessMessages(),否则只需在您的过程执行操作时停止消息泵。消息泵是被动的,你的线程需要从队列中请求消息,但由于线程忙于完成你的工作,它不会请求任何消息,因此没有开销。

只有一个地方线程不相等,这可以改变感知的执行速度:操作系统将线程调度到执行单元(核心),并且操作系统线程具有不同的优先级。您可以告诉操作系统需要使用SetThreadPriority() API(TThread.Priority属性使用)来区别对待某个线程。

答案 1 :(得分:10)

如果没有简单的源代码来重现问题,以及如何为线程计时,则很难理解软件中发生的情况。

明显地听起来像:

  • 架构问题 - 您的线程是如何定义的?
  • 测量问题 - 您如何计算线程?
  • 内存管理器和RTL字符串相关实现的typical scaling issue

关于最新一点,请考虑一下:

  • 当前内存管理器(FastMM4)在多核CPU上无法正常扩展;尝试使用每线程内存管理器,例如our experimental SynScaleMM - 请注意,例如Free Pascal编译器团队最近从头开始编写了一个新的缩放MM,以避免这种问题;
  • 尝试更改字符串进程实现以避免内存分配(使用静态缓冲区)和字符串引用计数(每个字符串引用计数访问产生LOCK DEC/INC,这在多代码CPU上无法很好地扩展 - 使用每个线程的char级进程,在静态缓冲区而不是PChar上使用例如string

我确信没有string操作,你会发现所有线程都是等效的。

简而言之:既不是当前的Delphi MM,也不是当前的字符串实现在多核CPU上都能很好地扩展。您刚刚发现了当前RTL的已知问题。阅读this SO question

答案 2 :(得分:6)

当你的代码控制了VCL线程时,例如,如果它在一个方法中并且没有调用任何VCL控件或调用Application.ProcessMessages,那么运行时不会因为它在主要的VCL线程。

没有开销,因为当你在自己的代码中时,你“拥有”线程的整个处理能力。

我建议您使用分析工具来查找实际瓶颈的位置。

答案 3 :(得分:3)

无法静态评估表现。为此,您需要获得AQTime或Delphi的其他性能分析器。我使用AQtime,我喜欢它,但我知道它被认为是昂贵的。

您的代码不会因为将其移动到后台线程而神奇地变得更快。如果你需要通过一些同步机制将大量数据从后台线程发送到前台线程,那么直到你在UI中看到结果的全包时间才会变得有点慢。

但是,如果您可以并行执行部分算法,也就是说,拆分您的工作,以便您有2个或更多工作线程处理您的数据,并且您有一个四核处理器,那么您的总时间可以修复工作量可能会减少。这并不意味着代码运行得更快,但是根据很多因素,您可能会从多线程中获得一些好处,最多可以达到计算机中的核心数量。使用两个线程而不是一个线程永远不会成为2倍的性能提升,但是在多线程并行解决方案中,您的性能可能会提高20%-40%,具体取决于堆的可伸缩性。在多线程负载下,以及IO /内存/缓存如何绑定您的工作负载。

至于提高线程优先级,通常你所做的一切都会破坏Windows系统性能的微妙平衡。通过提高优先级,您将(有时)实现名义上但不可重复且不可保证的性能提升。根据您在代码和数据源中执行的其他操作,使用线程优先级可能会引入细微问题。有关详情,请参阅Dining Philosophers问题。

优化字符串操作速度的最佳选择是首先对其进行测试并找出其大部分时间的确切位置。是堆操作吗?内存复制和移动操作?如果没有分析器,即使有其他人的建议,你仍然会成为编程的主要罪魁祸首;过早优化。以结果为导向。以科学为基础。测量。了解。然后决定。

话虽如此,我在我的时代看到过很多可怕的代码,并且人们做的一件事就是杀死他们的线程应用程序性能;使用TThread.Synchronize太多了。

这是一个病态(极端)的情况,遗憾的是,这种情况经常在野外发生:

   procedure TMyThread.Execute;
   begin
       while not Terminated do 
         Synchronize(DoWork);
   end;

这里的问题是100%的工作实际上是在前台完成的,而不是在线程上下文中执行的“if terminated”检查。要使上述代码更糟糕,请添加一个不间断的睡眠。

对于快速后台线程代码,请谨慎使用Synchronize或者根本不使用Synchronize,并确保它调用的代码很简单并快速执行,或者更好,如果您真的可以使用排队主线程活动,请使用TThread.Queue或PostMessage

答案 4 :(得分:1)

您是否询问后台线程是否会更快?如果您的后台线程将运行与主线程相同的代码,并且主线程中没有其他任何内容,那么您无法获得后台线程的任何内容。应该使用线程来分割和分配处理负载,否则这些处理负载在主线程中运行时会彼此竞争和/或相互阻塞。由于您似乎正在处理主线程处于空闲状态的情况,因此简单地生成线程以运行慢速代码将无济于事。

线程不是魔术,它们无法加速慢速代码或消除与主线程上的争用无关的特定段中的处理瓶颈。确保您的代码没有做您不知道的事情,并且您的时序方法是正确的。

我的第一个预感将是您与套接字的交互以您未检测到的方式影响您的时间安排...(我知道您说您确定没有涉及 - 但是也许再次检查......)