Speeding Up Gui Work
by Erick Engelke
September 8, 2024
Often powerful Web apps must do some GUI preparation, creating a lot of data for display on screen. But that can be slow.
Consider this basic code to write a few thousand lines to a MultiLineEdit text box:
procedure TForm1.LongOperationSlow;
var
i : integer;
begin
MultiLineEdit1.Lines.Clear;
for i := 0 to 2000 do
MultilineEdit1.Lines.Add('line ' + IntToStr( i ));
end;
It takes about 4 seconds on a modern browser. This raises some questions:
How do you know that it takes, that long, and how can we speed it up.
The first step is to write a quick little profiling tool which measures the speed in milliseconds. The DateTime in EWB is a millisecond clock.
So here is a routine to time a procedure and return the number of milliseconds it takes to execute.
type
proc = procedure of object;
function TimeThis( p : proc ) : double;
var
timer : DateTime;
seconds : double;
begin
timer := now;
p; // execute the procedure
seconds := (Integer(now) - Integer(timer)) / 1000;
result := seconds;
end;
With that function we’ll can time different versions of the procedure that accomplish the same thing.
And we’ll make a TButton which, when clicked, will call each of the four procedures we’ll try, and see how each stacks up.
Here’s the first fast version
procedure TForm1.LongOperationFast;
var
i : integer;
begin
// turn off the visibility of the TMultiLineEdit
MultiLineEdit1.Visible := False;
MultiLineEdit1.Lines.Clear;
for i := 0 to 2000 do
MultilineEdit1.Lines.Add('line ' + IntToStr( i ));
MultiLineEdit1.Visible := True;
end;
That reduces the time from 4.261 seconds to 0.129. Roughly a 40 times improvement.
The downside is that you would have to disable each control you wish to update.
Let’s try dispabling the whole TForm. Note, this does not close flicker because EWB doesn’t update the form until you complete your operations.
procedure TForm1.LongOperationFast2;
var
i : integer;
begin
// disable the TForm
Form1.Visible := False;
MultiLineEdit1.Lines.Clear;
for i := 0 to 2000 do
MultilineEdit1.Lines.Add('line ' + IntToStr( i ));
// Re-enable the form
Form1.Visible := True;
end;
This executes in 0.131 seconds, which is roughly the same speed, but much more convenient because we can update anything on the entire form.
The final version doesn’t upate the TMultiLineEdit directly. It builds the TStringList then assigns.
procedure TForm1.LongOperationFast3;
var
i : integer;
sl : TStringList;
begin
sl := TStringList.Create;
for i := 0 to 2000 do
sl.Add('line ' + IntToStr( i ));
MultiLineEdit1.Lines.Assign( sl );
end;
This executes in 0.001 second, or a 4,000 times speed increase over the original solution.
Speeding up TDataSet Operations
I coverred the TDataSet speedup solution in an earlier article. Check it out.
Showing Busy Mouse
It’s a good practice to use the ShowProgress routine to indicate when the computer is busy.
But calling:
ShowProgress('busy');
LongOperationSlow;
HideProgress;
doesn’t work, because EWB doesn’t update the mouse icon until it exits the current event.
The trick is to use the async keyword and schedule your slow operations.
Eg.
procedure TForm1.Button1Click(Sender: TObject);
begin
ShowProgress('Busy');
async CallProcedures;
async HideProgress;
end;
procedure TForm1.CallProcedures;
var
seconds : double;
begin
ShowProgress('working');
seconds := TimeThis( LongOperationSlow );
label1.Caption := FloatToStr( seconds ) + ' s';
seconds := TimeThis( LongOperationFast );
label2.Caption := FloatToStr( seconds ) + ' s';
seconds := TimeThis( LongOperationFast2 );
label3.Caption := FloatToStr( seconds ) + ' s';
seconds := TimeThis( LongOperationFast3 );
label4.Caption := FloatToStr( seconds ) + ' s';
HideProgress;
end;
That works like a charm.
Here’s the output:
By the way, which browser and operating system makes a huge difference. Here were the same tests tried on a M1 MacBook.
Browser | Time |
---|---|
Chrome |
4.1 seconds |
Firefox |
2.1 seconds |
Safari |
1.1 seconds |
IE (Windows Arm |
6.7 seconds |