Delphi XE2 64-разрядная чрезвычайно медленная производительность во время выполнения строковых процедур
Я переношу некоторые приложения с 32 до 64 бит delphi, которые выполняют большую обработку текста и замечают резкое изменение скорости обработки. Например, некоторые тесты с несколькими процедурами занимают более 200% времени в 64 бит, чем для компиляции до 32 (2000+ мс по сравнению с ~ 900).
Это нормально?
function IsStrANumber(const S: AnsiString): Boolean;
var P: PAnsiChar;
begin
Result := False;
P := PAnsiChar(S);
while P^ <> #0 do begin
if not (P^ in ['0'..'9']) then Exit;
Inc(P);
end;
Result := True;
end;
procedure TForm11.Button1Click(Sender: TObject);
Const x = '1234567890';
Var a,y,z: Integer;
begin
z := GetTickCount;
for a := 1 to 99999999 do begin
if IsStrANumber(x) then y := 0;//StrToInt(x);
end;
Caption := IntToStr(GetTickCount-z);
end;
Ответы
Ответ 1
Тест p^ in ['0'..'9']
медленный в 64-разрядной версии.
Добавлена встроенная функция с тестом для нижней/верхней границы вместо теста in []
плюс тест для пустой строки.
function IsStrANumber(const S: AnsiString): Boolean; inline;
var
P: PAnsiChar;
begin
Result := False;
P := Pointer(S);
if (P = nil) then
Exit;
while P^ <> #0 do begin
if (P^ < '0') then Exit;
if (P^ > '9') then Exit;
Inc(P);
end;
Result := True;
end;
Результаты тестов:
x32 x64
--------------------
hikari 1420 3963
LU RD 1029 1060
В 32-битной основной разнице в скорости есть inline и что P := PAnsiChar(S);
вызовет внешнюю процедуру RTL для проверки nil перед назначением значения указателя, тогда как P := Pointer(S);
просто назначает указатель.
Наблюдая, что цель здесь - проверить, является ли строка числом, а затем преобразовать ее,
почему бы не использовать RTL TryStrToInt()
, который делает все за один шаг и обрабатывает знаки, пробелы также.
Часто при профилировании и оптимизации подпрограмм самое главное - найти правильный подход к проблеме.
Ответ 2
Для этого не существует текущего решения, так как оно вызвано тем фактом, что код для большинства строковых подпрограмм в 64 бит скомпилирован с PUREPASCAL
, определенным, IOW, это простой Delphi, нет ассемблера, в то время как код для многих важных строковых процедур в 32 бит был выполнен с помощью проекта FastCode и в ассемблере.
В настоящее время нет эквивалентов FastCode в 64 бит, и я предполагаю, что команда разработчиков попытается в любом случае попытаться устранить ассемблер, тем более, что они переходят на другие платформы.
Это означает, что оптимизация сгенерированного кода становится все более важной. Я надеюсь, что анонсированный переход к бэкэнду LLVM значительно ускорит большую часть кода, поэтому чистый код Delphi больше не является проблемой.
Так жаль, никакого решения, но, возможно, объяснения.
Update
Как и в XE4, несколько процедур FastCode заменили неоптимизированные подпрограммы, о которых я рассказываю в предыдущих параграфах. Они обычно остаются PUREPASCAL
, но все же они представляют собой хорошую оптимизацию. Таким образом, ситуация не так плоха, как раньше. В процедурах TStringHelper
и простой строки по-прежнему отображаются некоторые ошибки и очень медленный код в OS X (особенно, когда речь идет о преобразовании из Unicode в Ansi или наоборот), но Win64сильная > часть RTL кажется намного лучше.
Ответ 3
Попытайтесь избежать выделения строк в вашем цикле.
В вашем случае может быть задействована подготовка стека к соглашению о вызове x64. Вы пытались сделать IsStrANumber
объявленным как inline
?
Я предполагаю, что это ускорит выполнение.
function IsStrANumber(P: PAnsiChar): Boolean; inline;
begin
Result := False;
if P=nil then exit;
while P^ <> #0 do
if not (P^ in ['0'..'9']) then
Exit else
Inc(P);
Result := True;
end;
procedure TForm11.Button1Click(Sender: TObject);
Const x = '1234567890';
Var a,y,z: Integer;
s: AnsiString;
begin
z := GetTickCount;
s := x;
for a := 1 to 99999999 do begin
if IsStrANumber(pointer(s)) then y := 0;//StrToInt(x);
end;
Caption := IntToStr(GetTickCount-z);
end;
Версия RTL "чистого паскаля" действительно является причиной медленности здесь...
Обратите внимание, что это еще хуже с компилятором FPC 64 бит по сравнению с 32-разрядной версией... Звучит, что компилятор Delphi - это не единственный! 64 бит не означает "быстрее", независимо от того, что говорит маркетинг! Иногда даже наоборот (например, JRE, как известно, медленнее на 64 бит, а новая x32 модель должна быть представлена в Linux, когда речь заходит о размере указателя).
Ответ 4
Код можно записать так: хорошие результаты:
function IsStrANumber(const S: AnsiString): Boolean; inline;
var
P: PAnsiChar;
begin
Result := False;
P := PAnsiChar(S);
while True do
begin
case PByte(P)^ of
0: Break;
$30..$39: Inc(P);
else
Exit;
end;
end;
Result := True;
end;
Intel (R) Core (TM) 2 CPU T5600 @1,83 ГГц
- x32-бит: 2730 мс
- x64-бит: 3260 мс
Intel (R) Pentium (R) D CPU 3,40 ГГц
- x32-бит: 2979 мс
- x64-бит: 1794 мс
Разрыв вышеперечисленного цикла может привести к более быстрому выполнению:
function IsStrANumber(const S: AnsiString): Boolean; inline;
type
TStrData = packed record
A: Byte;
B: Byte;
C: Byte;
D: Byte;
E: Byte;
F: Byte;
G: Byte;
H: Byte;
end;
PStrData = ^TStrData;
var
P: PStrData;
begin
Result := False;
P := PStrData(PAnsiChar(S));
while True do
begin
case P^.A of
0: Break;
$30..$39:
case P^.B of
0: Break;
$30..$39:
case P^.C of
0: Break;
$30..$39:
case P^.D of
0: Break;
$30..$39:
case P^.E of
0: Break;
$30..$39:
case P^.F of
0: Break;
$30..$39:
case P^.G of
0: Break;
$30..$39:
case P^.H of
0: Break;
$30..$39: Inc(P);
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
end;
Result := True;
end;
Intel (R) Core (TM) 2 CPU T5600 @1,83 ГГц
- x32-бит: 2199 мс
- x64-бит: 1934 мс
Intel (R) Pentium (R) D CPU 3,40 ГГц
- x32-бит: 1170 мс
- x64-бит: 1279 мс
Если вы также примените то, что сказал Арно Буше, вы можете сделать это еще быстрее.
Ответ 5
Преимущество 64-разрядной версии - это адресное пространство, а не скорость (если только ваш код не ограничен адресной памятью).
Исторически этот тип кода манипуляции с символами всегда был более медленным на более широких машинах. Это правда, переход от 16-битного 8088/8086 к 32-битовому 386. Ввод 8-разрядного char в 64-битный регистр - это потеря полосы памяти и кэша.
Для скорости вы можете избежать переменных char, использовать указатели, использовать таблицы поиска, использовать бит-parallelism (манипулировать 8 символами в одном 64-битном слове) или использовать инструкции SSE/SSE2.... Очевидно, некоторые из них сделают ваш код CPUID зависимым. Кроме того, откройте окно CPU во время отладки и посмотрите, что компилятор делает глупые вещи "для" вам нравятся бесшумные преобразования строк (особенно вокруг вызовов).
Вы можете попробовать посмотреть некоторые из встроенных подпрограмм Pascal в библиотеке FastCode. НАПРИМЕР. PosEx_Sha_Pas_2, хотя и не так быстро, как версии ассемблера, быстрее, чем RTL-код (в 32-битных).
Ответ 6
Вот две функции. Один проверяет только на положительные числа. Второй проверяет также отрицательный. И не ограничивается размером. Второй - в 4 раза быстрее обычного Val
.
function IsInteger1(const S: String): Boolean; overload;
var
E: Integer;
Value: Integer;
begin
Val(S, Value, E);
Result := E = 0;
end;
function IsInteger2(const S: String): Boolean; inline;
var
I: Integer;
begin
Result := False;
I := 0;
while True do
begin
case Ord(S[I+1]) of
0: Break;
$30..$39:
case Ord(S[I+2]) of
0: Break;
$30..$39:
case Ord(S[I+3]) of
0: Break;
$30..$39:
case Ord(S[I+4]) of
0: Break;
$30..$39:
case Ord(S[I+5]) of
0: Break;
$30..$39:
case Ord(S[I+6]) of
0: Break;
$30..$39:
case Ord(S[I+7]) of
0: Break;
$30..$39:
case Ord(S[I+8]) of
0: Break;
$30..$39:
case Ord(S[I+9]) of
0: Break;
$30..$39:
case Ord(S[I+10]) of
0: Break;
$30..$39: Inc(I, 10);
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
end;
Result := True;
end;
function IsInteger3(const S: String): Boolean; inline;
var
I: Integer;
begin
Result := False;
case Ord(S[1]) of
$2D,
$30 .. $39:
begin
I := 1;
while True do
case Ord(S[I + 1]) of
0:
Break;
$30 .. $39:
case Ord(S[I + 2]) of
0:
Break;
$30 .. $39:
case Ord(S[I + 3]) of
0:
Break;
$30 .. $39:
case Ord(S[I + 4]) of
0:
Break;
$30 .. $39:
case Ord(S[I + 5]) of
0:
Break;
$30 .. $39:
case Ord(S[I + 6]) of
0:
Break;
$30 .. $39:
case Ord(S[I + 7]) of
0:
Break;
$30 .. $39:
case Ord(S[I + 8]) of
0:
Break;
$30 .. $39:
case Ord(S[I + 9]) of
0:
Break;
$30 .. $39:
case Ord(S[I + 10]) of
0:
Break;
$30 .. $39:
case Ord(S[I + 11]) of
0:
Break;
$30 .. $39:
case Ord(S[I + 12]) of
0:
Break;
$30 .. $39:
case Ord(S[I + 13]) of
0:
Break;
$30 .. $39:
Inc(I, 13);
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
else
Exit;
end;
end;
else
Exit;
end;
Result := True;
end;