Para comprobar la ganancia que se podría obtener compilando para Windows 64 bits las aplicaciociones de 32 bits, he hecho un pequeño ejemplo que utiliza aritmética de 64 bits, y que pueda ser utilizado como benchmark comparativo.
Intentando que sea lo más real posible, he configurado el Visual C++ 2005 SP1 para que utilice en x86 instrucciones SSE2 (/arch:SSE2), y así poder manipular la información en bloques de más de 32 bits, aunque como se verá más adelante, el compilador no ha considerado que se obtenga ningún beneficio usándolas.
El programa utilizado, simplemente realiza un bucle de 10 millardos de iteraciones, donde se ejecuta una suma y un decremento. Al finalizar, se muestra en la consola el tiempo invertido.
#include <stdio.h>
#include <time.h>
void __cdecl main (void)
{
unsigned __int64 iCount, iRes;
unsigned int iInicio, iFin;
iInicio=clock();
iRes=0;
for (iCount=1; iCount<=10000000000; iCount++)
{
iRes+=iCount;
iRes--;
}
iFin=clock();
printf("%I64d %d\n", iRes, iFin-iInicio);
getchar();
}
Los tiempos obtenidos han sido:
Arquitectura | Tiempo (milisegundos): | Relativo: |
x86 | 41404 | 100% |
x64 | 20015 | 206% |
Los números cantan, la mejora de rendimiento cuando se usan números de 64 bits, es tremenda, superior al doble.
Pasemos a analizar con algo más de detalle la clave del programa: el bucle principal en C:
iRes=0;
for (iCount=1; iCount<=10000000000; iCount++)
{
iRes+=iCount;
iRes--;
}
Como se puede ver, para cada iteración se debe realizar una comparación de un número de 64 bits con un inmediato de 64 bits (iCount<=10000000000); un incremento de un número de 64 bits (iCount++); una adición de dos números de 64 bits (iRes+=iCount); y por último un decremento en uno de otro número de 64 bits (iRes–). En total 4 operaciones sobre valores de 64 bits.
Una vez compilado el código, el bucle anterior queda traducido en ensamblador x86 así:
xor esi, esi
xor edi, edi
mov ecx, 1
xor eax, eax
$LN3@main:
mov edx, ecx
add edx, -1
mov ebp, eax
adc ebp, -1
add esi, edx
adc edi, ebp
add ecx, 1
adc eax, 0
cmp eax, 2
jb SHORT $LN3@main
ja SHORT $LN8@main
cmp ecx, 1410065408
jbe SHORT $LN3@main
$LN8@main:
Es decir, requiere por cada iteración la ejecución de entre 9 y 13 instrucciones. Por otro lado, el mismo código en versión x64 es:
xor ebx, ebx
mov ecx, 1
mov edi, eax
mov rax, 10000000000
$LL3@main:
lea rbx, QWORD PTR [rbx+rcx-1]
inc rcx
cmp rcx, rax
jbe SHORT $LL3@main
Es decir solamente 4 instrucciones por iteración. Una mejora evidente es que el código actual es single-threaded, por lo que no se beneficia en absoluto de los procesadores multicore, ni del hyperthreading. Como el algoritmo es puramente sintético se podría repartir la carga por partes iguales a cada core, obteniéndose algo muy cercano al 200% de mejora adicional en equipos dual core.
En éste paquete (62 Kb. en formato ZIP), se incluyen el fuente, los binarios x86 y x64, el proyecto, y los listados en ensamblador de ambas versiones.
Muy bonito sino fuera que los int64 casi no se utilizan para nada.
Sería más interesante comparar con los mismos tipos de datos a ver si hay alguna diferencia.
La verdad es que apenas son necesarios los enteros de 64 bits en aplicaciones generales, lo que si es cierto es que determinados cálculos relacionados con proceso de señales o compresión de datos si que lo hacen.
Por supuesto en mucha menor medida que el ejemplo que he puesto, que digamos está al límite del máximo teórico.