提高Delphi 2010自己的调试可视化器速度

9
我编写了一个Delphi调试可视化工具,用于显示TDataSet的当前行值,源码和屏幕截图可以在http://delphi.netcode.cz/text/tdataset-debug-visualizer.aspx中找到。虽然运行良好,但是非常慢。我已经优化过一些内容(如获取字段名称),但仍然需要10秒才能显示20个字段 - 这很糟糕。
主要问题似乎是主代码中使用的IOTAThread90.Evaluate速度缓慢,下面的代码中这个过程占用大部分时间,带有**的行约占80%的时间。FExpression是代码中TDataset的名称。
procedure TDataSetViewerFrame.mFillData;
var
 iCount: Integer;
 I: Integer;
 //  sw: TStopwatch;
 s: string;
 begin
 //  sw := TStopwatch.StartNew;
   iCount := StrToIntDef(Evaluate(FExpression+'.Fields.Count'), 0);
   for I := 0 to iCount - 1 do
   begin
     s:= s + Format('%s.Fields[%d].FieldName+'',''+', [FExpression, I]);
  //  FFields.Add(Evaluate(Format('%s.Fields[%d].FieldName', [FExpression, I])));
     FValues.Add(Evaluate(Format('%s.Fields[%d].Value', [FExpression, I]))); //**
   end;
 if s<> '' then
   Delete(s, length(s)-4, 5);
 s := Evaluate(s);
 s:= Copy(s, 2, Length(s) -2);
 FFields.CommaText := s;
{  sw.Stop;
 s := sw.Elapsed;
 Application.MessageBox(Pchar(s), '');}
end;

现在我不知道如何提高性能。


我不熟悉这些数据组件,因此不知道如何显著提高性能。但是,您的复制可以被删除替换,这可能更快。也许 s <> '' 可以被 length(s) = 0 替换。如果有任何性能提升,我不知道。然而,总的来说,我认为使用 = 和 <> 进行字符串比较比智能 SameText(s1, s2) 和 SameStr(s1, s2) 程序要慢。 - Andreas Rejbrand
@Andreas - 这些微小的优化可能不会对10秒延迟有很大的贡献。 - Barry Kelly
@Barry Kelly:我知道。这就是为什么我写下了“我不知道如何显着提高性能”。 - Andreas Rejbrand
"Length(s) <> 0" 实际上比 "S <> ''" 更慢,因为 Length() 是一个内联函数调用,但仍然比后者慢,因为编译器将后者转换为 "Pointer(S) <> nil"。 - Andreas Hausladen
3个回答

9
< p > Evaluate需要做出惊人的工作。编译器需要将其编译,将符号解析为内存地址,而评估属性可能会导致函数被调用,这需要调试器将参数复制到被调试程序中,设置堆栈帧,调用要调用的函数,收集结果-这涉及暂停和恢复被调试程序。< /p> < p > 我只能建议尝试在Evaluate调用中打包更多的工作。我不确定调试器和评估器(它是编译器的一部分)在这些可视化器中的交互方式如何工作,但尽可能批量处理工作可能会有所帮助。在循环后调用Evaluate之前尝试构建一个更复杂的表达式。您可能需要使用某些转义或定界约定来解压缩结果。例如,请想象一下构建字段值列表并将其作为逗号分隔字符串返回的表达式会是什么样子-但您需要在值本身中转义逗号。< /p>

好的,作为快速测试,我建立了一个字段值列表(未转义),总时间现在约为7秒(未转义),但将“s.Fields [%d] .Value”替换为“%s.Fields [%d] .AsString”我知道在求值器中可以执行单个过程,但我能声明变量或执行序列语句吗?类似于 var l:TStringList; ds.GetFieldsNames(l); Result:= l.CommaText; l.free; - netcodecz
随机想法 - 我们能否为任何类定义一个特殊的“debugDump”函数,并只有一个文本替换可视化器尝试在任何对象上调用它?这将消除在许多(如果不是大多数)情况下编写可视化器的需要。 - Roddy

