2010-04-15 16 views
5

Acabo de entregar esta función en una tarea. Está hecho (por lo tanto, no hay etiqueta de tarea). Pero me gustaría ver cómo se puede mejorar esto.Ayuda para mejorar una función de ensamblaje simple

Esencialmente, la función suma los cuadrados de todos los números enteros entre 1 y el número dado, utilizando la siguiente fórmula:

n(n+1)(2n+1)/6 

Dónde n es el número máximo.

La función siguiente se realiza para detectar cualquier desbordamiento y devolver 0 en caso de que se produzca.

UInt32 sumSquares(const UInt32 number) 
{ 
    int result = 0; 
    __asm 
    { 
     mov eax, number //move number in eax 
     mov edx, 2  //move 2 in edx 
     mul edx   //multiply (2n) 
     jo end   //jump to end if overflow 
     add eax, 1  //addition (2n+1) 
     jo end   //jump to end if overflow 
     mov ecx, eax  //move (2n+1) in ecx 

     mov ebx, number //move number in ebx 
     add ebx, 1  //addition (n+1) 
     jo end   //jump to end if overflow 

     mov eax, number //move number in eax for multiplication 
     mul ebx   //multiply n(n+1) 
     jo end   //jump to end if overflow 
     mul ecx   //multiply n(n+1)(2n+1) 
     jo end   //jump to end if overflow 
     mov ebx, 6  //move 6 in ebx 
     div ebx   //divide by 6, the result will be in eax 

     mov result, eax //move eax in result 

end: 
    } 

    return result; 
} 

Básicamente, quiero saber qué puedo mejorar allí. En términos de mejores prácticas principalmente. Una cosa parece obvia: verificación de desbordamiento más inteligente (con una sola comprobación para cualquier entrada máxima que provoque un desbordamiento).

+1

CodeReview no es todavía fuera de beta, pero esto sería un buen candidato, una vez que es. –

Respuesta

8
mov eax, number //move number in eax 
    mov ecx, eax  //dup in ecx 
    mul ecx   //multiply (n*n) 
    jo end   //jump to end if overflow 
    add eax, ecx  //addition (n*n+n); can't overflow 
    add ecx, ecx  //addition (2n); can't overflow 
    add ecx, 1  //addition (2n+1); can't overflow 
    mul ecx   //multiply (n*n+n)(2n+1) 
    jo end   //jump to end if overflow 
    mov ecx, 6  //move 6 in ebx 
    div ecx   //divide by 6, the result will be in eax 

    mov result, eax //move eax in result 

de reducción de resistencia: en lugar de añadir multiplican.

Por análisis, menos comprobaciones de desbordamiento (puede hacerlo mejor como describió).

Mantener los valores en los registros en lugar de volver al argumento en la pila.

Elija los registros cuidadosamente para que los valores que se pueden reutilizar no se sobrescriban.

+0

Agregar no puede desbordar? ¿Estoy leyendo esto bien? – MPelletier

+3

No se puede desbordar porque n * n no, por lo que n * n + n no. El mayor n tal que n * n no se desbordará es 0xffff. 0xffff * 0xffff + 0xffff = 0xffff0000. Dado que en ese punto n <= 0xffff, 2n + 1 es como máximo 0x1ffff, nuevamente no hay desbordamiento. –

+0

Te votaría dos veces ahora si pudiera. ¡Eso es matemática sólida! – MPelletier

3
mov eax, number ; = n 
cmp eax, 0x928  ; cannot handle n >= 0x928 
jnc end 
shl eax, 1   ; = n(2) 
add eax, 3   ; = n(2)+3 
mov ebx, number 
mul ebx   ; = n(n(2)+3) 
add eax, 1   ; = n(n(2)+3)+1 
mov ebx, number 
mul ebx   ; = n(n(n(2)+3)+1) = n(n+1)(2n+1) 
mov ebx, 6 
div ebx 
mov result, eax 

En lugar de la comprobación de desbordamiento, esta solución comprueba la entrada contra el valor máximo conocido que la función puede manejar. Tenga en cuenta que la última multiplicación puede desbordarse, y se desbordará para cualquier entrada number mayor que 0x509. Verificar con un valor conocido en lugar de confiar en las comprobaciones de desbordamiento permite que la función maneje casi el doble de valores de entrada. De hecho, la función puede manejar cada entrada cuyo resultado se ajusta dentro de 32 bits.

3
UInt32 sumSquares(const UInt32 number) 
{ 
    __asm 
    { 
    mov eax, number  // n 
    cmd eax, MAX_VALUE 
    jg bad_value 

    lea ebx, [eax+1] // n + 1 
    lea ecx, [2*eax+1] // 2n + 1 

    mul ebx 
    mul ecx 

    shr eax, 1   // divide by 2 
    mov ebx, 2863311531 // inverse of 3 
    mul ebx    // divide by 3 

    ret 

    bad_value: 
    xor eax, eax  // return 0 
    ret 
    } 
} 

http://blogs.msdn.com/devdev/archive/2005/12/12/502980.aspx

Spara

+0

Los trucos de lea son interesantes para hablar; Creo que pueden ser más lentos que la aritmética directa en los procesadores más nuevos. La multiplicación por inversión es una buena técnica que no tuve la energía para describir ayer; Creo que su resultado está en edx, por lo que debe moverlo a eax. –

+0

@Doug Currie No estoy seguro de qué le hace pensar que el resultado está en EDX. Todas las operaciones se realizan en EAX y los bits de orden superior de la multiplicación están en EDX (que se descartan). ¿Me estoy perdiendo de algo? Interesantes trucos aritméticos son ciertamente posibles, pero hiciste un buen trabajo de eso antes, así que opté por un ejemplo más directo. – Sparafusile

+0

Supuse que se estaba multiplicando por 1431655765 (2^32/3). Leyendo más de cerca, veo que no lo eres. La multiplicación por inversión que está utilizando solo funciona cuando el dividendo es un múltiplo exacto del divisor. Sorprendentemente este es el caso de este algoritmo. +1 –

Cuestiones relacionadas