Подтвердить что ты не робот

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;
4b9b3361

Ответ 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;