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;
и функции "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
После прочтения прекрасного ответа Cosmin Prund, я не мог устоять перед установкой
Strings1[i] := RandomString(40, 40);
Strings2[i] := RandomString(40, 40);
для создания строк одинаковой длины и действительно.
2.74783364614126E-0002
1.96818773095322E-0002
ratio ~ 1.40
Hm... 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
Update
Казалось бы (см. комментарии ниже Cosmin Prund отличный ответ), так как оператор =
был изменен между D2009 и D2010. Кто-нибудь может это подтвердить?
Ответы
Ответ 1
Ответ
Все зависит от того, как вы создаете случайные строки. Я использовал модифицированную версию кода, потому что очень немногие из нас имеют подразделение 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 символов.
alt text http://fisiere.sediu.ro/PentruForumuri/V1_1_to_maxlen.png
Следующий тест, сделанный CreateRandomStringArrays
, возвращает строки длины 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). SameStr()
выигрывает этот раунд:
alt text http://fisiere.sediu.ro/PentruForumuri/V1_rnd_pm_50p.png
Заключение
Я не уверен. Я не ожидал, что это будет связано с длиной строки! Я ожидал бы, что обе функции будут обрабатывать строки разной длины молниеносно, но этого не происходит.
Ответ 2
SameStr имеет дополнительный третий параметр: LocaleOptions. Вы получаете поведение, подобное "=", оставляя третий параметр: независимое сравнение локального значения senstive.
Вы думаете, что это то же самое, что и двоичное сравнение, но это не так.
Так как строки 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.
Update:
Фактически, UStrEqual (посредством UStrCmp) также выполняет преобразования, такие как CompareStr, он смотрит на elemSize строк, чтобы решить, являются ли они Unicode или нет, и преобразует их, если они не являются.
Поэтому причина, по которой компилятор не использует SameStr (CompareStr) для оператора =
, ускользает от меня в данный момент. Единственное, о чем я могу думать, это то, что он имеет хорошую аналогию с LStrEqual, используемым для '=' - сравнить AnsiStrings. Я думаю, что знают только компиляторы.
Извините, что потратил ваше время. Я оставляю ответ, хотя, чтобы другим не пришлось идти по этому маршруту расследования.
Ответ 3
В моей системе "=" быстрее, чем SameStr.
SameStr становится быстрее (около 20%) с примером "RandomString (0,0)". но опять же, если это вторая строка, которая установлена в '', показатели почти одинаковы. После еще нескольких тестов кажется, что разница в длине не влияет на производительность, это пустая строка, которая делает.
Cosmin Prund только что опубликовал гораздо более тщательный анализ...
Следует иметь в виду, что для функций, которые являются такими маленькими (
1 миллион тестов за несколько мсек), фактический процессор, работающий с кодом, может иметь большое значение. Код ASM может быть немного более дружелюбным к BPU из 1 процессора, чем другой... Или некоторые инструкции могут работать более эффективно на разных CPU. Возможно, это повлияет на выравнивание данных. Ошибка кэша. Это всего лишь несколько примеров на аппаратном уровне, которые могут повлиять на конечную производительность.
Для справки, тесты, которые я делал, были на процессоре Phenom X4.
Ответ 4
В будущем будут продолжены некоторые тесты:
- Обновление Delphi Seattle 1
- i5-2500k @4.3Ghz
- 1 миллиард итераций
- сравнение 2 строк, длиной 17 символов
Различные тексты:
//= → 1890 мс
//CompareText → 4500 мс
//CompareStr → 2130 мс
Тот же текст:
//= → 1890 мс
//CompareText → 10900 ms
//CompareStr → 1895 мс
Заключение: = быстрее во всех случаях, однако CompareStr с тем же текстом почти так же быстро, как =. Кроме того, CompareText/Str кажется MUCH медленнее при работе с Ansi-строками.