Delphi:为什么二元字符串比较运算符(=)不使用SameStr?

时间:2010-07-12 16:33:02

标签: performance delphi string compiler-construction

众所周知,SameStr(S1, S2)S1 = S2更快,其中Delphi中的var S1, S2: string

(当然, SameText(S1, S2)AnsiLowerCase(S1) = AnsiLowerCase(S2)快得多。)

但是,据我所知,SameStr(S1, S2)S1 = S2完全相同,所以我不禁想知道为什么世界上Delphi编译器不使用{{1}使用SameStr运算符测试字符串相等性时的代码。当然必须有这个理由吗?

一些基准测试

一个简单的程序,

=

,其中

program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  RejbrandCommon;

const
  N = 1000000;

var
  Strings1, Strings2: StringArray;
  i: integer;
  b: {dummy }boolean;

procedure CreateRandomStringArrays;
var
  i: integer;
begin
  SetLength(Strings1, N);
  SetLength(Strings2, N);
  for i := 0 to N - 1 do
  begin
    Strings1[i] := RandomString(0, 40);
    Strings2[i] := RandomString(0, 40);
  end;
end;

begin

  CreateRandomStringArrays;

  StartClock;
  for i := 0 to N - 1 do
    if Strings1[i] = Strings2[i] then
      b := not b;
  StopClock;
  OutputClock;

  StartClock;
  for i := 0 to N - 1 do
    if SameStr(Strings1[i], Strings2[i]) then
      b := not b;
  StopClock;
  OutputClock;

  Pause;

end.

和内联

function RandomString(const LowerLimit: integer = 2; const UpperLimit: integer = 20): string;
var
  N, i: integer;
begin
  N := RandomRange(LowerLimit, UpperLimit);
  SetLength(result, N);
  for i := 1 to N do
    result[i] := RandomChar;
end;

和“clock”函数只包function RandomChar: char; begin result := chr(RandomRange(ord('A'), ord('Z'))); end; QueryPerformanceCounterQueryPerformanceFrequency,产生输出

Writeln

如果我们比较的两个弦的长度差异很大,那么差异就更大了。我们试试

2.56599325762716E-0002
1.24310093156453E-0002
ratio ~ 2.06

并获得

Strings1[i] := RandomString(0, 0); // = '';
Strings2[i] := RandomString(0, 40);

那么为什么编写1.81630411160156E-0002 4.44662043198641E-0003 ratio ~ 4.08 的程序集时,编译器不会使用SameStr代码?

更新

在阅读了Cosmin Prund的优秀答案之后,我无法抗拒设置

S1 = S2

生成相同长度的字符串。

Strings1[i] := RandomString(40, 40);
Strings2[i] := RandomString(40, 40);

嗯...... 2.74783364614126E-0002 1.96818773095322E-0002 ratio ~ 1.40 仍然胜利......

我的规格

SameStr

更新

看起来(请参阅Cosmin Prund的优秀答案下面的评论)就像CPU Brand String: Intel(R) Core(TM) i7 CPU 870 @ 2.93GHz Memory: 6 GB OS: Windows 7 Home Premium (64-bit) Compiler/RTL: Delphi 2009 运算符在D2009和D2010之间被更改了一样。谁能证实这一点?

4 个答案:

答案 0 :(得分:19)

答案

这完全取决于你如何构建随机字符串。我使用了代码的修改版本,因为我们很少有RejbrandCommon单元,因为我想使用Excel来完成我的分析(并制作漂亮的图片)。

代码(跳过代码看一些结论):

程序Project3;

{$APPTYPE CONSOLE}

uses
  SysUtils, Windows;

const
  StringsNumber = 2000000;

var
  Strings1, Strings2: array of string;
  StrLen: integer;
  b: {dummy }boolean;

function RandomString(MinLen, MaxLen:Integer):string;
var N, i:Integer;
begin
  N := MinLen + Random(MaxLen-MinLen);
  Assert(N >= MinLen); Assert(N <= MaxLen);
  SetLength(Result, N);
  for i:=1 to N do
    Result[i] := Char(32 + Random(1024)); // Random Unicode Char
end;

procedure CreateRandomStringArrays(StrLen:Integer);
var
  i: integer;
  StrLen2:Integer;
begin
  SetLength(Strings1, StringsNumber);
  SetLength(Strings2, StringsNumber);
  for i := 0 to StringsNumber - 1 do
  begin
    StrLen2 := StrLen + Random(StrLen div 2);
    Strings1[i] := RandomString(StrLen, StrLen2);
    StrLen2 := StrLen + Random(StrLen div 2);
    Strings2[i] := RandomString(StrLen, StrLen2);
  end;
end;

var C1, C2, C3, C4:Int64;

procedure RunTest(StrLen:Integer);
var i:Integer;
begin
  CreateRandomStringArrays(StrLen);

  // Test 1: using equality operator
  QueryPerformanceCounter(C1);
  for i := 0 to StringsNumber - 1 do
    if Strings1[i] = Strings2[i] then
      b := not b;
  QueryPerformanceCounter(C2);

  // Test 2: using SameStr
  QueryPerformanceCounter(C3);
  for i := 0 to StringsNumber - 1 do
    if SameStr(Strings1[i], Strings2[i]) then
      b := not b;
  QueryPerformanceCounter(C4);

  // Results:
  C2 := C2 - C1;
  C4 := C4 - C3;
  WriteLn(IntToStr(StrLen) + #9 + IntToStr(C2) + #9 + IntToStr(C4));
end;

begin

  WriteLn('Count'#9'='#9'SameStr');
  for StrLen := 1 to 50 do
    RunTest(StrLen);

end.

我使CreateRandomStringArrays例程采用了StrLen参数,因此我可以在循环中运行多个类似的测试。我使用QueryPerformanceCounter直接使用WriteLn并使用制表符分隔格式CreateRandomStringArrays结果,以便将其复制/粘贴到Excel中。在Excel中,我得到以下形式的结果:

StrLen  =   SameStr
1   61527   69364
2   60188   69450
3   72130   68891
4   78847   85779
5   77852   78286
6   83612   88670
7   93936   96773
然后我对事情进行了规范化。在每一行上,最大值为“1”,另一个值为1的百分比。结果如下所示:

StrLen  =   SameStr
1   0,88    1
2   0,86    1
3   1   0,95
4   0,91    1
5   0,99    1
6   0,94    1
7   0,97    1

然后我开始使用CreateRandomStringArrays例程来运行多个测试。

这是原始情况下情节的样子(CreateRandomStringArrays生成随机长度的字符串,长度为1到X轴上的任何内容)。蓝色是“=”运算符的结果,红色是“SameStr”的结果,越低越好。它清楚地表明,SameStr()的字符串边长超过10个字符。

alt text http://fisiere.sediu.ro/PentruForumuri/V1_1_to_maxlen.png

接下来的测试,使SameStr()返回EQUAL长度的字符串。字符串的内容仍然是完全随机的,但字符串的长度等于X轴上的任何内容。这次“=”运算符显然更有效:

alt text http://fisiere.sediu.ro/PentruForumuri/V1_equal_strings.png

现在真正的问题是,使用REAL代码,字符串相等的概率是多少? SameStr()开始获取地形的差异有多大?接下来的文字,我正在构建两个字符串,第一个是StrLen(X轴上的数字),第二个字符串的长度为StrLen + Random(4)。同样,“=”运算符更好:

alt text http://fisiere.sediu.ro/PentruForumuri/V1_rnd_p4.png

接下来的测试,我有两个字符串,每个字符串长度为:StrLen + Random(StrLen div 10)。 “=”运算符更好。

alt text http://fisiere.sediu.ro/PentruForumuri/V1_rnd_pm_10p.png

...和我的最终测试,长度为+/- 50%的字符串。公式:StrLen + Random(StrLen div 2)。 {{1}}赢了本轮:

alt text http://fisiere.sediu.ro/PentruForumuri/V1_rnd_pm_50p.png

结论

我不确定。我没想到这与字符串长度有关!我希望这两个函数能够快速处理不同长度的字符串,但不会发生。

答案 1 :(得分:4)

SameStr有一个可选的第三个参数:LocaleOptions。通过省略第三个参数,您得到类似于“=”的行为:案例敏感的区域独立比较。

您会认为这与二进制比较相同,但事实并非如此。

由于D2009 Delphi字符串除长度和引用计数外还有“代码页”有效负载。

  StrRec = packed record
    codePage: Word;
    elemSize: Word;
    refCnt: Longint;
    length: Longint;
  end;

执行String1 = String2时,您告诉编译器忽略有关该字符串的所有信息,只需进行二进制比较(它使用UStrEqual)。

当您执行SameStrCompareStr(由SameStr使用)时,Delphi将首先检查字符串是否为Unicode(UTF-16LE),如果不是,请在执行实际工作之前将其转换

当您查看CompareStr(没有第三个参数的那个)的实现时,您可以看到这一点,在初始优化之后,检查参数是否为unicode字符串,如果不是,则使用UStrFromLStr转换它们。

<强>更新

实际上,UStrEqual(通过UStrCmp)也会进行转换,比如CompareStr它会查看字符串的elemSize以决定它们是否为Unicode,如果不是则转换它们。

因此编译器不为=运算符使用SameStr(CompareStr)的原因目前无法解决。我唯一能想到的是它与用于'='的LStrEqual有一个很好的类比 - 比较AnsiStrings。我猜只有编译人员知道。

抱歉浪费了你的时间。我会留下答案,所以其他人不必走这条调查路线。

答案 2 :(得分:0)

在我的系统上,“=”比SameStr快。

SameStr以“RandomString(0,0)”为例变得更快(约20%)。但话又说回来,如果它是第二个设置为''的字符串,表现几乎相同。经过一些更多的测试后,似乎是长度的差异导致性能差异,这是空字符串。

Cosmin Prund刚刚发布了更为全面的分析......

应该记住的一件事是,对于那么小的功能( 运行代码的实际处理器可能会产生很大的不同。 ASM代码对于1个处理器的BPU可能比其他代码更友好......或者某些指令可能在不同的CPU上更有效地运行。数据对齐可能会影响它。缓存未命中。这些只是硬件级别的一些例子,可能会影响最终的性能。

有关信息,我所做的测试是在Phenom X4 CPU上进行的。

答案 3 :(得分:0)

将来有些测试会进一步发展:

  • Delphi Seattle Update 1
  • i5-2500k @ 4.3Ghz
  • 10亿次迭代
  • 比较2个字符串,长度为17个字符

不同的文字:
  // = - &gt; 1890毫秒   // CompareText - &gt; 4500毫秒
  // CompareStr - &gt; 2130毫秒

相同文字:
  // = - &gt; 1890毫秒   // CompareText - &gt; 10900毫秒
  // CompareStr - &gt; 1895 ms

结论:=在所有情况下都更快,但是具有相同文本的CompareStr几乎与=一样快。另外,在处理Ansi字符串时,CompareText / Str看起来要慢得多。