【问题标题】:Delphi: Why Doesn't Binary String Comparison Operator (=) use SameStr?Delphi:为什么二进制字符串比较运算符 (=) 不使用 SameStr?
【发布时间】:2010-07-12 16:33:02
【问题描述】:

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

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

但是,据我了解,SameStr(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;

“时钟”函数只是包装 QueryPerformanceCounterQueryPerformanceFrequencyWriteln,产生输出

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 编写汇编时不使用SameStr 代码呢?

更新

看完 Cosmin Prund 的精彩回答后,忍不住设置

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

产生长度相等的字符串。

2.74783364614126E-0002
1.96818773095322E-0002
ratio ~ 1.40

嗯...SameStr 还是赢了...

我的规格

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 出色答案下方的 cmets)就像 = 运算符在 D2009 和 D2010 之间更改。谁能证实这一点?

【问题讨论】:

  • 如果你想运行一些额外的测试,你可能想修复你的 RandomString 函数 (Result[N] -> Result[I])
  • @Ken Bourassa:哦,很好看!

标签: performance delphi string compiler-construction


【解决方案1】:

回答

这完全取决于您如何构建随机字符串。我使用了代码的修改版本,因为我们中很少有人拥有 RejbrandCommon 单元,而且我想使用 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 参数,这样我就可以在一个循环中运行多个类似的测试。我使代码直接使用QueryPerformanceCounterWriteLn 以制表符分隔格式的结果,因此我可以将其复制/粘贴到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

下一个测试,使CreateRandomStringArrays 返回长度相等的字符串。字符串的内容仍然是完全随机的,但字符串的长度等于 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)。 SameStr() 在这一轮获胜:

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

结论

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

【讨论】:

  • +1(如果可以的话,+10)。令人印象深刻的彻底。和非常有趣的结果。我肯定会再看看这个,看看它对我所从事的应用程序中的字符串密集型工作有什么影响。
  • +1 非常好的分析。我一直都知道 SameStr 特别擅长处理不同长度的字符串,但令我惊讶的是 = 实际上对于相同长度的字符串更快。你每天都会学到新东西!
  • 嗯...我无法重现“=”赢得等长字符串的结果...
  • @Andreas,这是我的代码的可下载版本,应该在您的计算机上编译不变,设置为“等长字符串”测试。下载试试看,告诉我:fisiere.sediu.ro/PentruForumuri/StrDemo.dpr
  • 这越来越有趣了。以下是我的一些结果,来自多台机器:fisiere.sediu.ro/PentruForumuri/dual_quad_xeon.txtfisiere.sediu.ro/PentruForumuri/intel_core_2_duo.txt;明天我将在英特尔 i7 上进行测试,可能与您的类似。我的猜测:它与编译器有关,与 CPU 无关。我在 Delphi 2010。如果您不介意,请使用我编译的代码进行测试:fisiere.sediu.ro/PentruForumuri/Project3.zip;只需在其中运行 EXE,它就会进行测试,将结果转储到控制台和 exe 旁边的“MyResult.txt”文件中。
【解决方案2】:

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) 作为 = 运算符的原因目前让我难以理解。我唯一能想到的是,它与用于“=”比较 AnsiStrings 的 LStrEqual 有一个很好的类比。我猜只有编译器的人知道。

很抱歉浪费了您的时间。不过,我将留下答案,因此其他人不必走这条调查路线。

【讨论】:

  • 嗯...但这不应该暗示 = 比 SameStr 快吗?
  • @Anders。不,看我的更新。 '=' 确实会进行转换。它只是使用了一个不同的比较循环,我猜它的性能比 CompareStr 中的低很多。
  • 不是什么大不了的事,但我的名字实际上是“安德烈亚斯”。
【解决方案3】:

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

使用“RandomString(0,0)”示例,SameStr 确实变得更快(大约 20%)。但是话又说回来,如果将第二个字符串设置为 '',则性能几乎相同。经过更多测试后,似乎不是长度差异造成了性能差异,而是空字符串。

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

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

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

【讨论】:

    【解决方案4】:

    一些测试将在未来进一步:

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

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

    相同的文字:
    // = -> 1890 毫秒
    // 比较文本 -> 10900 毫秒
    // CompareStr -> 1895 毫秒

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

    【讨论】:

      猜你喜欢
      • 2011-11-22
      • 2011-08-15
      • 2017-03-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-09
      • 2013-08-23
      • 2016-10-22
      相关资源
      最近更新 更多