Delphi: Почему не Делает Оператора сравнения Двоичной строки (=) используйте SameStr?

Это общеизвестно это SameStr(S1, S2) быстрее, чем S1 = S2, где var S1, S2: string в Delphi.

(И, конечно, 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;

и функции "часов" просто переносятся 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

Итак, почему компилятор не использует SameStr кодируйте при записи блока для S1 = S2?

Обновление

После чтения превосходного ответа 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) как = оператор был изменен между D2009 и D2010. Кто-либо может подтвердить это?

14
задан Andreas Rejbrand 29 July 2010 в 20:09
поделиться

3 ответа

SameStr имеет необязательный третий параметр: LocaleOptions. Вы получите поведение, подобное "=", если не укажете третий параметр: независимое от регистра языкового стандарта сравнение.

Вы могли бы подумать, что это то же самое, что и двоичное сравнение, но это не так.

Начиная с D2009, строки Delphi имеют полезную нагрузку «кодовую страницу» в дополнение к длине и счетчику ссылок.

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

Когда вы выполняете String1 = String2 , вы говорите компилятору игнорировать всю информацию о строке и просто выполнять двоичное сравнение (для этого используется UStrEqual).

Когда вы выполняете SameStr или CompareStr (который используется SameStr), Delphi сначала проверяет строку на предмет Unicode (UTF-16LE), а если нет, преобразует их перед делает фактическую работу.

Вы можете увидеть это, когда посмотрите на реализацию CompareStr (без третьего параметра), которая после начальной оптимизации проверяет, являются ли аргументы строками Unicode, и, если нет, преобразует их с помощью UStrFromLStr.

Обновление:

Фактически, UStrEqual (посредством UStrCmp) также выполняет преобразования, например CompareStr, он смотрит на elemSize строк, чтобы решить, являются ли они Unicode или нет, и преобразует их, если это не так.

Итак, причина, по которой компилятор не использует SameStr (CompareStr) для оператора = , в настоящий момент ускользает от меня. Единственное, что я могу придумать, это то, что у него есть хорошая аналогия с LStrEqual, используемым для '=' - сравнения AnsiStrings. Думаю, знают только компиляторы.

Простите, что зря потратил ваше время. Я оставляю ответ, чтобы другим не пришлось идти по этому пути расследования.

4
ответ дан 1 December 2019 в 12:26
поделиться

На моей системе "=" быстрее, чем SameStr.

SameStr действительно быстрее (примерно на 20%) на примере "RandomString(0,0)". но опять же, если это вторая строка, которая установлена в '', производительность почти одинаковая. После дополнительного тестирования, похоже, что разница в производительности не в длине, а в пустой строке.

Cosmin Prund только что опубликовал гораздо более тщательный анализ...

Одна вещь, которую следует иметь в виду, это то, что для функций, которые настолько малы ( 1 миллион проверяется за несколько мс), фактический процессор, на котором выполняется код, может иметь большое значение. Код ASM может быть немного более дружественным к BPU одного процессора, чем другого... Или какая-то инструкция может выполняться более эффективно на разных процессорах. Выравнивание данных может влиять на это. Промах в кэше. Это лишь несколько примеров того, что на аппаратном уровне может повлиять на конечную производительность.

Для справки, тесты я проводил на процессоре Phenom X4.

0
ответ дан 1 December 2019 в 12:26
поделиться

Ответ

Все зависит от того, как вы строите случайные строки. Я использовал модифицированную версию кода, потому что очень немногие из нас имеют блок 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 результаты в формате tab-delimited, чтобы я мог скопировать/вставить их в 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

Теперь главный вопрос, с РЕАЛЬНЫМ кодом, какова вероятность того, что строки будут равны? И насколько большой должна быть разница, чтобы 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

Заключение

Я не уверен. Я не ожидал, что это связано с длиной строки! Я ожидал, что обе функции будут молниеносно обрабатывать строки разной длины, но этого не происходит.

19
ответ дан 1 December 2019 в 12:26
поделиться
Другие вопросы по тегам:

Похожие вопросы: