我很困惑.在CodeRage今天,Marco Cantu说CharInSet很慢,我应该尝试使用Case语句.我在我的解析器中这样做,然后用AQTime检查加速是什么.我发现Case语句要慢得多.
执行的4,894,539次:
而不是CharInSet(P ^,['',#10,#13,#0])做inc(P);
时间为0.25秒.
但执行次数相同:
而确实是 ',#10,#13,#0的
案例P ^
:break;
else inc(P);
结束;
"while True"需要0.16秒,第一种情况需要0.80秒,else情况需要0.13秒,总计1.09秒,或者超过4倍.
CharInSet语句的汇编代码是:
添加edi,$ 02
mov edx,$ 0064b290
movzx eax,[edi]
调用CharInSet
测试a1,a1
jz $ 00649f18(返回add语句)
而案例逻辑就是这样:
movzx eax,[edi]
sub ax,$ 01
jb $ 00649ef0
sub ax,$ 09
jz $ 00649ef0
sub ax,$ 03
jz $ 00649ef0
add edi,$ 02
jmp $ 00649ed6(返回movzx声明)
案例逻辑在我看来是使用非常有效的汇编程序,而CharInSet语句实际上必须调用CharInSet函数,该函数在SysUtils中并且也很简单,是:
function CharInSet(C:AnsiChar; const CharSet:TSysCharSet):Boolean;
begin
结果:= CharSet中的C;
结束;
我认为这样做的唯一原因是因为在Delphi 2009中不再允许['',#10,#13,#0]中的P ^,因此调用会转换类型以允许它.
尽管如此,我对此感到非常惊讶,仍然不相信我的结果.
AQTime是否测量错误,我在这个比较中遗漏了什么,或者CharInSet真的是一个值得使用的有效函数吗?
结论:
我想你明白了,巴里.感谢您抽出宝贵时间做详细示例.我在我的机器上测试了你的代码,得到了.171,.066和.052秒(我猜我的桌面比你的笔记本快一点).
在AQTime中测试该代码,它给出:三次测试的0.79,1.57和1.46秒.在那里,您可以看到仪器的大量开销.但令我惊讶的是,这种开销将明显的"最佳"结果改为CharInSet函数,这实际上是最差的.
所以Marcu是正确的,CharInSet更慢.但是你无意中(或者可能是故意的)通过在Set方法中提取CharInSet用AnsiChar(P ^)做的事情给了我一个更好的方法.除了比case方法更小的速度优势,它也比使用案例更少的代码和更容易理解.
您还让我意识到使用AQTime(以及其他仪器分析器)进行错误优化的可能性.知道这一点将有助于我决定使用Delphi的Profiler和内存分析工具,这也是我的问题的另一个答案AQTime如何做到这一点?.当然,AQTime在使用时不会改变代码,所以它必须使用其他魔法才能完成.
所以答案是AQTime正在显示导致错误结论的结果.
跟进:我把这个问题留给了"指责",即AQTime的结果可能会产生误导.但公平地说,我应该引导你阅读这个问题:Delphi有一个快速的GetToken例程吗?开始认为AQTime给出了误导性的结果,并得出结论认为它没有.
AQTime是一个仪表分析器.仪器分析仪通常不适合测量代码时间,特别是在像您这样的微基准测试中,因为仪器的成本往往超过被测物的成本.另一方面,仪器分析器擅长分析内存和其他资源使用情况.
定期检查CPU位置的采样分析器通常更适合测量代码时间.
无论如何,这是另一个微基准测试,它确实表明case
声明比快CharInSet
.但是,请注意,set check仍然可以与类型转换一起使用以消除截断警告(实际上这是CharInSet存在的唯一原因):
{$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.
它在我的笔记本电脑上的输出是:
CharInSet: 0.261 seconds case stmt: 0.077 seconds set test: 0.060 seconds
Barry,我想指出你的基准测试并不能反映各种方法的实际性能,因为实现的结构不同.相反,所有方法都应该使用"while True do"构造,以更好地反映不同方式进行char-in-set检查的影响.
这里替换了测试方法(P2没有改变,P1和P3现在使用"while True do"构造):
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;
我的工作站给出:
CharInSet: 0.099 seconds case stmt: 0.043 seconds set test: 0.043 seconds
哪个更符合预期结果.对我来说,似乎使用'case in'结构并没有真正帮助.对不起马克!