promoción - Delphi XE2 rendimiento de tiempo de ejecución extremadamente lento de 64 bits en rutinas de cadena
spinning center referidos (6)
Aquí hay dos funciones. Uno solo verifica los números positivos. El segundo también busca negativo. Y no está limitado al tamaño. El segundo es 4 veces más rápido que Val
regular.
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;
Estoy portando algunas aplicaciones de 32 a 64 bits delphi, que hacen un montón de procesamiento de texto, y noté un cambio extremo en la velocidad de procesamiento. Realicé algunas pruebas con algunos procedimientos, por ejemplo, esto lleva más del 200% del tiempo en 64 bits que la compilación en 32 (2000+ ms en comparación con ~ 900)
¿Esto es 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;
El código se puede escribir así con buenos resultados de rendimiento:
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;
CPU Intel (R) Core (TM) 2 T5600 a 1.83GHz
- x32 bits: 2730 ms
- x64 bits: 3260 ms
Intel (R) Pentium (R) D CPU 3.40GHz
- x32 bits: 2979 ms
- x64 bits: 1794 ms
Desenrollar el ciclo anterior puede dar como resultado una ejecución más rápida:
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;
CPU Intel (R) Core (TM) 2 T5600 a 1.83GHz
- x32-bit: 2199 ms
- x64 bits: 1934 ms
Intel (R) Pentium (R) D CPU 3.40GHz
- x32 bits: 1170 ms
- x64 bits: 1279 ms
Si también aplica lo que dijo Arnaud Bouchez, puede hacerlo aún más rápido.
Intenta evitar cualquier asignación de cadenas en tu ciclo.
En su caso, la preparación de la pila de la convención de llamadas x64 podría estar involucrada. ¿ IsStrANumber
hacer que IsStrANumber
declarara en inline
?
Supongo que esto lo hará más rápido.
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 versión "pura pascal" de la RTL es de hecho la causa de la lentitud aquí ...
Tenga en cuenta que es aún peor con el compilador FPC de 64 bits, en comparación con la versión de 32 bits ... ¡Parece que el compilador Delphi no es el único! 64 bits no significa "más rápido", ¡lo que dice el marketing! A veces es incluso lo contrario (por ejemplo, se sabe que el JRE es más lento en 64 bits, y se debe introducir un nuevo modelo x32 en Linux cuando se trata del tamaño del puntero).
La prueba p^ in [''0''..''9'']
es lenta en 64 bits.
Se agregó una función en línea con una prueba para límite inferior / superior en lugar de la prueba in []
, más una prueba para una cadena vacía.
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;
Resultados de referencia:
x32 x64
--------------------
hikari 1420 3963
LU RD 1029 1060
En 32 bits, la diferencia de velocidad principal está en línea y que P := PAnsiChar(S);
llamará a una rutina RTL externa para una comprobación nula antes de asignar el valor del puntero, mientras que P := Pointer(S);
simplemente asigna el puntero.
Observando que el objetivo aquí es probar si una cadena es un número y luego convertirlo, ¿por qué no usar el RTL TryStrToInt()
, que hace todo en un solo paso y maneja los signos, los espacios en blanco también.
A menudo, al perfilar y optimizar las rutinas, lo más importante es encontrar el enfoque correcto para el problema.
La ventaja de 64 bits está en el espacio de direcciones, no en la velocidad (a menos que su código esté limitado por la memoria direccionable).
Históricamente, este tipo de código de manipulación de caracteres siempre ha sido más lento en máquinas más amplias. Era cierto pasar del 8088/8086 de 16 bits al 386 de 32 bits. Poner un char de 8 bits en un registro de 64 bits es una pérdida de ancho de banda de memoria y memoria caché.
Para mayor velocidad, puede evitar variables char, usar punteros, usar tablas de búsqueda, usar bit paralelismo (manipular 8 caracteres en una palabra de 64 bits) o usar las instrucciones SSE / SSE2 ... Obviamente, algunos de estos harán que su código CPUID sea dependiente. Además, abra la ventana de la CPU mientras depura y busque al compilador haciendo estupideces "para" que le gusten las conversiones silenciosas de cadenas (especialmente en las llamadas).
Puede tratar de mirar algunas de las rutinas nativas de Pascal en la Biblioteca de FastCode. EG PosEx_Sha_Pas_2, aunque no tan rápido como las versiones del ensamblador, es más rápido que el código RTL (en 32 bits).
No hay una solución actual para esto, ya que es causada por el hecho de que el código para la mayoría de las rutinas de cadena en 64 bits se compila con PUREPASCAL
definido, IOW, es simple Delphi, sin ensamblador, mientras que el código para muchos de los rutinas de cadena importantes en 32 bits fueron realizadas por el proyecto FastCode y en el ensamblador.
Actualmente, no hay equivalentes FastCode en 64 bits, y supongo que el equipo desarrollador intentará eliminar el ensamblador de todos modos, especialmente porque se están moviendo a más plataformas.
Esto significa que la optimización del código generado se vuelve más y más importante. Espero que la mudanza anunciada a un backend de LLVM acelere gran parte del código considerablemente, por lo que el código Delphi puro ya no es un problema.
Lo siento, no hay solución, pero tal vez una explicación.
Actualizar
A partir de XE4, bastantes rutinas FastCode han reemplazado las rutinas no optimizadas de las que hablo en los párrafos anteriores. Por lo general, siguen siendo PUREPASCAL
, pero representan una buena optimización. Entonces la situación no es tan mala como solía ser. Las TStringHelper
y de cadena simple todavía muestran algunos errores y algunos códigos extremadamente lentos en OS X (especialmente cuando se trata de la conversión de Unicode a Ansi o viceversa), pero la parte de Win64 de la RTL parece ser mucho mejor.