este es un fragmento de código de ensamblaje
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov edx, len ;message length
mov ecx, msg ;message to write
mov ebx, 1 ;file descriptor (stdout)
mov eax, 4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax, 1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db 'Hello, world!',0xa ;our dear string
len equ $ - msg ;length of our dear string
Dado un sistema informático específico, ¿es posible predecir con precisión el tiempo de ejecución real de un código de ensamblaje?
Respuestas:
Solo puedo citar del manual de una CPU bastante primitiva, un procesador 68020 de alrededor de 1986: "Calcular el tiempo de ejecución exacto de una secuencia de instrucciones es difícil, incluso si tienes un conocimiento preciso de la implementación del procesador". Que no tenemos. Y en comparación con un procesador moderno, esa CPU era primitiva .
No puedo predecir el tiempo de ejecución de ese código, y tú tampoco. Pero ni siquiera puede definir qué es el "tiempo de ejecución" de un fragmento de código, cuando un procesador tiene memorias caché masivas y capacidades desordenadas enormes. Un procesador moderno típico puede tener 200 instrucciones "en vuelo", es decir, en varias etapas de ejecución. Entonces, el tiempo desde tratar de leer el primer byte de instrucción hasta retirar la última instrucción puede ser bastante largo. Pero la demora real para todo otro trabajo que el procesador necesita hacer puede ser (y típicamente es) mucho menor.
Por supuesto, hacer dos llamadas al sistema operativo hace que esto sea completamente impredecible. No sabes qué hace realmente "escribir en stdout", por lo que no puedes predecir la hora.
Y no puede saber la velocidad del reloj de la computadora en el momento preciso en que ejecuta el código. Puede estar en algún modo de ahorro de energía, la computadora puede haber reducido la velocidad del reloj porque se calienta, por lo que incluso el mismo número de ciclos de reloj puede tomar diferentes cantidades de tiempo.
En general: totalmente impredecible.
fuente
No puede hacer esto en general, pero en algunos sentidos, puede hacerlo, y ha habido algunos casos históricos en los que realmente tuvo que hacerlo.
El Atari 2600 (o Atari Video Computer System) fue uno de los primeros sistemas de videojuegos domésticos y se lanzó por primera vez en 1978. A diferencia de los sistemas posteriores de la era, Atari no podía permitirse darle al dispositivo un búfer de cuadros, lo que significa que la CPU tenía ejecutar código en cada línea de exploración para determinar qué producir: si este código tomara 17.08 microsegundos para ejecutarse (el intervalo HBlank), los gráficos no se establecerían correctamente antes de que la línea de exploración comenzara a dibujarlos. Peor aún, si el programador quería dibujar contenido más complejo de lo que normalmente permitía Atari, tenía que medir los tiempos exactos para las instrucciones y cambiar los registros gráficos a medida que se dibujaba el haz, con un lapso de 57,29 microsegundos para toda la línea de exploración.
Sin embargo, el Atari 2600, como muchos otros sistemas basados en el 6502, tenía una característica muy importante que permitía la cuidadosa administración del tiempo requerida para este escenario: la CPU, la RAM y la señal de TV se salieron de los relojes basados en el mismo maestro reloj. La señal de TV salió de un reloj de 3.98 MHz, dividiendo las veces anteriores en un número entero de "relojes de color" que administraban la señal de TV, y un ciclo de los relojes de CPU y RAM fue exactamente tres relojes de color, permitiendo que el reloj de la CPU sea Una medida precisa del tiempo en relación con la señal de TV de progreso actual. (Para obtener más información sobre esto, consulte la Guía del programador de Stella , escrita para el emulador Stella Atari 2600 ).
Este entorno operativo, además, significaba que cada instrucción de CPU tenía una cantidad definida de ciclos que tomaría en cada caso, y muchos desarrolladores de 6502 publicaron esta información en tablas de referencia. Por ejemplo, considere esta entrada para la
CMP
instrucción (Comparar memoria con acumulador), tomada de esta tabla :Utilizando toda esta información, Atari 2600 (y otros desarrolladores de 6502) pudieron determinar exactamente cuánto tiempo tardó en ejecutarse su código y crear rutinas que hicieron lo que necesitaban y cumplían con los requisitos de sincronización de señal de TV de Atari. Y debido a que este tiempo era tan exacto (especialmente para instrucciones que perdían el tiempo como NOP), incluso pudieron usarlo para modificar los gráficos a medida que se dibujaban.
Por supuesto, el 6502 de Atari es un caso muy específico, y todo esto es posible solo porque el sistema tenía todo lo siguiente:
Todas estas cosas se unieron para crear un sistema donde era posible crear conjuntos de instrucciones que tomaron una cantidad exacta de tiempo, y para esta aplicación, eso es exactamente lo que se exigía. La mayoría de los sistemas no tienen este grado de precisión simplemente porque no es necesario: los cálculos se realizan cuando se realizan o si se necesita una cantidad de tiempo exacta, se puede consultar un reloj independiente. Pero si la necesidad es correcta (como en algunos sistemas integrados), todavía puede aparecer y podrá determinar con precisión cuánto tiempo tarda su código en ejecutarse en estos entornos.
Y también debo agregar el gran descargo de responsabilidad masivo de que todo esto solo se aplica a la construcción de un conjunto de instrucciones de ensamblaje que llevará una cantidad exacta de tiempo. Si lo que desea hacer es tomar una pieza arbitraria de ensamblaje, incluso en estos entornos, y preguntar "¿Cuánto tiempo lleva ejecutar esto?", Categóricamente no puede hacer eso, ese es el problema de detención , que se ha demostrado que no tiene solución.
EDITAR 1: En una versión anterior de esta respuesta, dije que el Atari 2600 no tenía forma de informar al procesador de dónde estaba en la señal de TV, lo que lo obligó a mantener todo el programa contado y sincronizado desde el principio. Como se me señaló en los comentarios, esto es cierto para algunos sistemas como el ZX Spectrum, pero no es cierto para el Atari 2600, ya que contiene un registro de hardware que detiene la CPU hasta que se produzca el siguiente intervalo de supresión horizontal, así como una función para comenzar el intervalo de supresión vertical a voluntad. Por lo tanto, el problema de contar los ciclos se limita a cada línea de exploración, y solo se vuelve exacto si el desarrollador desea cambiar el contenido a medida que se dibuja la línea de exploración.
fuente
Hay dos aspectos en juego aquí
Como señala @ gnasher729, si conocemos las instrucciones exactas para ejecutar, aún es difícil estimar el tiempo de ejecución exacto debido a cosas como el almacenamiento en caché, la predicción de ramas, el escalado, etc.
Sin embargo, la situación es aún peor. Dado un trozo de ensamblaje, es imposible saber qué instrucciones se ejecutarán, o incluso saber cuántas instrucciones se ejecutarán. Esto se debe al teorema de Rice: si pudiéramos determinar eso con precisión, entonces podríamos usar esa información para resolver el problema de detención, lo cual es imposible.
El código de ensamblaje puede contener saltos y ramas, que son suficientes para hacer que la traza completa de un programa sea posiblemente infinita. Se ha trabajado en aproximaciones conservadoras del tiempo de ejecución, que da límites superiores en la ejecución, a través de cosas como la semántica de costos o sistemas de tipo anotado. No estoy familiarizado con nada para el ensamblaje específicamente, pero no me sorprendería si existiera algo así.
fuente
mov
es Turing-Completesys_exit
y, por lo tanto, detenemos el cronómetro. Si restringimos la finalización de programas, lo cual es razonable para una pregunta tan práctica, entonces la respuesta es en realidad sí (siempre que tenga una instantánea perfecta del estado, hw y sw, del sistema justo antes de iniciar el programa).int
s pueden ejecutar código arbitrario, esperar operaciones de E / S arbitrarias, etc.¿La elección del "sistema informático" incluiría microcontroladores? Algunos microcontroladores tienen tiempos de ejecución muy predecibles, por ejemplo, la serie PIC de 8 bits tiene cuatro ciclos de reloj por instrucción a menos que la instrucción se bifurque a una dirección diferente, lea desde flash o sea una instrucción especial de dos palabras.
Las interrupciones obviamente interrumpirán este tipo de timimg, pero es posible hacer mucho sin un controlador de interrupciones en una configuración de "metal desnudo".
Usando el ensamblaje y un estilo de codificación especial, es posible escribir código que siempre tomará el mismo tiempo en ejecutarse. No es tan común ahora que la mayoría de las variantes de PIC tienen temporizadores múltiples, pero es posible.
fuente
En la era de las computadoras de 8 bits, algunos juegos hicieron algo así. Los programadores usarían la cantidad exacta de tiempo necesario para ejecutar las instrucciones, en función de la cantidad de tiempo que tomaron y la velocidad de reloj conocida de la CPU, para sincronizar con los tiempos exactos del hardware de video y audio. En aquellos días, la pantalla era un monitor de tubo de rayos catódicos que recorría cada línea de la pantalla a una velocidad fija y pintaba esa fila de píxeles activando y desactivando el rayo catódico para activar o desactivar los fósforos. Debido a que los programadores necesitaban decirle al hardware de video qué mostrar justo antes de que el rayo llegara a esa parte de la pantalla, y ajustar el resto del código al tiempo restante, llamaron a eso "correr el rayo".
Absolutamente no funcionaría en ninguna computadora moderna, o para un código como su ejemplo.
Por qué no? Aquí hay algunas cosas que arruinarían el tiempo simple y predecible:
La velocidad de la CPU y las recuperaciones de memoria son cuellos de botella en el tiempo de ejecución. Es una pérdida de dinero ejecutar una CPU más rápido de lo que puede obtener instrucciones para ejecutar, o instalar memoria que puede entregar bytes más rápido de lo que la CPU puede aceptarlos. Debido a esto, las computadoras viejas no funcionaban en el mismo reloj. Las CPU modernas funcionan mucho más rápido que la memoria principal. Lo logran al tener instrucciones y cachés de datos. La CPU aún se detendrá si alguna vez necesita esperar bytes que no están en el caché. Por lo tanto, las mismas instrucciones se ejecutarán mucho más rápido si ya están en el caché que si no lo están.
Además, las CPU modernas tienen tuberías largas. Mantienen su alto rendimiento al hacer que otra parte del chip haga un trabajo preliminar en las siguientes instrucciones en la tubería. Esto fallará si la CPU no sabe cuál será la próxima instrucción, lo que puede suceder si hay una rama. Por lo tanto, las CPU intentan predecir saltos condicionales. (No tiene ninguno en este fragmento de código, pero tal vez hubo un salto condicional mal predicho que obstruyó la tubería. Además, una buena excusa para vincular esa respuesta legendaria). Del mismo modo, los sistemas que llaman
int 80
a atrapar en modo kernel en realidad están utilizando una característica complicada de la CPU, una puerta de interrupción, que introduce un retraso impredecible.Si su sistema operativo utiliza la multitarea preventiva, el hilo que ejecuta este código podría perder su división de tiempo en cualquier momento.
Competir con la viga también solo funcionó porque el programa se ejecutaba en el metal desnudo y golpeaba directamente en el hardware. Aquí, estás llamando
int 80
para hacer una llamada al sistema. Eso transfiere el control al sistema operativo, lo que no le brinda garantía de tiempo. Luego le dice que haga E / S en una secuencia arbitraria, que podría haber sido redirigida a cualquier dispositivo. Es demasiado abstracto para usted decir cuánto tiempo lleva la E / S, pero seguramente dominará el tiempo dedicado a ejecutar instrucciones.Si desea una sincronización exacta en un sistema moderno, debe introducir un ciclo de retardo. Tienes que hacer que las iteraciones más rápidas se ejecuten a la velocidad de la más lenta, ya que no es posible lo contrario. Una de las razones por las que las personas lo hacen en el mundo real es para evitar la filtración de información criptográfica a un atacante que puede tomar el tiempo que las solicitudes tardan más que otras.
fuente
Esto es algo tangencial, pero el transbordador espacial tenía 4 computadoras redundantes que dependían de estar sincronizadas con precisión, es decir, su tiempo de ejecución coincidía exactamente.
El primer intento de lanzamiento del transbordador espacial se eliminó cuando la computadora Backup Flight Software (BFS) se negó a sincronizarse con las cuatro computadoras del Sistema de software de aviónica primario (PASS). Detalles en "The Bug Heard Round the World" aquí . Lectura fascinante sobre cómo se desarrolló el software para que coincida ciclo por ciclo y podría brindarle información interesante.
fuente
Creo que estamos mezclando dos problemas diferentes aquí. (Y sí, sé que otros lo han dicho, pero espero poder expresarlo más claramente).
Primero, necesitamos pasar del código fuente a la secuencia de instrucciones que realmente se ejecuta (que necesita conocer los datos de entrada y el código: ¿cuántas veces da la vuelta a un ciclo? ¿Qué rama se toma después de una prueba? ) Debido al problema de detención, la secuencia de instrucciones puede ser infinita (sin terminación) y no siempre se puede determinar estáticamente, incluso con el conocimiento de los datos de entrada.
Una vez establecida la secuencia de instrucciones a ejecutar, entonces desea determinar el tiempo de ejecución. Eso ciertamente puede estimarse con cierto conocimiento de la arquitectura del sistema. Pero el problema es que en muchas máquinas modernas, el tiempo de ejecución depende en gran medida del almacenamiento en caché de las recuperaciones de memoria, lo que significa que depende tanto de los datos de entrada como de las instrucciones ejecutadas. También depende de adivinar correctamente los destinos de rama condicionales, que nuevamente dependen de los datos. Así que solo será una estimación, no será exacta.
fuente