我很困惑。在CodeRage今天,Marco Cantu说CharInSet很慢,我应该尝试使用Case语句。我在我的解析器中这样做,然后用AQTime检查加速是什么。我发现Case语句要慢得多。
4,894,539次执行:
虽然不是CharInSet(P ^,['',#10,#13,#0])做inc(P);
定时为0.25秒。
但执行次数相同:
而真的是 案例P ^ '',#10,#13,#0:休息;
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]
子斧,$ 01
jb $ 00649ef0
sub ax,$ 09
jz $ 00649ef0
sub ax,$ 03
jz $ 00649ef0
添加edi,$ 02
jmp $ 00649ed6(回到movzx声明)
案例逻辑在我看来是使用非常高效的汇编程序,而CharInSet语句实际上必须调用CharInSet函数,该函数在SysUtils中并且也很简单,是:
function CharInSet(C:AnsiChar; const CharSet:TSysCharSet):Boolean;
开始
结果:= CharSet中的C;
端;
我认为这样做的唯一原因是因为在Delphi 2009中不再允许['',#10,#13,#0]中的P ^,因此调用会转换类型以允许它。< / p>
尽管如此,我对此感到非常惊讶,仍然不相信我的结果。
AQTime是否测量错误,我在这个比较中遗漏了什么,或者CharInSet真的是一个值得使用的有效函数吗?
结论:
我想你明白了,巴里。感谢您抽出宝贵时间做详细示例。我在我的机器上测试了你的代码并获得了.171,.066和.052秒(我猜我的桌面比你的笔记本电脑快一点)。
在AQTime中测试该代码,它给出:三次测试的0.79,1.57和1.46秒。在那里,您可以看到仪器的大量开销。但令我惊讶的是,这种开销将明显的“最佳”结果改为CharInSet函数,实际上是最差的。
所以Marcu是正确的,CharInSet更慢。但是你无意中(或者可能是故意的)通过在Set方法中提取CharInSet用AnsiChar(P ^)做的事情给了我一个更好的方法。除了案例方法的次要速度优势之外,它还比使用案例更少的代码和更易理解。
您还了解了使用AQTime(以及其他仪器分析器)进行错误优化的可能性。了解这一点将有助于我的决定Profiler and Memory Analysis Tools for Delphi,这也是我的问题How Does AQTime Do It?的另一个答案。当然,AQTime在使用时不会改变代码,所以它必须使用其他一些魔法才能完成。
所以答案是AQTime正在显示导致错误结论的结果。
跟进:我把这个问题留给了“指责”,即AQTime的结果可能会产生误导。但公平地说,我应该引导你阅读这个问题:Is There A Fast GetToken Routine For Delphi?开始认为AQTime会产生误导性的结果,并得出结论认为它没有。
答案 0 :(得分:25)
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
答案 1 :(得分:7)
这里替换了测试方法(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'结构并没有真正帮助。对不起Marco!
答案 2 :(得分:3)
可以在那里找到Delphi的免费抽样分析器:
https://forums.codegear.com/thread.jspa?messageID=18506
除了仪表分析仪的不正确时间测量问题之外,应该注意哪个更快也将取决于“案例”分支的可预测性。 如果“case”中的测试具有相似的概率,则“case”的性能最终会低于CharInSet。
答案 3 :(得分:2)
函数“CharInSet”中的代码比“case”快,时间花在'call'上, 使用 而不是(cp ^ in [..])那么
你会看到这是禁食。
答案 4 :(得分:1)
据我所知,如果调用它们都使用短指针,则调用需要与跳转相同的处理器操作量。长指针可能会有所不同。默认情况下,调用汇编程序不使用堆栈。如果有足够的空闲寄存器使用寄存器。因此堆栈操作也需要零时间。只是寄存器非常快。
相比之下,我看到的案例变体使用了加法和子操作,这些操作非常慢并且可能会增加大部分的额外时间。