Cross posted from my website...
The final result:
First, we need to start with a default VCL application in Delphi. Because I do not know the ways that Lazarus handles threads I make no promises that it carries over. So many people are concerned about threads, their dangers, and potential problems with exceptions that I figured I would show you how to benchmark faster on multicore computers. Safely so.
First, get a decent UI consisting of a pair of labels, one indicating which function is being tested and the other for the average execution time, a default progress bar, and two buttons for testing 10,000 and 100,000 times. Why just 10,000 and 100,000? Well, doing 1,000,000 can take a while to accomplish. Also, make sure your progressbar has "step" set to 1, not 10;
Now, above your form object insert this blank template code:[pascal]
TBenchmarkThread = class(TThread)
private
constructor Create(CreateSuspended: boolean = false);
protected
fTimes: Cardinal; // The number of times we execute the same function.
fProg: TProgressBar; // Our Progressbar (multithreaded progressbar calls!).
fS: String;
procedure Execute; override;
end;
[/pascal]
First, why these pieces? Well, the function I will be testing is StrToInt and I'll be testing 10000 and -10000 conversions simultaneously; that means half the tests are negative, half are positive. The internal progressbar reference lets us easily indicate our progress, and carry it between applications.
Now, doubleclick the first benchmark button, the one for the 10000, and enter this code into the function that was created:[pascal]
Benchmark(TButton(Sender).Tag);
[/pascal]This lets us assign this to the other button too, which you should do, and set the tags of each button to the appropriate number. Once this is done define a private benchmark function in your form. Some of these functions and properties aren't around yet, but don't panic. Mine is:[pascal]procedure Benchmark(Times: Cardinal);
procedure TForm1.Benchmark(Times: Cardinal);
var t1,t2: TBenchmarkThread;
begin
progress.Max := Times;
progress.Position := 0;
resultLbl.Caption := '...';
t1 := TBenchMarkThread.Create( '10000', Times div 2, progress);
t2 := TBenchMarkThread.Create('-10000', Times div 2, progress);
while not t1.Terminated and not t2.Terminated do
begin
Application.ProcessMessages;
sleep(20);
end;
// Calculate the average execution time:
resultLbl.Caption := floattostr((t1.ExecutionTime + t2.ExecutionTime) / Times)+' msec';
t1.Free;
t2.Free;
end;[/pascal]
Notice the section where the application processes its messages and then sleeps for a little? Well, this reduces the load of your main thread monitoring the sub-threads and still keep your application updating ... IE, you avoid that pesky "not responding" label in the task manager. And you can drag the window around, etc. Notice that the threads terminate themselves when the results are complete, and that we free the threads later. Now, there isn't a progress label in this yet, but I'll show you that in a minute.
Okay, revise the constructor to match these parameters:[pascal]constructor Create(Str: String; Count: Cardinal; ProgressBar: TProgressBar; CreateSuspended: boolean = false);[/pascal]And, add this property to your thread's private area:[pascal]ExecutionTime: Cardinal;[/pascal]
And now for the nitty gritty, the thread code:[pascal]
constructor TBenchmarkThread.Create(Str: String; Count: Cardinal; ProgressBar: TProgressBar; CreateSuspended: boolean = false);
begin
fS := Str;
fTimes := Count;
fProg := ProgressBar;
inherited Create(CreateSuspended);
end;
procedure TBenchmarkThread.Execute;
var i: cardinal;
begin
ExecutionTime := GetTickCount;
for i := 1 to fTimes do begin
StrToInt(fS);
Synchronize(fProg.StepIt); // This prevents simultaneous function calls. ESSENTIAL!
end;
ExecutionTime := GetTickCount-ExecutionTime;
Terminate; // Done, and prove it.
end;
[/pascal]
You are done. Wow, easy huh? Here is our final code:[pascal]unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls;
type
TBenchmarkThread = class(TThread)
private
ExecutionTime: Cardinal;
constructor Create(Str: String; Count: Cardinal; ProgressBar: TProgressBar; CreateSuspended: boolean = false);
protected
fTimes: Cardinal; // The number of times we execute the same function.
fProg: TProgressBar; // Our Progressbar (multithreaded progressbar calls!).
fS: String;
procedure Execute; override;
end;
TForm1 = class(TForm)
Label1: TLabel;
resultLbl: TLabel;
progress: TProgressBar;
Button1: TButton;
Button2: TButton;
Button3: TButton;
QuitBtn: TButton;
procedure QuitBtnClick(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
procedure Benchmark(Times: Cardinal);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Benchmark(Times: Cardinal);
var t1,t2: TBenchmarkThread;
begin
progress.Max := Times;
progress.Position := 0;
resultLbl.Caption := '...';
t1 := TBenchMarkThread.Create( '10000', Times div 2, progress);
t2 := TBenchMarkThread.Create('-10000', Times div 2, progress);
while not t1.Terminated and not t2.Terminated do
begin
Application.ProcessMessages;
sleep(20);
end;
// Calculate the average execution time:
resultLbl.Caption := floattostr((t1.ExecutionTime + t2.ExecutionTime) / Times)+' msec';
t1.Free;
t2.Free;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Benchmark(TButton(Sender).Tag);
end;
procedure TForm1.QuitBtnClick(Sender: TObject);
begin
Close;
end;
{ TBenchmarkThread }
constructor TBenchmarkThread.Create(Str: String; Count: Cardinal; ProgressBar: TProgressBar; CreateSuspended: boolean = false);
begin
fS := Str;
fTimes := Count;
fProg := ProgressBar;
inherited Create(CreateSuspended);
end;
procedure TBenchmarkThread.Execute;
var i: cardinal;
begin
ExecutionTime := GetTickCount;
for i := 1 to fTimes do begin
StrToInt(fS);
Synchronize(fProg.StepIt); // This prevents simultaneous function calls. ESSENTIAL!
end;
ExecutionTime := GetTickCount-ExecutionTime;
Terminate; // Done, and prove it.
end;
end.[/pascal]
I bet you might just be surprised at how quick it is too! Here's the example application. Maybe this will encourage more people to venture into multithreaded games and applications for games; it isn't as hard as some people make it out to be. I wrote one just thirty minutes ago to compare StrToInt to my own internal one, for scripting stuff, to see just how efficient they were ... in a faster benchmarking method. You can even test different functions simultaneously in real time, which is just a whole lot faster on a multi cored computer.
Bookmarks