Delphi XE2 64-bit performances d'exécution extrêmement lentes sur les routines string

je transfère quelques applications de 32 à 64 bits delphi, qui font beaucoup de traitement de texte, et j'ai remarqué un changement extrême dans la vitesse de traitement. Fait quelques tests avec quelques procédures, par exemple, cela prend déjà plus de 200% du temps en 64bits que compiler à 32 (2000 + ms par rapport à ~ 900)

Est-ce normal?

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;
25
demandé sur hikari 2012-06-29 14:49:45

6 réponses

le test p^ in ['0'..'9'] est lent en 64 bits.

ajout d'une fonction inlined avec un test pour la limite inférieure/supérieure au lieu de la in [] test, plus un test pour une chaîne vide.

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;

les résultats de Benchmark:

        x32     x64
--------------------
hikari  1420    3963
LU RD   1029    1060

en 32 bits, la différence de vitesse principale est en lingue et que P := PAnsiChar(S); appellera une routine RTL externe pour une vérification nulle avant d'attribuer la valeur du pointeur, alors que P := Pointer(S); attribue le pointeur.

Observant que le but ici est de tester si une chaîne est un nombre, puis de le convertir, pourquoi ne pas utiliser le RTL TryStrToInt(), qui fait tout en un pas et gère les signes, les blancs aussi.

Souvent lors de l'analyse et de l'optimisation des routines, la chose la plus importante est de trouver la bonne approche du problème.

1
répondu LU RD 2014-02-22 00:03:39

il n'y a pas de solution actuelle pour cela, car il est causé par le fait que le code de la plupart des routines de chaîne en 64 bits est compilé avec PUREPASCAL défini, IOW, il est simple Delphi, aucun assembleur, alors que le code pour beaucoup des routines de chaîne importantes en 32 bits a été fait par le projet FastCode, et en assembleur.

Actuellement, il n'y a pas d'équivalent FastCode en 64 bits, et je suppose que l'équipe de développement va essayer d'éliminer assembleur de toute façon, d'autant plus qu'ils se déplacent à plus de plates-formes.

Cela signifie que l'optimisation du code généré devient de plus en plus important. J'espère que le déplacement annoncé vers un backend LLVM accélérera considérablement la plupart du code, donc le code Delphi pur n'est plus un tel problème.

Donc désolé, pas de solution, mais peut-être une explication.

mise à Jour

à partir de XE4, plusieurs routines de FastCode ont remplacé les routines non optimisées dont je parle dans le paragraphe ci-dessus. paragraphe. Ils sont généralement encore PUREPASCAL, mais ils représentent pourtant une bonne optimisation. Donc, la situation n'est pas aussi mauvaise qu'elle l'était. TStringHelper et les routines simples affichent encore quelques bugs et du code extrêmement lent dans OS X (surtout en ce qui concerne la conversion D'Unicode en Ansi ou vice versa), mais le Win64 partie de la RTL semble être beaucoup mieux.

34
répondu Rudy Velthuis 2013-08-31 13:14:17

Essayez d'éviter toute attribution des chaînes dans votre boucle.

dans votre cas, la préparation de la pile de la convention d'appel x64 pourrait être impliquée. Avez-vous essayé de faire IsStrANumber a déclaré que inline?

je suppose que cela va le rendre plus rapide.

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;

la version "pur pascal" de la RTL est en effet la cause de la lenteur ici...

notez que C'est encore pire avec le compilateur 64 bits FPC, comparé à la version 32 bits... Semblait que l' Delphi compilateur n'est pas le seul! 64 bit ne signifie pas" plus rapide", quoi que le marketing dise! Il est parfois même le contraire (par exemple, le JRE est connu pour être plus lent sur 64 bits, et un nouveau x32 modèle doit être introduit dans Linux lorsqu'il s'agit de la taille du pointeur).

6
répondu Arnaud Bouchez 2012-06-29 15:40:05

le code peut être écrit comme ceci avec de bons résultats de perfomance:

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;
      ..: Inc(P);
    else
      Exit;
    end;
  end;
  Result := True;
end;

Intel(R) Core (TM)2 CPU T5600 @ 1.83 GHz

  • x32 bits : 2730 ms
  • 64 bits : 3260 ms

Intel(R) Pentium (R) d CPU 3.40 GHz

  • x32 bits : 2979 ms
  • 64 bits : 1794 ms