5
因为 Delphi 是与您调试的 exe 不同的进程,所以您不能直接使用您的 exe 的内存指针,因此您需要使用“.Evaluate”来完成所有操作。
您可以使用两种不同的方法: 1.将特殊的调试转储函数添加到可执行文件中,该函数可以在一次调用中完成所有值的检索。 2.注入特殊的 dll 到 exe 中,与 1 相同(更多的黑客技术等)。
我已经使选项 1 可以工作,而选项 2 也应该是可能的,但会比较复杂和“丑陋”,因为它需要采用某些黑客技巧……
使用下面的代码(只需添加到 dpr 文件)即可:
Result := 'Dump=' + Evaluate('TObjectDumper.SpecialDump(' + FExpression + ')');

示例代码选项1,将其更改为您的TDataset(可能创建所有值的CSV字符串):

unit Unit1;

interface

type
  TObjectDumper = class
  public
    class function SpecialDump(aObj: TObject): string;
  end;

implementation

class function TObjectDumper.SpecialDump(aObj: TObject): string;
begin
  Result := ''; 
  if aObj <> nil then 
    Result := 'Special dump: ' + aObj.Classname;
end;

initialization
  //dummy call, just to ensure it is linked c.q. used by compiler
  TObjectDumper.SpecialDump(nil);

end.

编辑:如果有人感兴趣的话:我也成功实现了选项2(BPL注入)。

0

我还没有机会尝试使用调试可视化器,所以我不知道这是否有效,但您是否尝试过使用Evaluate()将FExpression转换为其实际内存地址?如果您可以做到这一点,那么将该内存地址强制转换为TDataSet指针,并正常使用其属性,而无需进行其他Evaluate()调用。例如:

procedure TDataSetViewerFrame.mFillData; 
var 
  DS: TDataSet;
  I: Integer; 
  //  sw: TStopwatch; 
begin 
  //  sw := TStopwatch.StartNew; 
  DS := TDataSet(StrToInt(Evaluate(FExpression)); // this line may need tweaking
  for I := 0 to DS.Fields.Count - 1 do 
  begin 
    with DS.Fields[I] do begin
      FFields.Add(FieldName);
      FValues.Add(VarToStr(Value));
    end;
  end; 
  {
  sw.Stop; 
  s := sw.Elapsed; 
  Application.MessageBox(Pchar(s), '');
  } 
end; 

好主意,但是存在一个问题:Evaluate('@'+FExpression)返回0,而Evaluate(Format('Addr(%s)', [FExpression]))返回的地址与在IDE中监视列表中的Addr(dataset)或@dataset不同 -> AV。 - netcodecz
例如,从可视化器返回$23F288,同时从IDE返回@ds1 = $AC46AC。 代码: iAddr:= StrToInt(Evaluate(Format('Addr(%s)',[FExpression]))); Application.MessageBox(Pchar(IntToStr(i)),''); //调试 DS:= TDataSet(iAddr); - netcodecz
1
实际指向的TDataSet对象的地址与获取TDataSet指针本身的地址不同。换句话说:ds1 <> @ds1。等我有机会时,我会尝试使用可视化工具并看看它们能做些什么。 - Remy Lebeau
好的,我知道了——我的错误, iAddr := StrToInt(Evaluate(Format('Integer(%s)', [FExpression]))); 返回正确的地址,但使用这个地址会导致AV。 - netcodecz
1
玩弄它时,我发现OTA评估器能够返回内存地址、整数值以及字符串。如果将可视化表达式本身传递给IOTAThread90.Evaluate(),它可以返回正在评估的TDataSet对象的内存地址。然而,它是作为被调试可执行文件所知的内存地址,因此在调试器的地址空间中无效。因此,要么坚持使用Evaluate()(比示例更强大),要么使用IOTAProcess60.ReadProcessMemory()手动从被调试可执行文件的地址空间中读取数据。 - Remy Lebeau

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