减少在VCL中花费的CPU时间

时间:2012-01-27 13:22:57

标签: delphi bitmap vcl

MIDI播放器尽可能精确地播放音符非常重要。我从来没有成功,总是责怪计时器(参见前一个帖子:How to prevent hints interrupting a timer)。最近我收购了ProDelphi并开始测量那段时间到底消耗了多少。结果非常令人惊讶,请参阅下面的示例代码。

procedure TClip_View.doMove (Sender: TObject; note, time, dure, max_time: Int32);
var x: Int32;
begin
{$IFDEF PROFILE}Profint.ProfStop; Try; Profint.ProfEnter(@self,1572 or $58B20000); {$ENDIF}
   Image.Picture.Bitmap.Canvas.Pen.Mode := pmNot;
   Image.Picture.Bitmap.Canvas.MoveTo (FPPos, 0);
   Image.Picture.Bitmap.Canvas.LineTo (FPPos, Image.Height);
   x := time * GPF.PpM div MIDI_Resolution;
   Image.Picture.Bitmap.Canvas.Pen.Mode := pmNot;
   Image.Picture.Bitmap.Canvas.MoveTo (x, 0);
   Image.Picture.Bitmap.Canvas.LineTo (x, Image.Height);
   FPPos := x;
//   Bevel.Left := time * GPF.PpM div MIDI_Resolution;
{$IFDEF PROFILE}finally; Profint.ProfExit(1572); end;{$ENDIF}
end; // doMove //

测量结果(在I​​ntel i7-920上没有调试代码,2,7Ghz):

  1. 95微秒的代码如图所示
  2. 除了现在注释掉的语句(Bevel.Left :=)之外,
  3. 5.609毫秒被注释掉
  4. 所有代码都被x := time * GPF.PpM div MIDI_Resolution;
  5. 替换时为0.056微秒

    只需移动Bevel就可以花费60倍于绘制Canvas的CPU。这让我感到惊讶。测量1的结果非常可听(除此之外还有更多),但不是2和3。我需要某种形式的反馈给用户,因为玩家现在正在处理,钢琴卷上的某种线是可接受的方式。在我永远不断寻求减少定时事件循环中的CPU周期时,我有一些问题:

    • 为什么要花费很长一段时间在斜面上移动?
    • 有没有办法减少比在位图上绘制更多的CPU周期?
    • 绘画时有没有办法减少闪烁?

3 个答案:

答案 0 :(得分:10)

您将无法改变世界,也无法改变VCL或Windows。我怀疑你要求那些人......

恕我直言,你应该更好地改变你的架构:

  • 声音处理应该在一个(或多个)分离的线程中,并且不应该完全链接到UI(例如,不要从它发送GDI消息);
  • UI刷新应使用分辨率为500毫秒的计时器(半秒刷新声音反应充分),而不是每次都有变化。

也就是说,音序器不会刷新UI,但UI会定期询问音序器的当前状态。这将是恕我直言更顺利。

回答您的确切问题:

  • “移动斜面”实际上是发送了几条GDI消息,渲染将由GDI堆栈(gdi32.dll)使用临时位图进行;
  • 尝试使用较小的位图,或尝试使用Direct X缓冲区映射;
  • 在您的TForm.OnCreate事件上尝试DoubleBuffered := true,或者使用一个专用组件(TPaintBox),其中包含整个组件内容的全局位图,类似于消息WM_ERASEBKGND

一些代码:

   procedure TMyPaintBox.WMEraseBkgnd(var Message: TWmEraseBkgnd);
   begin
     Message.Result := 1; // no erasing is necessary after this method call
   end;

答案 1 :(得分:3)

我觉得你的位图缓冲是错误的。移动剪辑时,根本不需要重新绘制剪辑。您可以尝试使用此剪辑组件结构:

TMidiClip = Class(TControl)
Private
 FBuffer: TBitmap;
 FGridPos: TPoint;
 FHasToRepaint: Boolean;
Public
  Procedure Paint; Override; // you only draw the bitmap on the control canvas
  Procedure Refresh; // you recompute the FBuffer.canvas
End;

更改某些属性(如“剪辑刻度长度”)时,将“FHasToRepaint”设置为true,但不更改“FGridPos”(网格上的位置)时。所以大多数时候,在你的Paint事件中,你只有你的FBuffer的副本。

实际上,这非常依赖于网格及其子项(剪辑)的设计。 我可能错了,但似乎你的设计在控件中分解不够:主网格应该是TControl,剪辑应该是TControl,甚至剪辑上的事件应该是一些TControls ......你只能定义一个通过这种方式强烈优化位图缓冲系统(又名“双缓冲”)。

关于定时器:你应该使用一个处理每个音频样本的音乐时钟,否则你的分辨率不够好。这样的时钟可以使用“Windows音频驱动程序”(mmsystem.pas)或“Asio”驱动程序(例如,在BASS,Delphi Asio Vst项目中有接口)来实现。

答案 2 :(得分:2)

到目前为止,解决此问题的最佳方法是阻止GUI消息队列干扰您的MIDI播放器。将MIDI播放器放在后台线程上,以便它可以在不中断主线程的情况下完成其工作。当然,这依赖于您在具有多个处理器的计算机上运行,​​但现在将其视为理所当然并非不合理。

根据您的评论判断,您的音频处理似乎被UI线程阻止。不要让这种情况发生,你的音频问题就会消失。如果您使用TThread.Synchronize之类的东西从音频线程触发VCL事件,那么将阻止UI线程。改为使用异步通信方法。

你建议的加速VCL的替代方案并不可行。您无法修改VCL,即使可以,也可能很容易成为底层Windows代码的瓶颈。