代码之家  ›  专栏  ›  技术社区  ›  Andreas Rejbrand

德尔菲:为什么二进制字符串比较运算符(=)不使用SameStr?

  •  14
  • Andreas Rejbrand  · 技术社区  · 14 年前

    众所周知 SameStr(S1, S2) S1 = S2 ,其中 var S1, S2: string 在德尔菲。

    (以及, 当然 , SameText(S1, S2) AnsiLowerCase(S1) = AnsiLowerCase(S2) .)

    但据我所知, 萨梅斯特(S1,S2) 做的和 S1=S2 ,所以我不禁要问,到底为什么Delphi编译器不使用 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;
    

    以及内联的

    function RandomChar: char;
    begin
      result := chr(RandomRange(ord('A'), ord('Z')));
    end;
    

    “时钟”的功能就是 QueryPerformanceCounter , QueryPerformanceFrequency ,和 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
    

    所以为什么编译器不使用 萨梅斯特 编写程序集时的代码 S1=S2 ?

    更新

    在读了科斯敏·普朗德的精彩回答后,我忍不住定格了

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

    产生长度相等的字符串。

    2.74783364614126E-0002
    1.96818773095322E-0002
    ratio ~ 1.40
    

    嗯。。。 萨梅斯特 仍然赢。。。

    我的规格

    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
    

    更新

    看起来(见下面Cosmin Prund的精彩回答)就像 = 操作员在D2009和D2010之间更改。有人能证实吗?

    4 回复  |  直到 14 年前
        1
  •  19
  •   Cosmin Prund    14 年前

    回答

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

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

    项目3;

    {$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 结果采用制表符分隔格式,以便我可以将其复制/粘贴到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
    

    然后我开始玩 创建随机字符串数组 运行多个测试的例程。

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

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

    下一次测试 创建随机字符串数组 返回长度相等的字符串。字符串的内容仍然是完全随机的,但是字符串的长度等于X轴上的任何长度。这次“=”运算符显然更有效:

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

    现在真正的问题是,对于真正的代码,字符串相等的概率是多少?SameStr()获得地形的差异有多大?下一个文本,我正在构建两个字符串,第一个是StrLLN(X轴上的数字),第二个字符串具有Strel+Strand(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+随机(StrLen div 2)。这个 SameStr() 赢得本轮比赛:

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

    结论

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

        2
  •  4
  •   Marjan Venema    14 年前

    SameStr有一个可选的第三个参数:LocaleOptions。通过省略第三个参数(case sensitive locale-independent comparison),可以获得类似于“=”的行为。

    你会认为这和二进制比较是一样的,但事实并非如此。

    由于D2009 Delphi字符串除了长度和refcount之外还有一个“代码页”负载。

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

    当你做一个 String1 = String2 您告诉编译器忽略关于字符串的所有信息,只需进行一个二进制比较(它使用ustreequal进行比较)。

    当你做一个 SameStr CompareStr (SameStr使用)Delphi将首先检查字符串是否为Unicode(UTF-16LE),如果不是,则在执行实际工作之前转换它们。

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

    更新:

    实际上,ustriqual(通过UStrCmp)也进行转换,就像CompareStr一样,它查看字符串的elemSize来决定它们是否是Unicode,如果不是,则进行转换。

    所以编译器不使用SameStr(CompareStr)作为 = 接线员此刻躲开了我。我能想到的唯一一件事是,它与用于“=”比较AnsiStrings的LStrEqual有一个很好的类比。我想只有编译人员知道。

    很抱歉浪费了你的时间。不过,我要留下答案,这样其他人就不用走这条调查之路了。

        3
  •  0
  •   Ken Bourassa    14 年前

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

    以“RandomString(0,0)”为例,SameStr的速度确实更快(大约20%)。但是,如果第二个字符串设置为“”,则性能几乎相同。经过更多的测试,似乎不是长度的不同导致性能的不同,而是空字符串。

    Cosmin Prund刚刚发布了一个更深入的分析。。。

    要记住的一件事是,对于这么小的函数( 在几毫秒内测试100万次),实际运行代码的处理器可能会有很大的不同。ASM代码对1个处理器的BPU可能比另一个更友好一些。。。或者某些指令可以在不同的CPU上更有效地运行。数据对齐可能会影响它。缓存未命中。这些只是硬件级别上可能影响最终性能的几个例子。

    作为参考,我做的测试是在Phenom X4处理器上进行的。

        4
  •  0
  •   hikari    8 年前

    未来还会有一些测试:

    • Delphi西雅图更新1
    • 4.3Ghz时为i5-2500k
    • 10亿次迭代
    • 比较2个字符串,17个字符长

    不同文本:
    //=->1890毫秒
    //比较文本->4500毫秒
    //比较器->2130毫秒

    同一文本:
    //=->1890毫秒
    //比较文本->10900毫秒
    //比较器->1895毫秒

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