I\'m perplexed. At CodeRage today, Marco Cantu said that CharInSet was slow and I should try a Case statement instead. I did so in my parser and then checked with AQTime wha
Barry, I'd like to point out that your benchmark does not reflect the actual performance of the various methods, because the structure of the implementations differ. Instead, all methods should use a "while True do" construct, to better reflect the impact of the differing ways to do a char-in-set check.
Here a replacement for the test-methods (P2 is unchanged, P1 and P3 now use the "while True do" construct) :
procedure P1;
var
cp: PChar;
begin
cp := PChar(SampleString);
while True do
if CharInSet(cp^, [#0, ';', '.']) then
Break
else
Inc(cp);
end;
procedure P2;
var
cp: PChar;
begin
cp := PChar(SampleString);
while True do
case cp^ of
'.', #0, ';':
Break;
else
Inc(cp);
end;
end;
procedure P3;
var
cp: PChar;
begin
cp := PChar(SampleString);
while True do
if AnsiChar(cp^) in [#0, ';', '.'] then
Break
else
Inc(cp);
end;
My workstation gives :
CharInSet: 0.099 seconds
case stmt: 0.043 seconds
set test: 0.043 seconds
Which better matches the expected results. To me, it seems using the 'case in' construct doesn't really help. I'm sorry Marco!
The code in the function"CharInSet" is faster than 'case', the time is spent on 'call', use while not (cp^ in [..]) then
you will see this is the fasted.
As I know call takes same amount of processor operations as jump does if they are both using short pointers. With long pointers may be different. Call in assembler does not use stack by default. If there is enough free registers used register. So stack operations take zero time too. It is just registers which is very fast.
In contrast case variant as I see uses add and sub operations which are quite slow and probably add most of extratime.
A free sampling profiler for Delphi can be found there:
https://forums.codegear.com/thread.jspa?messageID=18506
Apart from the issue of incorrect time measurement of instrumenting profilers, it should be noted that which is faster will also depend on how predictable the "case" branches are. If the tests in the "case" have all a similar probability of being encountered, performance of "case" can end up lower than that of CharInSet.
AQTime is an instrumenting profiler. Instrumenting profilers often aren't suitable for measuring code time, particularly in microbenchmarks like yours, because the cost of the instrumentation often outweighs the cost of the thing being measured. Instrumenting profilers, on the other hand, excel at profiling memory and other resource usage.
Sampling profilers, which periodically check the location of the CPU, are usually better for measuring code time.
In any case, here's another microbenchmark which indeed shows that a case
statement is faster than CharInSet
. However, note that the set check can still be used with a typecast to eliminate the truncation warning (actually this is the only reason that CharInSet exists):
{$apptype console}
uses Windows, SysUtils;
const
SampleString = 'foo bar baz blah de;blah de blah.';
procedure P1;
var
cp: PChar;
begin
cp := PChar(SampleString);
while not CharInSet(cp^, [#0, ';', '.']) do
Inc(cp);
end;
procedure P2;
var
cp: PChar;
begin
cp := PChar(SampleString);
while True do
case cp^ of
'.', #0, ';':
Break;
else
Inc(cp);
end;
end;
procedure P3;
var
cp: PChar;
begin
cp := PChar(SampleString);
while not (AnsiChar(cp^) in [#0, ';', '.']) do
Inc(cp);
end;
procedure Time(const Title: string; Proc: TProc);
var
i: Integer;
start, finish, freq: Int64;
begin
QueryPerformanceCounter(start);
for i := 1 to 1000000 do
Proc;
QueryPerformanceCounter(finish);
QueryPerformanceFrequency(freq);
Writeln(Format('%20s: %.3f seconds', [Title, (finish - start) / freq]));
end;
begin
Time('CharInSet', P1);
Time('case stmt', P2);
Time('set test', P3);
end.
Its output on my laptop here is:
CharInSet: 0.261 seconds
case stmt: 0.077 seconds
set test: 0.060 seconds