为什么 CharInSet 比 Case 语句快? [英] Why is CharInSet faster than Case statement?

查看:31
本文介绍了为什么 CharInSet 比 Case 语句快?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我很困惑.今天在 CodeRage,Marco Cantu 说 CharInSet 很慢,我应该尝试使用 Case 语句.我在我的解析器中这样做,然后用 AQTime 检查加速是什么.我发现 Case 语句要慢得多.

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 what the speedup was. I found the Case statement to be much slower.

4,894,539 次执行:

4,894,539 executions of:

虽然不是 CharInSet (P^, [' ', #10,#13, #0]) 做 inc(P);

while not CharInSet (P^, [' ', #10,#13, #0]) do inc(P);

时间为 0.25 秒.

was timed at 0.25 seconds.

但执行次数相同:

while True do
  
的案例P^    '', #10, #13, #0: 中断;
    else inc(P);
  结束;

while True do
  case P^ of
    ' ', #10, #13, #0: break;
    else inc(P);
  end;

while True"需要 0.16 秒,第一种情况需要 0.80 秒,else 情况需要 0.13 秒,总计 1.09 秒,或者超过 4 倍的时间.

takes .16 seconds for the "while True", .80 seconds for the first case, and .13 seconds for the else case, totaling 1.09 seconds, or over 4 times as long.

CharInSet 语句的汇编代码是:

The assembler code for the CharInSet statement is:

添加 edi,$02
mov edx,$0064b290
movzx eax,[edi]
调用 CharInSet
测试 a1,a1
jz $00649f18(回到add语句)

add edi,$02
mov edx,$0064b290
movzx eax,[edi]
call CharInSet
test a1,a1
jz $00649f18 (back to the add statement)

而案例逻辑很简单:

movzx eax,[edi]
副斧,$01
jb $00649ef0
副斧,$09
jz $00649ef0
副斧,$03
jz $00649ef0
添加 edi,$02
jmp $00649ed6(回到movzx语句)

movzx eax,[edi]
sub ax,$01
jb $00649ef0
sub ax,$09
jz $00649ef0
sub ax,$03
jz $00649ef0
add edi,$02
jmp $00649ed6 (back to the movzx statement)

在我看来,case 逻辑使用了非常高效的汇编程序,而 CharInSet 语句实际上必须调用 CharInSet 函数,该函数在 SysUtils 中也很简单,是:

The case logic looks to me to be using very efficient assembler, whereas the CharInSet statement actually has to make a call to the CharInSet function, which is in SysUtils and is also simple, being:

function CharInSet(C: AnsiChar; const CharSet: TSysCharSet): Boolean;
开始
结果 := C in CharSet;
结束;

function CharInSet(C: AnsiChar; const CharSet: TSysCharSet): Boolean;
begin
Result := C in CharSet;
end;

我认为这样做的唯一原因是因为 [' ', #10, #13, #0] 中的 P^ 在 Delphi 2009 中不再被允许,因此调用会进行类型转换以允许它.

I think the only reason why this is done is because P^ in [' ', #10, #13, #0] is no longer allowed in Delphi 2009 so the call does the conversion of types to allow it.

尽管如此,我对此感到非常惊讶,但仍然不相信我的结果.

None-the-less I am very surprised by this and still don't trust my result.

是 AQTime 测量出了什么问题,我在这个比较中遗漏了什么,还是 CharInSet 真的是一个值得使用的高效函数?

Is AQTime measuring something wrong, am I missing something in this comparison, or is CharInSet truly an efficient function worth using?

结论:

我想你明白了,巴里.感谢您抽出时间做详细的例子.我在我的机器上测试了你的代码,得到了 0.171、.066 和 0.052 秒(我猜我的台式机比你的笔记本电脑快一点).

I think you got it, Barry. Thank you for taking the time and doing the detailed example. I tested your code on my machine and got .171, .066 and .052 seconds (I guess my desktop is a bit faster than your laptop).

在 AQTime 中测试该代码,它为三个测试提供:0.79、1.57 和 1.46 秒.在那里您可以看到仪器的巨大开销.但真正让我感到惊讶的是,这种开销将明显的最佳"结果变成了实际上最差的 CharInSet 函数.

Testing that code in AQTime, it gives: 0.79, 1.57 and 1.46 seconds for the three tests. There you can see the large overhead from the instrumentation. But what really surprises me is that this overhead changes the apparent "best" result to be the CharInSet function which is actually the worst.

所以 Marcu 是正确的,而 CharInSet 较慢.但是您无意中(或者可能是故意)通过使用 Set 方法中的 AnsiChar(P^) 取出 CharInSet 正在做的事情给了我一个更好的方法.除了比 case 方法在速度上略有优势外,它也比使用 case 代码更少,更容易理解.

So Marcu is correct and CharInSet is slower. But you've inadvertently (or maybe on purpose) given me a better way by pulling out what CharInSet is doing with the AnsiChar(P^) in Set method. Other than the minor speed advantage over the case method, it is also less code and more understandable than using the cases.

您还让我意识到使用 AQTime(和其他检测分析器)进行错误优化的可能性.知道这一点将有助于我做出用于 Delphi 的分析器和内存分析工具这也是我的问题的另一个答案AQTime 是如何做到的?.当然,AQTime 在检测时并不会改变代码,所以它必须使用一些其他魔法来做到这一点.

You've also made me aware of the possibility of incorrect optimization using AQTime (and other instrumenting profilers). Knowing this will help my decision re Profiler and Memory Analysis Tools for Delphi and it also is another answer to my question How Does AQTime Do It?. Of course, AQTime doesn't change the code when it instruments, so it must use some other magic to do it.

所以答案是 AQTime 显示的结果会导致错误的结论.

So the answer is that AQTime is showing results that lead to the incorrect conclusion.

Followup:我留下这个问题的指责"是 AQTime 结果可能具有误导性.但公平地说,我应该指导你通读这个问题:Is是否有适用于 Delphi 的快速 GetToken 例程? 开始认为 AQTime 给出了误导性结果,并得出结论认为它没有.

Followup: I left this question with the "accusation" that AQTime results may be misleading. But to be fair, I should direct you to read through this question: Is There A Fast GetToken Routine For Delphi? which started off thinking AQTime gave misleading results, and concludes that it does not.

推荐答案

AQTime 是一个检测分析器.检测分析器通常不适合测量代码时间,尤其是在像您这样的微基准测试中,因为检测的成本通常超过被测量事物的成本.另一方面,检测分析器擅长分析内存和其他资源使用情况.

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.

定期检查 CPU 位置的采样分析器通常更适合测量代码时间.

Sampling profilers, which periodically check the location of the CPU, are usually better for measuring code time.

无论如何,这是另一个微基准测试,它确实表明 case 语句比 CharInSet 语句更快.但是,请注意集合检查仍然可以与类型转换一起使用以消除截断警告(实际上这是 CharInSet 存在的唯一原因):

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

这篇关于为什么 CharInSet 比 Case 语句快?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