Improve speed of own debug visualizer for Delphi 2010
Asked Answered
K

3

9

I wrote Delphi debug visualizer for TDataSet to display values of current row, source + screenshot: http://delphi.netcode.cz/text/tdataset-debug-visualizer.aspx . Working good, but very slow. I did some optimalization (how to get fieldnames) but still for only 20 fields takes 10 seconds to show - very bad.

Main problem seems to be slow IOTAThread90.Evaluate used by main code shown below, this procedure cost most of time, line with ** about 80% time. FExpression is name of TDataset in code.

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;

Now I have no idea how to improve performance.

Kimmi answered 31/3, 2010 at 20:14 Comment(4)
I am not familiar with these data components, so I do not know how to significantly improve performance. However, your Copy could be replaced by a Delete, which probably is faster. And perhaps s <> '' could be replaced by length(s) = 0. If there is any performance gain in this I do not know. In general, however, I believe that string comparison using = and <> are slower than the smart SameText(s1, s2) and SameStr(s1, s2) routines.Ghassan
@Andreas - those micro-optimizations couldn't possibly contribute much to a 10 second delaySoubrette
@Barry Kelly: I know. That's why I wrote "I do not know how to significantly improve performance".Ghassan
Length(s) <> 0 is actually slower because Length() is an inlined function call but still slower than S <> '' because the compiler translates the later to Pointer(S) <> nil.Ophthalmitis
S
9

That Evaluate needs to do a surprising amount of work. The compiler needs to compile it, resolving symbols to memory addresses, while evaluating properties may cause functions to be called, which needs the debugger to copy the arguments across into the debugee, set up a stack frame, invoke the function to be called, collect the results - and this involves pausing and resuming the debugee.

I can only suggest trying to pack more work into the Evaluate call. I'm not 100% sure how the interaction between the debugger and the evaluator (which is part of the compiler) works for these visualizers, but batching up as much work as possible may help. Try building up a more complicated expression before calling Evaluate after the loop. You may need to use some escaping or delimiting convention to unpack the results. For example, imagine what an expression that built the list of field values and returned them as a comma separated string would look like - but you would need to escape commas in the values themselves.

Soubrette answered 31/3, 2010 at 20:43 Comment(2)
Ok, as quick test I built list of field values (without escape) and total time is now about 7s (without escape), but replaced "s.Fields[%d].Value" to "%s.Fields[%d].AsString" I know that in evaluator can be executed single procedure, but can I declare variable or execute sequence statement? something like var l:TStringList; ds.GetFieldsNames(l); Result:= l.CommaText; l.free;Kimmi
Random idea - could we just define a special "debugDump" function for any class, and have just one text-replacement visualiser that tries to invoke that on any object? That would remove the need to write visualisers in many (if not most) cases.Fannie
C
5

Because Delphi is a different process than your debugged exe, you cannot direct use the memory pointers of your exe, so you need to use ".Evaluate" for everything.

You can use 2 different approaches:

  1. Add special debug dump function into executable, which does all value retrieving in one call
  2. Inject special dll into exe with does the same as 1 (more hacking etc)

I got option 1 working, 2 should also be possible but a little bit more complicated and "ugly" because of hacking tactics... With code below (just add to dpr) you can use:

Result := 'Dump=' + Evaluate('TObjectDumper.SpecialDump(' + FExpression + ')');

Demo code of option 1, change it for your TDataset (maybe make CSV string of all values?):

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.

Edit: in case someone is interested: I got option 2 working too (bpl injection)

Causalgia answered 17/8, 2010 at 9:53 Comment(0)
U
0

I have not had a chance to play with the debug visualizers yet, so I do not know if this work, but have you tried using Evaluate() to convert FExpression into its actual memory address? If you can do that, then type-cast that memory address to a TDataSet pointer and use its properties normally without going through additional Evaluate() calls. For example:

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; 
Urey answered 1/4, 2010 at 20:10 Comment(5)
good idea, but there is a problem: Evaluate('@'+FExpression) return 0, Evaluate(Format('Addr(%s)', [FExpression])) return different address than Addr(dataset) or @dataset in watch list in the IDE -> AVKimmi
For example from visualizer return $23F288 from IDE in same moment return @ds1 = $AC46AC. Code: iAddr := StrToInt(Evaluate(Format('Addr(%s)', [FExpression]))); Application.MessageBox(Pchar(IntToStr(i)), '');// debug DS := TDataSet(iAddr);Kimmi
The address of the actual TDataSet object being pointed at is different than the address of taking the address of the TDataSet pointer itself. IOW: ds1 <> @ds1. When I get a chance, I will play with the visualizers and see what can be done with them.Urey
Good point, I know - my mistake, iAddr := StrToInt(Evaluate(Format('Integer(%s)', [FExpression]))); returning correct addr but using this address resulting in AV.Kimmi
Playing with it, I see the OTA evaluator is able to return memory addresses and integer values as well as strings. If you pass the visualizer Expression by itself to IOTAThread90.Evaluate(), it can return the memory address of the TDataSet object being evaluated. However, it is the memory address as known by the debugged executable, and thus is not valid within the debugger's address space. So either stick with Evaluate() (which is more powerful then the examples use) or else use IOTAProcess60.ReadProcessMemory() to manually read data from the debugged executable's address space.Urey

© 2022 - 2024 — McMap. All rights reserved.