Why is CharInSet faster than Case statement?

后端 未结 5 978
借酒劲吻你
借酒劲吻你 2020-12-05 11:26

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

相关标签:
5条回答
  • 2020-12-05 11:33

    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!

    0 讨论(0)
  • 2020-12-05 11:38

    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.

    0 讨论(0)
  • 2020-12-05 11:44

    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.

    0 讨论(0)
  • 2020-12-05 11:52

    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.

    0 讨论(0)
  • 2020-12-05 11:53

    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
    
    0 讨论(0)
提交回复
热议问题