Delphi: Почему оператор сравнения двоичных строк (=) не использует SameStr? - PullRequest
14 голосов
/ 12 июля 2010

Общеизвестно, что 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;

и функции "clock" просто обертывают 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?

Update

Прочитав превосходный ответ Космина Прунда, я не удержался от установки

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

Обновление

Казалось бы (см. Комментарии ниже, отличный ответ Космина Прунда) как =оператор был изменен между D2009 и D2010.Кто-нибудь может это подтвердить?

Ответы [ 4 ]

19 голосов
/ 12 июля 2010

Ответ

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

альтернативный текст 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).Опять же, оператор "=" лучше:

альтернативный текст http://fisiere.sediu.ro/PentruForumuri/V1_rnd_p4.png

Следующий тест, у меня есть две строки, каждая из которых имеет длину: StrLen + Random (StrLen div 10).Оператор "=" лучше.

альтернативный текст http://fisiere.sediu.ro/PentruForumuri/V1_rnd_pm_10p.png

... и мой последний тест, строки длиной +/- 50%.Формула: StrLen + Random (StrLen div 2).SameStr() выигрывает в этом раунде:

альтернативный текст http://fisiere.sediu.ro/PentruForumuri/V1_rnd_pm_50p.png

Заключение

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

4 голосов
/ 12 июля 2010

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 (без третьего параметра), которая,после первоначальной оптимизации проверяет, являются ли аргументы строками Юникода, и, если нет, преобразует их с помощью UStrFromLStr.

Обновление:

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

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

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

0 голосов
/ 27 сентября 2016

Некоторые тесты продолжатся в будущем:

  • Delphi Seattle Update 1
  • i5-2500k @ 4,3 ГГц
  • 1 миллиард итераций
  • сравнение 2 строк длиной 17 символов

Другой текст:
// = -> 1890 мс
// CompareText -> 4500 мс
// CompareStr -> 2130 мс

Тот же текст:
// = -> 1890 мс
// CompareText -> 10900 мс
// CompareStr -> 1895 мс

Вывод: = во всех случаях быстрее, однако CompareStr с тем же текстом почти так же быстро, как и =. Кроме того, CompareText / Str кажется намного медленнее при работе со строками Ansi.

0 голосов
/ 12 июля 2010

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

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

Космин Прунд только что опубликовал гораздо более тщательный анализ ...

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...