performance - que - Rendimiento de Delphi: caso contra si
metodo delphi ppt (4)
Supongo que podría haber cierta superposición con las preguntas anteriores de SO, pero no pude encontrar una pregunta específica de Delphi sobre este tema.
Supongamos que desea comprobar si una variable entera de 32 bits sin signo "MiAcción" es igual a cualquiera de las constantes ACCIÓN1, ACCIÓN2, ..., ACCIÓNn, donde n es, digamos 1000. Supongo que, además de ser más elegante,
case MyAction of
ACTION1: {code};
ACTION2: {code};
...
ACTIONn: {code};
end;
es mucho más rápido que
if MyAction = ACTION1 then
// code
else if MyAction = ACTION2 then
// code
...
else if MyAction = ACTIONn then
// code;
Supongo que la variante if toma tiempo O (n) para completarse (es decir, para encontrar la acción correcta) si la acción correcta ACTIONi tiene un valor alto de i, mientras que la variante de caso tarda mucho menos tiempo (O (1)?) .
- ¿Estoy en lo cierto de que el cambio es mucho más rápido?
- ¿Tengo razón en que el tiempo requerido para encontrar la acción correcta en el caso del interruptor en realidad es independiente de n? Es decir, ¿es cierto que realmente no demora más verificar un millón de casos que verificar 10 casos?
- ¿Cómo funciona esto exactamente?
- Sí, el interruptor es O (1) mientras que las cascadas if son O (n)
- Sí, ver (1)
- Con una mesa auxiliar
El compilador traducirá una declaración de caso en uno de:
- Tabla de dos niveles, utilizando una tabla para asignar el valor a un índice y usando el índice para seleccionar una dirección de una tabla de salto
- Salto indirecto a través de la mesa
- Saltos secuenciales
- Búsqueda binaria: esto es recursivo, por lo que las hojas de la búsqueda binaria pueden usar cualquiera de 2, 3 o 4.
Utiliza heurísticas como el número de casos, el rango de casos, el número de alternativas distintas (cada alternativa puede implementar un rango de valores diferentes), etc.
La intuición para el enunciado de caso es que es una operación O(1)
.
Siempre es bueno verificar la realidad primero ...
Al compilador de Delphi 2010 parece gustarle mucho probar y ramificar. Por ejemplo, el siguiente código simple no se compila en una tabla de sucursal.
var
c: (aaa, bbb, ccc);
begin
case c of
aaa: sleep(0);
bbb: sleep(0);
ccc: sleep(0);
end;
end.
El compilador generará el siguiente código:
Project56.dpr.24: case c of
0040A1C4 0FB6053C0E4100 movzx eax,[$00410e3c]
0040A1CB 2C01 sub al,$01
0040A1CD 7208 jb $0040a1d7
0040A1CF 740F jz $0040a1e0
0040A1D1 FEC8 dec al
0040A1D3 7414 jz $0040a1e9
0040A1D5 EB19 jmp $0040a1f0
Project56.dpr.25: aaa: sleep(0);
0040A1D7 6A00 push $00
0040A1D9 E86EDAFFFF call Sleep
0040A1DE EB10 jmp $0040a1f0
Project56.dpr.26: bbb: sleep(0);
0040A1E0 6A00 push $00
0040A1E2 E865DAFFFF call Sleep
0040A1E7 EB07 jmp $0040a1f0
Project56.dpr.27: ccc: sleep(0);
0040A1E9 6A00 push $00
0040A1EB E85CDAFFFF call Sleep
Incluso los casos más complicados se compilarán en una serie de prueba y salto. Por ejemplo ...
var
c: (aaa, bbb, ccc, eee, fff, ggg, hhh);
begin
case c of
aaa: sleep(0);
bbb: sleep(0);
ccc: sleep(0);
hhh: sleep(0);
end;
end.
... está compilado en ...
Project56.dpr.24: case c of
0040A1C4 0FB6053C0E4100 movzx eax,[$00410e3c]
0040A1CB 2C01 sub al,$01
0040A1CD 720C jb $0040a1db
0040A1CF 7413 jz $0040a1e4
0040A1D1 FEC8 dec al
0040A1D3 7418 jz $0040a1ed
0040A1D5 2C04 sub al,$04
0040A1D7 741D jz $0040a1f6
0040A1D9 EB22 jmp $0040a1fd
Project56.dpr.25: aaa: sleep(0);
0040A1DB 6A00 push $00
0040A1DD E86ADAFFFF call Sleep
0040A1E2 EB19 jmp $0040a1fd
Project56.dpr.26: bbb: sleep(0);
0040A1E4 6A00 push $00
0040A1E6 E861DAFFFF call Sleep
0040A1EB EB10 jmp $0040a1fd
Project56.dpr.27: ccc: sleep(0);
0040A1ED 6A00 push $00
0040A1EF E858DAFFFF call Sleep
0040A1F4 EB07 jmp $0040a1fd
Project56.dpr.28: hhh: sleep(0);
0040A1F6 6A00 push $00
0040A1F8 E84FDAFFFF call Sleep
La causa más probable de este código es que las tablas de salto no funcionan muy bien con las cachés L1 y, debido a esa versión de prueba y salto, pueden ser más rápidas si no hay una gran cantidad de etiquetas de casos presentes.
Una "prueba" para ese razonamiento es el siguiente programa que sí se traduce en una tabla de saltos.
var
b: byte;
begin
case b of
0: sleep(0);
1: sleep(0);
2: sleep(0);
3: sleep(0);
4: sleep(0);
5: sleep(0);
6: sleep(0);
7: sleep(0);
8: sleep(0);
9: sleep(0);
10: sleep(0);
11: sleep(0);
12: sleep(0);
13: sleep(0);
14: sleep(0);
15: sleep(0);
16: sleep(0);
17: sleep(0);
18: sleep(0);
19: sleep(0);
20: sleep(0);
end;
end.
Project56.dpr.12: case b of
0040A178 0FB6C0 movzx eax,al
0040A17B 83F814 cmp eax,$14
0040A17E 0F8728010000 jnbe $0040a2ac
0040A184 FF24858BA14000 jmp dword ptr [eax*4+$40a18b]
...
Project56.dpr.14: 1: sleep(0);
0040A1EB 6A00 push $00
0040A1ED E85ADAFFFF call Sleep
0040A1F2 E9B5000000 jmp $0040a2ac
Project56.dpr.15: 2: sleep(0);
0040A1F7 6A00 push $00
0040A1F9 E84EDAFFFF call Sleep
0040A1FE E9A9000000 jmp $0040a2ac
Project56.dpr.16: 3: sleep(0);
0040A203 6A00 push $00
0040A205 E842DAFFFF call Sleep
0040A20A E99D000000 jmp $0040a2ac
...
Barry podría darnos una respuesta definitiva a esa pregunta. Solo estoy probando y divagando.
Tenga en cuenta que si el valor de MyAction es ponderado, se puede obtener un buen rendimiento con la cascada if..else, donde coloca los casos más probables cerca de la parte superior. No estoy diciendo que compita con la declaración caso / cambio en términos de rendimiento cuando se trata de enteros. Pero si un caso no es adecuado (supongamos que tiene cadenas, por ejemplo), coloque sus pruebas de alto porcentaje en la parte superior.