Sé que las operaciones a nivel de bits son tan rápidas en los procesadores modernos, porque pueden operar en 32 o 64 bits en paralelo, por lo que las operaciones a nivel de bits solo requieren un ciclo de reloj. Sin embargo, la adición es una operación compleja que consiste en al menos una y posiblemente hasta una docena de operaciones de bits, por lo que, naturalmente, pensé que sería de 3 a 4 veces más lenta. Me sorprendió ver, después de un punto de referencia simple, que la adición es exactamente tan rápida como cualquiera de las operaciones de bits (XOR, OR, AND, etc.). ¿Alguien puede arrojar luz sobre esto?
73
Respuestas:
La adición es rápida porque los diseñadores de CPU han puesto los circuitos necesarios para hacerlo rápido. Se necesitan muchas más puertas que las operaciones bit a bit, pero es tan frecuente que los diseñadores de CPU han considerado que vale la pena. Ver https://en.wikipedia.org/wiki/Adder_(electronics) .
Ambos pueden hacerse lo suficientemente rápido como para ejecutarse dentro de un solo ciclo de CPU. No son igualmente rápidos (la adición requiere más compuertas y más latencia que una operación bit a bit), pero es lo suficientemente rápido como para que un procesador pueda hacerlo en un ciclo de reloj. Hay una sobrecarga de latencia por instrucción para la lógica de control y decodificación de instrucciones, y la latencia para eso es significativamente mayor que la latencia para hacer una operación bit a bit, por lo que la diferencia entre los dos se ve inundada por esa sobrecarga. La respuesta de AProgrammer y la respuesta de Paul92 explican bien esos efectos.
fuente
Hay varios aspectos.
El costo relativo de una operación bit a bit y una adición. Un sumador ingenuo tendrá una profundidad de puerta que dependerá linealmente del ancho de la palabra. Hay enfoques alternativos, más costosos en términos de puertas, que reducen la profundidad (IIRC, la profundidad depende entonces logarítmicamente del ancho de la palabra). Otros han dado referencias para tales técnicas, solo señalaré que la diferencia también es menos importante de lo que puede parecer teniendo en cuenta el costo de la operación debido a la necesidad de una lógica de control que agregue retrasos.
Luego está el hecho de que los procesadores generalmente están sincronizados (estoy al tanto de algunas investigaciones o diseños no sincronizados para propósitos especiales, pero ni siquiera estoy seguro de que algunos estén disponibles comercialmente). Eso significa que cualquiera que sea la velocidad de una operación, tomará un múltiplo entero del ciclo del reloj.
Finalmente están las consideraciones micro-arquitectónicas: ¿estás seguro de que mides lo que quieres? Hoy en día, los procesadores tienden a ser canalizados, multiescalares, con ejecución fuera de orden y cualquier otra cosa. Eso significa que pueden ejecutar varias instrucciones al mismo tiempo, en varias etapas de finalización. Si quiere mostrar por mediciones que una operación lleva más tiempo que otra, debe tener en cuenta ese aspecto, ya que su objetivo es ocultar esa diferencia. Es muy posible que tenga el mismo rendimiento para operaciones de suma y bit a bit cuando usa datos independientes, pero una medida de la latencia o la introducción de dependencias entre las operaciones puede mostrar lo contrario. Y también debe asegurarse de que el cuello de botella de su medida esté en la ejecución, y no, por ejemplo, en los accesos a la memoria.
fuente
paddw
) a 2 por reloj, pero booleanos (comopand
) a 3 por reloj. (Skylake pone un sumador de vectores en los tres puertos de ejecución de vectores.)Las CPU funcionan en ciclos. En cada ciclo, algo sucede. Por lo general, una instrucción tarda más ciclos en ejecutarse, pero se ejecutan varias instrucciones al mismo tiempo, en diferentes estados.
Por ejemplo, un procesador simple puede tener 3 pasos para cada instrucción: buscar, ejecutar y almacenar. En cualquier momento, se están procesando 3 instrucciones: una se está recuperando, una se está ejecutando y una almacena sus resultados. Esto se llama una tubería y tiene en este ejemplo 3 etapas. Los procesadores modernos tienen tuberías con más de 15 etapas. Sin embargo, además, así como la mayoría de las operaciones aritméticas, generalmente se ejecutan en una etapa (estoy hablando de la operación de sumar 2 números por la ALU, no de la instrucción en sí misma; dependiendo de la arquitectura del procesador, la instrucción puede requerir más ciclos para recuperar argumentos de la memoria, realizar condicionales, almacenar resultados en la memoria).
La duración de un ciclo está determinada por la ruta crítica más larga. Básicamente, es la mayor cantidad de tiempo necesaria para que se complete alguna etapa de la tubería. Si desea acelerar la CPU, debe optimizar la ruta crítica. Si no es posible reducir la ruta crítica per se, puede dividirse en 2 etapas de la tubería, y ahora puede registrar su CPU a casi el doble de frecuencia (suponiendo que no haya otra ruta crítica que le impida hacerlo) ) Pero esto viene con una sobrecarga: debe insertar un registro entre las etapas de la tubería. Lo que significa que realmente no obtienes una velocidad 2 veces mayor (el registro necesita tiempo para almacenar los datos) y has complicado todo el diseño.
Ya existen métodos bastante eficientes para realizar la suma (por ejemplo, llevar sumadores de búsqueda anticipada) y la suma no es una ruta crítica para la velocidad del procesador, por lo que no tiene sentido dividirla en múltiples ciclos.
Además, tenga en cuenta que si bien puede parecerle complicado, en hardware las cosas se pueden hacer en paralelo muy rápido.
fuente
Los procesadores están sincronizados, por lo que incluso si algunas instrucciones pueden hacerse claramente más rápido que otras, bien pueden tomar el mismo número de ciclos.
Probablemente encontrará que los circuitos necesarios para transportar datos entre registros y unidades de ejecución son significativamente más complicados que los sumadores.
Tenga en cuenta que la simple instrucción MOV (registro a registro) hace incluso menos cómputo que la lógica bit a bit, sin embargo, tanto MOV como ADD generalmente toman un ciclo. Si MOV se pudiera hacer el doble de rápido, las CPU se sincronizarían dos veces más rápido y los ADD serían dos ciclos.
fuente
La adición es lo suficientemente importante como para que no espere a que un bit de transporte se propague a través de un acumulador de 64 bits: el término para eso es un sumador de anticipación de transporte y que son básicamente parte de las CPU de 8 bits (y sus ALU) y hacia arriba. De hecho, los procesadores modernos tampoco necesitan mucho más tiempo de ejecución para una multiplicación completa: carry-lookahead es en realidad una herramienta realmente antigua (y relativamente asequible) en la caja de herramientas del diseñador de un procesador.
fuente
lea
instrucción shift + add ).Creo que sería difícil encontrar un procesador que tuviera más ciclos que una operación bit a bit. En parte porque la mayoría de los procesadores deben realizar al menos una adición por ciclo de instrucción simplemente para incrementar el contador del programa. Las simples operaciones bit a bit no son tan útiles.
(Ciclo de instrucción, no ciclo de reloj; por ejemplo, el 6502 toma un mínimo de dos ciclos de reloj por instrucción debido a que no está conectado y no tiene un caché de instrucciones)
El concepto real que puede faltar es el de la ruta crítica : dentro de un chip, la operación más larga que se puede realizar dentro de un ciclo dicta, a nivel de hardware, qué tan rápido se puede sincronizar el chip.
La excepción a esto es la lógica asincrónica (raramente utilizada y apenas comercializada), que realmente se ejecuta a diferentes velocidades dependiendo del tiempo de propagación lógica, la temperatura del dispositivo, etc.
fuente
En el nivel de la puerta, tiene razón en que se necesita más trabajo para hacer la suma y, por lo tanto, lleva más tiempo. Sin embargo, ese costo es lo suficientemente trivial que no importa.
Los procesadores modernos están sincronizados. No puede hacer instrucciones en nada excepto en múltiplos de esta frecuencia de reloj. Si las velocidades de reloj se elevaran, para maximizar la velocidad de las operaciones bit a bit, tendría que gastar al menos 2 ciclos en la suma. Pasaría gran parte de este tiempo esperando porque realmente no necesitaba el tiempo completo de 2 ciclos. Solo necesitabas 1.1 (o algún número como ese). Ahora su chip agrega más lento que todos los demás en el mercado.
Peor aún, el simple acto de agregar o realizar operaciones bit a bit es solo una pequeña parte de lo que sucede durante un ciclo. Debe poder obtener / decodificar instrucciones dentro de un ciclo. Debe poder realizar operaciones de caché dentro de un ciclo. Están sucediendo muchas otras cosas en la misma escala de tiempo que la simple adición o la operación bit a bit.
La solución, por supuesto, es desarrollar una tubería masivamente profunda, dividiendo estas tareas en pequeñas partes que se ajusten al pequeño tiempo de ciclo definido por una operación bit a bit. El Pentium 4 mostró los límites del pensamiento en estos términos de tubería profunda. Surgen todo tipo de problemas. En particular, la ramificación se vuelve notoriamente difícil porque tienes que vaciar la tubería una vez que tienes los datos para determinar qué rama tomar.
fuente
Los procesadores modernos están sincronizados: cada operación toma un número integral de ciclos de reloj. Los diseñadores del procesador determinan la duración de un ciclo de reloj. Hay dos consideraciones allí: una, la velocidad del hardware, por ejemplo, medida como el retraso de una sola compuerta NAND. Esto depende de la tecnología utilizada y de compensaciones como la velocidad frente al uso de energía. Es independiente del diseño del procesador. Dos, los diseñadores deciden que la duración de un ciclo de reloj es igual a n retrasos de una sola puerta NAND, donde n podría ser 10, o 30, o cualquier otro valor.
Esta opción n limita cuán complejas pueden ser las operaciones que se pueden procesar en un ciclo. Habrá operaciones que se pueden hacer en 16 pero no en 15 retrasos NAND. Por lo tanto, elegir n = 16 significa que tal operación se puede hacer en un ciclo, elegir n = 15 significa que no se puede hacer.
Los diseñadores elegirán n para que muchas operaciones importantes se puedan realizar en uno o dos o tres ciclos. n será elegido localmente óptimo: si reemplaza n con n-1, la mayoría de las operaciones serían un poco más rápidas, pero algunas (aquellas que realmente necesitan los retrasos completos de n NAND) serían más lentas. Si pocas operaciones se ralentizaran, de modo que la ejecución general del programa sea más rápida en promedio, entonces habría elegido n-1. También podrías haber elegido n + 1. Eso hace que la mayoría de las operaciones sean un poco más lentas, pero si tiene muchas operaciones que no se pueden realizar en n retrasos, pero se pueden realizar en n + 1, entonces el procesador en general sería más rápido.
Ahora su pregunta: Sumar y restar son operaciones tan comunes que desea poder ejecutarlas en un solo ciclo. Como resultado, no importa que AND, OR, etc. puedan ejecutarse más rápido: todavía necesitan ese ciclo. Por supuesto, la unidad "calculando" AND, OR, etc. tiene mucho tiempo para girar los pulgares, pero eso no se puede evitar.
Tenga en cuenta que no se trata solo de si una operación se puede realizar con n demoras NAND o no: una adición, por ejemplo, se puede hacer más rápido al ser un poco inteligente, aún más rápido al ser muy inteligente, aún un poco más rápido al invertir cantidades extraordinarias de hardware y, por fin, un procesador puede tener una combinación de circuitos muy rápidos, muy caros y un poco más lentos y más baratos, por lo que existe la posibilidad de hacer una operación lo suficientemente rápida gastando más dinero en ella.
Ahora puede hacer que la velocidad del reloj sea tan alta / el ciclo tan corto que solo las operaciones de bit simples se ejecutan en un ciclo y todo lo demás en dos o más. Eso probablemente ralentizaría el procesador. Para las operaciones que toman dos ciclos, generalmente hay gastos generales para mover una instrucción incompleta de un ciclo al siguiente, por lo que dos ciclos no significa que tenga el doble de tiempo para la ejecución. Entonces, para hacer la adición en dos ciclos, no podría duplicar la velocidad del reloj.
fuente
Permítanme corregir algunas cosas que no se mencionaron explícitamente en sus respuestas existentes:
Esto es verdad. Etiquetar una CPU como bit "XX" generalmente (no siempre) significa que la mayoría de sus estructuras comunes (anchos de registro, RAM direccionable, etc.) tienen un tamaño de XX bits (a menudo "+/- 1" o algo así). Pero con respecto a su pregunta, puede asumir con seguridad que una CPU con 32 bits o 64 bits realizará cualquier operación básica de bits en 32 o 64 bits en tiempo constante.
Esta conclusión no es necesariamente el caso. Especialmente las CPU con conjuntos de instrucciones enriquecidos (google CISC vs. RISC) pueden tomar fácilmente más de un ciclo incluso para comandos simples. Con el entrelazado, incluso los comandos más simples pueden descomponerse en fetch-exec-store con 3 relojes (como ejemplo).
No, la suma de enteros es una operación simple; resta también. Es muy fácil implementar sumadores en hardware completo, y hacen sus cosas tan instantáneamente como las operaciones básicas de bits.
Tomará 3-4 veces más transistores, pero en comparación con el panorama general que es descuidado.
Sí: la suma de enteros es una operación bit a bit (con algunos bits más que los otros, pero aún así). No hay necesidad de hacer nada por etapas, no hay necesidad de algoritmos complicados, relojes o cualquier otra cosa.
Si desea agregar más bits que la arquitectura de su CPU, incurrirá en una penalización por tener que hacerlo por etapas. Pero esto está en otro nivel de complejidad (nivel de lenguaje de programación, no nivel de ensamblaje / código de máquina). Este era un problema común en el pasado (o en la actualidad en pequeñas CPU integradas). Para PC, etc., sus 32 o 64 bits son suficientes para que los tipos de datos más comunes comiencen a convertirse en un punto discutible.
fuente
imul rax, rcx
tiene una latencia de 3c y un rendimiento por 1c en la familia Intel Sandybridge y AMD Ryzen). Incluso la multiplicación completa de 64 bits (que produce el resultado de 128 bits en rdx: rax) tiene la misma latencia y rendimiento, pero se implementa como 2 uops (que se ejecutan en paralelo en diferentes puertos). (Consulte agner.org/optimize para obtener tablas de instrucciones y una excelente guía de microarchivos).uint32_t
valores. Esto sigue siendo relevante hoy para int64_t en objetivos de 32 bits. AVR es un microcontrolador RISC de 8 bits, por lo que los enteros de 32 bits requieren 4 instrucciones: godbolt.org/g/wre0fM