Déroulement de la au-dessus de la boucle peut conduire à une exécution plus rapide:

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;
      ..:
        case P^.B of
          0: Break;
          ..:
            case P^.C of
              0: Break;
              ..:
                case P^.D of
                  0: Break;
                  ..:
                    case P^.E of
                      0: Break;
                      ..:
                        case P^.F of
                          0: Break;
                          ..:
                            case P^.G of
                              0: Break;
                              ..:
                                case P^.H of
                                  0: Break;
                                  ..: 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 GHz

  • x32 bits : 2199 ms
  • 64 bits : 1934 ms

Intel(R) Pentium (R) d CPU 3.40 GHz

  • x32 bits : 1170 ms
  • 64 bits : 1279 ms

si vous appliquez aussi ce qu'Arnaud Bouchez a dit, vous pouvez le rendre encore plus rapide.

5
répondu pani 2012-06-30 23:16:40

l'avantage de 64 bits est dans l'espace d'adresse, pas la vitesse (sauf si votre code est limité par la mémoire adressable).

historiquement, ce genre de code de manipulation de caractères a toujours été plus lent sur les machines plus larges. Il est vrai qu'il est passé du 8088/8086 16 bits au 386 32 bits. Mettre un char 8-bit dans un registre 64-bit est un gaspillage de bande passante de mémoire et de cache.

pour la vitesse, vous pouvez éviter les variables char, utiliser des pointeurs, utiliser des tables de recherche, Utiliser le parallélisme de bits (manipuler 8 caractères en un mot de 64 bits), ou utiliser le SSE/SSE2... instruction. Évidemment, certains d'entre eux rendront votre code CPUID dépendant. En outre, ouvrez la fenêtre CPU pendant le débogage, et cherchez le compilateur faisant des choses stupides "pour" vous aimez les conversions de chaîne silencieuse (surtout autour des appels).

vous pourriez essayer de regarder certaines des routines Pascal natives dans la bibliothèque FastCode. PosEx_Sha_Pas_2, bien que moins rapide que les versions assembleur, est plus rapide que le code RTL (en 32 bits).

1
répondu Guy Gordon 2012-06-30 06:58:21

Voici deux fonctions. On ne vérifie que les nombres positifs. La deuxième vérifie négatif aswell. Et n'est pas limitée à la taille. La seconde est 4x plus rapide que la normale 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;
      ..:
        case Ord(S[I+2]) of
          0: Break;
          ..:
            case Ord(S[I+3]) of
              0: Break;
              ..:
                case Ord(S[I+4]) of
                  0: Break;
                  ..:
                    case Ord(S[I+5]) of
                      0: Break;
                      ..:
                        case Ord(S[I+6]) of
                          0: Break;
                          ..:
                            case Ord(S[I+7]) of
                              0: Break;
                              ..:
                                case Ord(S[I+8]) of
                                  0: Break;
                                  ..:
                                    case Ord(S[I+9]) of
                                      0: Break;
                                      ..: 
                                        case Ord(S[I+10]) of
                                          0: Break;
                                          ..: 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
    D,
     .. :
    begin
      I := 1;
      while True do
      case Ord(S[I + 1]) of
        0:
        Break;
         .. :
        case Ord(S[I + 2]) of
          0:
          Break;
           .. :
          case Ord(S[I + 3]) of
            0:
            Break;
             .. :
            case Ord(S[I + 4]) of
              0:
              Break;
               .. :
              case Ord(S[I + 5]) of
                0:
                Break;
                 .. :
                case Ord(S[I + 6]) of
                  0:
                  Break;
                   .. :
                  case Ord(S[I + 7]) of
                    0:
                    Break;
                     .. :
                    case Ord(S[I + 8]) of
                      0:
                      Break;
                       .. :
                      case Ord(S[I + 9]) of
                        0:
                        Break;
                         .. :
                        case Ord(S[I + 10]) of
                          0:
                          Break;
                           .. :
                          case Ord(S[I + 11]) of
                            0:
                            Break;
                             .. :
                            case Ord(S[I + 12]) of
                              0:
                              Break;
                               .. :
                              case Ord(S[I + 13]) of
                                0:
                                Break;
                                 .. :
                                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;
1
répondu user3323367 2014-02-23 14:26:09