Hoy quiero mostrar una pequeña diferencia entre el for y el while. Para ello vamos a empezar con el siguiente fragmento de código.
char *cptr; char *texto = "abcdefghijklmnopq"; for (cptr=texto;*cptr;cptr++) { printf("%c",*cptr); } cptr = texto; while (*cptr) { printf("%c",*cptr); cptr++; }
Utilizando el compilador de Microsoft sin optimizaciones obtenemos.
for (cptr=texto;*cptr;cptr++) mov eax,dword ptr [ebp-14h] mov dword ptr [ebp-10h],eax jmp 1 2:mov ecx,dword ptr [ebp-10h] add ecx,1 mov dword ptr [ebp-10h],ecx 1:mov edx,dword ptr [ebp-10h] movsx eax,byte ptr [edx] test eax,eax je 3 |
cptr = texto; while (*cptr) 3:mov eax,dword ptr [ebp-14h] mov dword ptr [ebp-10h],eax mov ecx,dword ptr [ebp-10h] movsx edx,byte ptr [ecx] test edx,edx je 4 |
printf("%c",*cptr); mov ecx,dword ptr [ebp-10h] movsx edx,byte ptr [ecx] push edx push offset string "%c" call printf add esp,8 jmp 2 |
printf("%c",*cptr); mov eax,dword ptr [ebp-10h] movsx ecx,byte ptr [eax] push ecx push offset string "%c" call printf add esp,8 cptr++; mov edx,dword ptr [ebp-10h] add edx,1 mov dword ptr [ebp-10h],edx jmp 3 4: |
Como se aprecia utilizando el while hemos eliminado la instruccion jmp 1 el resto del código se mantiene inalterado. Este comportamiento que acabamos de observar se debe a que el ciclo for genera las instrucciones de lazo al comienzo.
Veamos que ocurre si necesitamos ignorar el tratamiento del caracter b, es decir este no sera mostrado por pantalla.
for (cptr=texto;*cptr;cptr++)
{
if (*cptr =='b') continue;
printf("%c",*cptr);
} |
cptr = texto;
while (*cptr)
{
if (*cptr != 'b')
{
printf("%c",*cptr);
}
cptr++;
} |
Como podemos apreciar el uso del for genera un código mas limpio, ya que utilizando la sentencia continue se puede ir a la proxima iteración, mientras que en un while hay que invertir la condicion para poder ejecutar la instruccion de iteracion como si de un else se tratara.
for (cptr=texto;*cptr;cptr++) ... if (*cptr =='b') continue; mov ecx,dword ptr [ebp-10h] movsx edx,byte ptr [ecx] cmp edx,62h jne 5 jmp 2 printf("%c",*cptr); 5: .. |
while (*cptr) ... if (*cptr != 'b') mov edx,dword ptr [ebp-10h] movsx eax,byte ptr [edx] cmp eax,62h je 6 cptr++; 6: ... |
No me explico porque el compilador utilizo dos instrucciones de salto en el caso del for, de no haber sido asi los dos ejemplos serían idénticos. A modo de resumen utilizando el while hemos conseguido reducir una instruccion, si utilizamos la sentencia continue se genera la misma cantidad de instrucciones en los dos casos (o debería), eso si el codigo con while se vuelve mas extenso visualmente
En estos ejemplos sencillos donde se esta iterando una cadena de caracteres se pueden eliminar un par de instrucciones más si almacenamos el valor del caracter en una variable temporal.
char c;
cptr = texto;
while (*cptr)
{
c = *cptr;
if (c != 'b')
{
printf("%c",c);
}
cptr++
}
if (*cptr !='b') mov edx,dword ptr [ebp-10h] movsx eax,byte ptr [edx] cmp eax,62h je 6 printf("%c",*cptr); mov ecx,dword ptr [ebp-10h] movsx edx,byte ptr [ecx] push edx push offset string "%c" call printf add esp,8 jmp 2 |
c = *cptr; mov ecx,dword ptr [ebp-10h] mov dl,byte ptr [ecx] mov byte ptr [ebp-14h],dl if (c != 'b') movsx edx,byte ptr [ebp-14h] cmp edx,62h je 6 printf("%c",c); movsx ecx,byte ptr [ebp-14h] push ecx push offset string "%c" call printf add esp,8 jmp 2 |
A pesar de utilizar 3 instrucciones para inicializar la variable c , cada vez que accedemos a *cptr nos ahorramos una instrucción, en tres accesos amortizamos el coste. Quiero aclarar que esto no es aplicable cuando iteramos entre estructuras. Se debe utilizar cuando accedemos continuamente a un mismo valor a traves de un puntero.
Vamos a realizar las ultimas modificaciones sobre el ciclo par intentar optimizarlo un poco mas, si el compilador nos ayuda claro esta.
while (c=*(cptr++))
{
if (*cptr != 'b')
{
printf("%c",*cptr);
}
}
while (*cptr) { c=*cptr; cptr++; mov ecx,dword ptr [ebp-10h] movsx edx,byte ptr [ecx] test edx,edx je 4 mov eax,dword ptr [ebp-10h] mov cl,byte ptr [eax] mov byte ptr [ebp-14h],cl mov edx,dword ptr [ebp-10h] add edx,1 mov dword ptr [ebp-10h],edx |
while (c=*(cptr++)) { mov edx,dword ptr [ebp-10h] ecx = cptr mov al,byte ptr [edx] mov byte ptr [ebp-14h],al c = *cptr movsx ecx,byte ptr [ebp-14h] mov edx,dword ptr [ebp-10h] cptr++ add edx,1 mov dword ptr [ebp-10h],edx test ecx,ecx c == 0 je 4 |
Como imaginaba, el compilador no ha sido de gran ayuda, ya que ha cargado el valor de cptr en edx por segunda vez sin necesidad; además en vez de utilizar un test al,al que ya contiene el valor de c, ha utilizado ecx resultando en una instruccion más. A pesar de todo el código es una instruccion menor. Eso si aqui se ha hecho una comparación desigual, pero no va mejorar mucho si utilizamos c = *(cptr++);en el código de la izquierda.
No me cabe duda el compilador no optimiza lo mas obvio, es cierto que he compilado sin optimizaciones. De todas formas voy a realizar pruebas utilizando gcc o g++ y expondre mis resultados
No hay comentarios:
Publicar un comentario