Supongamos que me dan una serie de enteros de ancho fijo (es decir, caben en un registro de ancho ), . Quiero calcular la suma en una máquina con aritmética de complemento a 2, que realiza adiciones módulo con semántica envolvente. Eso es fácil, pero la suma puede desbordar el tamaño del registro, y si lo hace, el resultado será incorrecto.
Si la suma no se desborda, quiero calcularla y verificar que no haya desbordamiento, lo más rápido posible. Si la suma se desborda, solo quiero saber que sí, no me importa ningún valor.
Agregar ingenuamente números en orden no funciona, porque una suma parcial puede desbordarse. Por ejemplo, con registros de 8 bits, es válido y tiene una suma de , aunque la suma parcial desborda el rango de registro .
Obviamente, podría usar un registro más grande como acumulador, pero supongamos el caso interesante en el que ya estoy usando el tamaño de registro más grande posible.
Existe una técnica bien conocida para agregar números con el signo opuesto como la suma parcial actual . Esta técnica evita desbordamientos en cada paso, a costa de no ser amigable con el caché y no aprovechar mucho la predicción de rama y la ejecución especulativa.
¿Existe una técnica más rápida que tal vez aproveche el permiso para desbordar sumas parciales y sea más rápida en una máquina típica con un indicador de desbordamiento, un caché, un predictor de rama y ejecución y cargas especulativas?
(Este es un seguimiento de la suma segura de desbordamiento )
fuente
Respuestas:
Puedes añadirnorte números de tamaño w sin desbordamiento si está utilizando ⌈ logn ⌉ + w bits aritméticos. Mi sugerencia es hacer exactamente eso y luego verificar si el resultado está en el rango. Los algoritmos para la aritmética de multiprecisión son bien conocidos (consulte la sección 4.3 de TAOCP si necesita una referencia), a menudo hay soporte de hardware para la adición ( bandera de transporte y agregar con instrucciones de transporte ), incluso sin dicho soporte puede implementarlo sin salto dependiente de datos ( que es bueno para los predictores de salto) y solo necesita una pasada de los datos y puede visitar los datos en el orden más conveniente (que es bueno para el caché).
Si los datos no caben en la memoria, el factor limitante será el IO y qué tan bien logra superponer el IO con el cálculo.
Si los datos se ajustan en la memoria, probablemente tenga⌈ logn ⌉ ≤ w (La única excepción que se me ocurre es un microprocesador de 8 bits que generalmente tiene 64K de memoria), lo que significa que está haciendo aritmética de doble precisión. La sobrecarga sobre un bucle haciendow -la aritmética de bits puede ser solo dos instrucciones (una para firmar extender, la otra para agregar con carry) y un ligero aumento de la presión de registro (pero si estoy en lo cierto, incluso el registro hambriento x86 tiene suficientes registros para que el único acceso de memoria en el bucle interno puede recuperar los datos). Creo que es probable que un procesador OO pueda programar las operaciones adicionales durante la latencia de carga de memoria, por lo que el bucle interno se ejecutará a la velocidad de la memoria y, por lo tanto, el ejercicio será uno de maximizar el uso del ancho de banda disponible (prefetch o técnicas de intercalado podrían ayudar dependiendo de la arquitectura de la memoria).
Considerando el último punto, es difícil pensar en otros algoritmos con mejor rendimiento. Los saltos dependientes de los datos (y por lo tanto no predecibles) están fuera de discusión al igual que varios pases en los datos. Incluso tratar de usar los diversos núcleos del procesador actual sería difícil ya que el ancho de banda de la memoria probablemente estará saturado, pero podría ser una forma fácil de implementar el acceso intercalado.
fuente
En una máquina donde los tipos enteros se comportan como un anillo algebraico abstracto [básicamente significa que se envuelven], uno podría calcular las sumas de los ítems [i] y (ítem [i] >> 16) para hasta aproximadamente 32767 ítems. El primer valor daría los 32 bits más bajos de la suma correcta. El último valor produciría los bits 16-47 de algo cercano a la suma correcta, y el uso del valor anterior puede ajustarse fácilmente para obtener los bits 16-47 de la suma exacta exacta.
El seudocódigo sería algo como:
Después del código anterior, Sum2 y Sum1 juntos deberían producir la suma correcta, independientemente de los desbordamientos intermedios. Si es necesario sumar más de 32768 números, se pueden dividir en grupos de 32768, y después de calcular Sum2 para cada grupo, se puede agregar a una "gran suma" de dos variables para todos los grupos en su conjunto.
En algunos idiomas, el operador de desplazamiento a la derecha podría ser reemplazado por una división por 65536. Eso generalmente funciona cuando se calcula Sum2, pero no cuando se extrae Sum1MSB. El problema es que algunos idiomas redondean las divisiones hacia cero, mientras que aquí es necesario realizar un redondeo de división al siguiente número más bajo (hacia el infinito negativo). Los errores de cálculo de Sum2 se corregirían más adelante, pero los errores de cálculo de Sum2LSB afectarían el resultado final.
Tenga en cuenta que nada en los resultados finales indicaría si alguno de los cálculos que involucran Sum1 se ha "desbordado", pero si se garantiza que los valores se ajustarán limpiamente, el código no debería tener que preocuparse por si se produjo algún desbordamiento.
fuente