En informática , ingeniería informática e implementaciones de lenguajes de programación , una máquina de pila es un procesador de computadora o una máquina virtual en la que la interacción principal es mover valores temporales de corta duración hacia y desde una pila de inserción . En el caso de un procesador de hardware, se utiliza una pila de hardware . El uso de una pila reduce significativamente el número requerido de registros de procesador . Las máquinas apilables amplían los autómatas de empuje hacia abajo con operaciones adicionales de carga/almacenamiento o apilamientos múltiples y, por lo tanto, son Turing-completas .
La mayoría o todas las instrucciones de la máquina de pila suponen que los operandos provendrán de la pila y los resultados se colocarán en la pila. La pila contiene fácilmente más de dos entradas o más de un resultado, por lo que se puede calcular un amplio conjunto de operaciones. En el código de máquina de pila (a veces llamado código p ), las instrucciones con frecuencia tendrán solo un código de operación que ordena una operación, sin campos adicionales que identifiquen una constante, un registro o una celda de memoria, lo que se conoce como formato de dirección cero . [1] Se dice que una computadora que opera de tal manera que la mayoría de sus instrucciones no incluyen direcciones explícitas utiliza instrucciones de dirección cero. [2] Esto simplifica enormemente la decodificación de instrucciones. Las bifurcaciones, los inmediatos de carga y las instrucciones de carga/almacenamiento requieren un campo de argumento, pero las máquinas de pila a menudo organizan que los casos frecuentes de estos todavía encajen junto con el código de operación en un grupo compacto de bits . La selección de operandos a partir de resultados anteriores se realiza implícitamente ordenando las instrucciones. Algunos conjuntos de instrucciones de máquinas de pila están destinados a la ejecución interpretativa de una máquina virtual, en lugar de controlar el hardware directamente.
Los operandos constantes enteros son impulsados por instrucciones Push
o Load Immediate
. A menudo se accede a la memoria mediante instrucciones separadas Load
que Store
contienen una dirección de memoria o que calculan la dirección a partir de valores en la pila. Todas las máquinas de pila prácticas tienen variantes de los códigos de operación de carga y almacenamiento para acceder a variables locales y parámetros formales sin cálculos de direcciones explícitos. Esto puede ser mediante compensaciones desde la dirección actual de la parte superior de la pila o mediante compensaciones desde un registro de base de cuadro estable.
El conjunto de instrucciones lleva a cabo la mayoría de las acciones de ALU con operaciones de sufijo ( notación polaca inversa ) que funcionan solo en la pila de expresiones, no en registros de datos ni celdas de memoria principal. Esto puede resultar muy conveniente para ejecutar lenguajes de alto nivel porque la mayoría de las expresiones aritméticas se pueden traducir fácilmente a notación postfija.
Por ejemplo, considere la expresión A *( B - C )+( D + E ), escrita en notación polaca inversa como A B C - * D E + +. Compilar y ejecutar esto en una máquina de pila imaginaria simple tomaría la forma:
# contenido de la pila (más a la izquierda = arriba = más reciente): empujar A # A empujar B# BA empujar C # CBA restar # BC A multiplicar # A*(BC) presione D # DA*(BC) presione E # EDA*(BC) añadir # D+EA*(BC) añadir # A*(BC)+(D+E)
Las operaciones aritméticas 'restar', 'multiplicar' y 'sumar' actúan sobre los dos operandos superiores de la pila. La computadora toma ambos operandos de los valores superiores (más recientes) de la pila. La computadora reemplaza esos dos valores con la diferencia, suma o producto calculado. En otras palabras, los operandos de la instrucción se "sacan" de la pila y sus resultados luego se "empujan" nuevamente a la pila, listos para la siguiente instrucción.
Las máquinas apilables pueden tener su pila de expresiones y su pila de devolución de llamadas separadas o como una estructura integrada. Si se separan, las instrucciones de la máquina apilable se pueden canalizar con menos interacciones y menos complejidad de diseño, por lo que normalmente se ejecutará más rápido.
La optimización del código de pila compilado es bastante posible. Se ha demostrado que la optimización back-end de la salida del compilador mejora significativamente el código [3] [4] y potencialmente el rendimiento, mientras que la optimización global dentro del propio compilador logra mayores beneficios. [5]
Algunas máquinas apiladoras tienen una pila de tamaño limitado, implementada como un archivo de registro. La ALU accederá a esto con un índice. Un archivo de registro grande utiliza muchos transistores y, por lo tanto, este método sólo es adecuado para sistemas pequeños. Algunas máquinas tienen una pila de expresiones en la memoria y una pila de registros separada. En este caso, el software o una interrupción pueden mover datos entre ellos. Algunas máquinas tienen una pila de tamaño ilimitado, implementada como una matriz en RAM, que se almacena en caché mediante una cierta cantidad de registros de direcciones "superiores a la pila" para reducir el acceso a la memoria. Excepto por las instrucciones explícitas de "carga desde la memoria", el orden de uso de los operandos es idéntico al orden de los operandos en la pila de datos, por lo que se puede lograr fácilmente una excelente captación previa.
Considerar X+1
. Se compila en Load X
; Load 1
; Add
. Con una pila almacenada completamente en RAM, esto realiza escrituras y lecturas implícitas de la pila en memoria:
para un total de 5 referencias de caché de datos.
El siguiente paso es una máquina de pila o intérprete con un único registro en la parte superior de la pila. El código anterior entonces hace:
para un total de 3 referencias de caché de datos, en el peor de los casos. Generalmente, los intérpretes no rastrean el vacío porque no es necesario: cualquier valor debajo del puntero de la pila es un valor no vacío y el registro de caché TOS siempre se mantiene activo. Sin embargo, los intérpretes típicos de Java no almacenan en buffer la parte superior de la pila de esta manera porque el programa y la pila tienen una combinación de valores de datos cortos y anchos.
Si la máquina de pila cableada tiene 2 o más registros de pila superior, o un archivo de registro, en este ejemplo se evita todo acceso a la memoria y solo hay 1 ciclo de caché de datos.
Robert proporcionó por primera vez en una conferencia una descripción de un método de este tipo que requiere que solo se mantengan dos valores a la vez en los registros, con un conjunto limitado de operandos predefinidos que se pueden ampliar mediante la definición de más operandos, funciones y subrutinas. S. Barton en 1961. [6] [7]
Ejemplos de conjuntos de instrucciones de pila ejecutados directamente en hardware incluyen
Ejemplos de máquinas de pila virtuales interpretadas en software:
Las máquinas de pila pura son bastante ineficientes para procedimientos que acceden a múltiples campos desde el mismo objeto. El código de máquina de la pila debe recargar el puntero del objeto para cada cálculo de puntero + desplazamiento. Una solución común para esto es agregar algunas características de la máquina de registro a la máquina de pila: un archivo de registro visible dedicado a guardar direcciones e instrucciones de estilo registro para realizar cargas y cálculos simples de direcciones. Es poco común que los registros sean completamente de propósito general, porque entonces no hay una razón sólida para tener una pila de expresiones e instrucciones postfix.
Otro híbrido común es comenzar con una arquitectura de máquina de registro y agregar otro modo de dirección de memoria que emule las operaciones push o pop de máquinas de pila: 'memaddress = reg; reg += instr.displ'. Esto se utilizó por primera vez en la minicomputadora PDP-11 de DEC . [26] Esta característica se implementó en las computadoras VAX y en los microprocesadores Motorola 6800 y M68000 . Esto permitió el uso de métodos de pila más simples en los primeros compiladores. También admitía eficientemente máquinas virtuales que utilizaban intérpretes de pila o código subproceso . Sin embargo, esta característica no ayudó a que el código de la máquina de registro fuera tan compacto como el código de máquina de pila pura. Además, la velocidad de ejecución fue menor que cuando se compilaba bien con la arquitectura de registro. Es más rápido cambiar el puntero de la parte superior de la pila sólo ocasionalmente (una vez por llamada o devolución) en lugar de subirlo y bajarlo constantemente a lo largo de cada instrucción del programa, y es incluso más rápido evitar por completo las referencias a la memoria.
Más recientemente, las llamadas máquinas de pila de segunda generación han adoptado una colección dedicada de registros para que sirvan como registros de direcciones, descargando la tarea de direccionamiento de memoria de la pila de datos. Por ejemplo, MuP21 se basa en un registro llamado "A", mientras que los procesadores GreenArrays más recientes se basan en dos registros: A y B. [23]
La familia de microprocesadores Intel x86 tiene un conjunto de instrucciones de estilo registro (acumulador) para la mayoría de las operaciones, pero usa instrucciones de pila para su x87 , aritmética de punto flotante Intel 8087 , que se remonta al coprocesador iAPX87 (8087) para 8086 y 8088. Es decir, no hay registros de punto flotante accesibles para el programador, sino solo una pila profunda de 8 niveles y 80 bits de ancho. El x87 depende en gran medida de la CPU x86 para recibir asistencia en el desempeño de sus operaciones.
La mayoría de las computadoras actuales (de cualquier estilo de conjunto de instrucciones) y la mayoría de los compiladores utilizan una gran pila de devolución de llamadas en la memoria para organizar las variables locales de corta duración y los enlaces de retorno para todos los procedimientos o funciones actualmente activos. Cada llamada anidada crea un nuevo marco de pila en la memoria, que persiste hasta que se completa la llamada. Esta pila de devolución de llamadas puede ser administrada completamente por el hardware a través de registros de direcciones especializados y modos de direcciones especiales en las instrucciones. O puede ser simplemente un conjunto de convenciones seguidas por los compiladores, utilizando registros genéricos y modos de dirección de registro+desplazamiento. O puede ser algo intermedio.
Dado que esta técnica es ahora casi universal, incluso en las máquinas registradoras, no resulta útil referirse a todas estas máquinas como máquinas apiladas. Ese término se reserva comúnmente para máquinas que también utilizan una pila de expresiones e instrucciones aritméticas de solo pila para evaluar las partes de una sola declaración.
Las computadoras comúnmente brindan acceso directo y eficiente a las variables globales del programa y a las variables locales solo del procedimiento o función más interno actual, el marco de pila superior. El direccionamiento de 'nivel superior' del contenido de los marcos de pila de las personas que llaman generalmente no es necesario y no es compatible directamente con el hardware. Si es necesario, los compiladores admiten esto pasando punteros de marco como parámetros ocultos adicionales.
Algunas máquinas apilables de Burroughs admiten referencias de nivel superior directamente en el hardware, con modos de dirección especializados y un archivo de registro de "visualización" especial que contiene las direcciones de trama de todos los ámbitos externos. Actualmente, sólo MCST Elbrus ha hecho esto en hardware. Cuando Niklaus Wirth desarrolló el primer compilador Pascal para el CDC 6000 , descubrió que, en general, era más rápido pasar los punteros de marco como una cadena, en lugar de actualizar constantemente conjuntos completos de punteros de marco. Este método de software tampoco agrega gastos generales para lenguajes comunes como C, que carecen de referencias de nivel superior.
Las mismas máquinas Burroughs también admitían el anidamiento de tareas o subprocesos. La tarea y su creador comparten los marcos de pila que existían en el momento de la creación de la tarea, pero no los marcos posteriores del creador ni los marcos propios de la tarea. Este estaba sostenido por una pila de cactus , cuyo diagrama de disposición se asemejaba al tronco y los brazos de un cactus Saguaro . Cada tarea tenía su propio segmento de memoria que contenía su pila y los fotogramas que posee. La base de esta pila está vinculada a la mitad de la pila de su creador. En máquinas con un espacio de direcciones plano convencional, la pila del creador y las pilas de tareas serían objetos de montón separados en un solo montón.
En algunos lenguajes de programación, los entornos de datos de alcance externo no siempre están anidados en el tiempo. Estos lenguajes organizan sus procedimientos 'registros de activación' como objetos de montón separados en lugar de marcos de pila añadidos a una pila lineal.
En lenguajes simples como Forth , que carecen de variables locales y de denominación de parámetros, los marcos de pila no contendrían más que direcciones de rama de retorno y gastos generales de gestión de marcos. Por lo tanto, su pila de devolución contiene direcciones de devolución simples en lugar de marcos. La pila de devolución está separada de la pila de valores de datos para mejorar el flujo de configuración de llamadas y devoluciones.
Las máquinas apilables a menudo se comparan con las máquinas de registro, que contienen valores en una serie de registros . Las máquinas registradoras pueden almacenar estructuras similares a pilas en esta matriz, pero una máquina registradora tiene instrucciones que eluden la interfaz de la pila. Las máquinas registradoras superan habitualmente a las máquinas apilables, [27] y las máquinas apilables siguen siendo un actor de nicho en los sistemas de hardware. Pero las máquinas de pila se utilizan a menudo para implementar máquinas virtuales debido a su simplicidad y facilidad de implementación. [28]
Las máquinas apilables tienen una mayor densidad de código . A diferencia de las instrucciones comunes de las máquinas de pila que pueden caber fácilmente en 6 bits o menos, las máquinas de registro requieren dos o tres campos de números de registro por instrucción ALU para seleccionar operandos; las máquinas de registro más densas promedian alrededor de 16 bits por instrucción más los operandos. Las máquinas de registro también utilizan un campo de compensación más amplio para códigos de operación de almacenamiento de carga. El código compacto de una máquina de pila naturalmente cabe en más instrucciones en la memoria caché y, por lo tanto, podría lograr una mejor eficiencia de la memoria caché , reduciendo los costos de memoria o permitiendo sistemas de memoria más rápidos por un costo determinado. Además, la mayoría de las instrucciones de la máquina de pila son muy simples y están hechas de un solo campo de código de operación o de un campo de operando. Por tanto, las máquinas apilables requieren muy pocos recursos electrónicos para decodificar cada instrucción.
Un programa tiene que ejecutar más instrucciones cuando se compila en una máquina de pila que cuando se compila en una máquina de registro o en una máquina de memoria a memoria. Cada carga variable o constante requiere su propia instrucción Load separada, en lugar de estar agrupada dentro de la instrucción que usa ese valor. Las instrucciones separadas pueden ser simples y de ejecución más rápida, pero el recuento total de instrucciones es aún mayor.
La mayoría de los intérpretes de registros especifican sus registros por número. Pero no se puede acceder a los registros de una máquina host en una matriz indexada, por lo que se asigna una matriz de memoria para los registros virtuales. Por lo tanto, las instrucciones de un intérprete de registros deben utilizar memoria para pasar los datos generados a la siguiente instrucción. Esto obliga a los intérpretes de registros a ser mucho más lentos en microprocesadores fabricados con una regla de proceso fina (es decir, transistores más rápidos sin mejorar las velocidades del circuito, como el Haswell x86). Estos requieren varios relojes para acceder a la memoria, pero solo un reloj para acceder a los registros. En el caso de una máquina de pila con un circuito de reenvío de datos en lugar de un archivo de registro, los intérpretes de pila pueden asignar los registros de la máquina principal para los operandos superiores de la pila en lugar de la memoria de la máquina principal.
En una máquina de pila, los operandos utilizados en las instrucciones siempre están en un desplazamiento conocido (establecido en el puntero de la pila), desde una ubicación fija (la parte inferior de la pila, que en un diseño de hardware siempre podría estar en la ubicación de memoria cero), evitando que el valioso almacenamiento en caché o en la CPU se utilice para almacenar tantas direcciones de memoria o números de índice. Esto puede preservar dichos registros y caché para su uso en cálculos sin flujo. [ cita necesaria ]
Algunos en la industria creen que las máquinas de pila ejecutan más ciclos de caché de datos para valores temporales y variables locales que las máquinas de registro. [29]
En las máquinas de pila, los valores temporales a menudo se derraman en la memoria, mientras que en las máquinas con muchos registros estos temporales generalmente permanecen en los registros. (Sin embargo, estos valores a menudo deben dividirse en "marcos de activación" al final de la definición de un procedimiento, bloque básico o, como mínimo, en un búfer de memoria durante el procesamiento de interrupciones). Los valores derramados en la memoria agregan más ciclos de caché. Este efecto de derrame depende del número de registros ocultos utilizados para almacenar los valores de la parte superior de la pila, de la frecuencia de las llamadas a procedimientos anidados y de las tasas de procesamiento de interrupciones de la computadora host.
En las máquinas de registro que utilizan compiladores de optimización, es muy común que las variables locales más utilizadas permanezcan en los registros en lugar de en las celdas de memoria del marco de la pila. Esto elimina la mayoría de los ciclos de caché de datos para leer y escribir esos valores. El desarrollo de la "programación de pila" para realizar análisis de variables en vivo y, por lo tanto, retener variables clave en la pila durante períodos prolongados, ayuda a resolver esta preocupación. [3] [4] [5]
Por otro lado, las máquinas de registro deben derramar muchos de sus registros en la memoria a través de llamadas a procedimientos anidados. La decisión de qué registros derramar y cuándo se toma estáticamente en el momento de la compilación en lugar de en la profundidad dinámica de las llamadas. Esto puede generar más tráfico de caché de datos que en una implementación avanzada de máquina de pila.
En las máquinas de registro, una subexpresión común (una subexpresión que se usa varias veces con el mismo valor de resultado) se puede evaluar solo una vez y su resultado se puede guardar en un registro rápido. Las reutilizaciones posteriores no tienen coste de tiempo ni de código, sólo una referencia de registro. Esta optimización acelera las expresiones simples (por ejemplo, cargar la variable X o el puntero P), así como las expresiones complejas menos comunes.
En las máquinas apilables, por el contrario, los resultados se pueden almacenar de dos maneras. En primer lugar, los resultados se pueden almacenar utilizando una variable temporal en la memoria. El almacenamiento y las recuperaciones posteriores cuestan instrucciones adicionales y ciclos de caché de datos adicionales. Hacer esto solo es una ganancia si el cálculo de la subexpresión cuesta más en tiempo que recuperarlo de la memoria, lo que en la mayoría de las CPU de pila, casi siempre es el caso. Nunca vale la pena buscar variables simples y punteros, porque ya tienen el mismo costo de un ciclo de caché de datos por acceso. Sólo vale la pena marginalmente para expresiones como X+1
. Estas expresiones más simples constituyen la mayoría de las expresiones redundantes y optimizables en programas escritos en lenguajes distintos de los lenguajes concatenativos . Un compilador optimizador sólo puede ganar con redundancias que el programador podría haber evitado en el código fuente. [ cita necesaria ]
La segunda forma deja un valor calculado en la pila de datos y lo duplica según sea necesario. Esto utiliza operaciones para copiar entradas de la pila. La pila debe tener una profundidad suficiente para las instrucciones de copia disponibles de la CPU. El código de pila escrito a mano a menudo utiliza este enfoque y alcanza velocidades como las de las máquinas de registro de uso general. [30] [9] Desafortunadamente, los algoritmos para una "programación de pila" óptima no se utilizan ampliamente en los lenguajes de programación.
En las máquinas modernas, el tiempo para recuperar una variable del caché de datos suele ser varias veces mayor que el tiempo necesario para las operaciones básicas de ALU. Un programa se ejecuta más rápido sin detenerse si sus cargas de memoria pueden iniciarse varios ciclos antes de la instrucción que necesita esa variable. Las máquinas complejas pueden hacer esto con una canalización profunda y una "ejecución desordenada" que examina y ejecuta muchas instrucciones a la vez. Las máquinas de registro pueden incluso hacer esto con hardware "en orden" mucho más simple, una canalización poco profunda y compiladores ligeramente más inteligentes. El paso de carga se convierte en una instrucción separada y esa instrucción se programa estáticamente mucho antes en la secuencia del código. El compilador coloca pasos independientes en el medio.
La programación de accesos a la memoria requiere registros de repuesto explícitos. No es posible en máquinas apiladas sin exponer algún aspecto de la microarquitectura al programador. Para la expresión AB -, B debe evaluarse y presionarse inmediatamente antes del paso Menos. Sin permutación de pila o subprocesos múltiples de hardware, se puede colocar relativamente poco código útil mientras se espera que finalice la carga B. Las máquinas de pila pueden solucionar el retraso de la memoria al tener una canalización de ejecución fuera de orden que cubra muchas instrucciones a la vez o, más probablemente, pueden permutar la pila de modo que puedan trabajar en otras cargas de trabajo mientras se completa la carga, o pueden Puede entrelazar la ejecución de diferentes subprocesos de programa, como en el sistema Unisys A9. [31] Las cargas computacionales cada vez más paralelas de hoy en día sugieren, sin embargo, que esta podría no ser la desventaja que se pensaba que era en el pasado.
Las máquinas apilables pueden omitir la etapa de búsqueda de operandos de una máquina registradora. [30] Por ejemplo, en el microprocesador Java Optimized Processor (JOP), los 2 operandos superiores de la pila ingresan directamente a un circuito de reenvío de datos que es más rápido que el archivo de registro. [32]
El algoritmo Tomasulo encuentra paralelismo a nivel de instrucción emitiendo instrucciones a medida que sus datos están disponibles. Conceptualmente, las direcciones de las posiciones en una pila no son diferentes de los índices de registro de un archivo de registro. Esta vista permite la ejecución desordenada del algoritmo Tomasulo para ser utilizado con máquinas apiladas.
La ejecución fuera de orden en máquinas apiladas parece reducir o evitar muchas dificultades teóricas y prácticas. [33] La investigación citada muestra que una máquina de pila de este tipo puede explotar el paralelismo a nivel de instrucciones, y el hardware resultante debe almacenar en caché los datos de las instrucciones. Estas máquinas evitan eficazmente la mayoría de los accesos a la memoria de la pila. El resultado logra un rendimiento (instrucciones por reloj ) comparable al de las máquinas con arquitectura de carga y almacenamiento , con densidades de código mucho más altas (porque las direcciones de los operandos están implícitas).
Un problema que surgió en la investigación fue que se necesitan alrededor de 1,88 instrucciones de máquina de pila para realizar el trabajo de una instrucción en una máquina con arquitectura de almacén de carga. [ cita necesaria ] Por lo tanto, las máquinas apilables fuera de servicio de la competencia requieren aproximadamente el doble de recursos electrónicos para seguir las instrucciones ("estaciones de emisión"). Esto podría compensarse con ahorros en caché de instrucciones y memoria y circuitos de decodificación de instrucciones.
Algunas máquinas apilables sencillas tienen un diseño de chip totalmente personalizado hasta el nivel de los registros individuales. El registro de dirección de la parte superior de la pila y los N buffers de datos de la parte superior de la pila se construyen a partir de circuitos de registros individuales separados, con sumadores separados y conexiones ad hoc.
Sin embargo, la mayoría de las máquinas apiladas se construyen a partir de componentes de circuitos más grandes donde los N buffers de datos se almacenan juntos dentro de un archivo de registro y comparten buses de lectura/escritura. Las instrucciones de la pila decodificadas se asignan a una o más acciones secuenciales en ese archivo de registro oculto. Las cargas y las operaciones de ALU actúan en algunos registros superiores, y los derrames y rellenos implícitos actúan en los registros inferiores. El decodificador permite que el flujo de instrucciones sea compacto. Pero si el flujo de código tuviera campos explícitos de selección de registros que manipularan directamente el archivo de registro subyacente, el compilador podría hacer un mejor uso de todos los registros y el programa se ejecutaría más rápido.
Las máquinas apilables microprogramadas son un ejemplo de esto. El motor de microcódigo interno es una especie de máquina de registro tipo RISC o una máquina tipo VLIW que utiliza múltiples archivos de registro. Cuando se controla directamente mediante un microcódigo específico de la tarea, ese motor realiza mucho más trabajo por ciclo que cuando se controla indirectamente mediante un código de pila equivalente para esa misma tarea.
Los traductores de código objeto para HP 3000 y Tandem T/16 son otro ejemplo. [34] [35] Tradujeron secuencias de código de pila en secuencias equivalentes de código RISC. Las optimizaciones "locales" menores eliminaron gran parte de la sobrecarga de una arquitectura de pila. Se utilizaron registros de repuesto para descartar cálculos de direcciones repetidos. El código traducido aún conservaba mucha sobrecarga de emulación debido a la falta de coincidencia entre las máquinas originales y de destino. A pesar de esa carga, la eficiencia del ciclo del código traducido coincidió con la eficiencia del ciclo del código de pila original. Y cuando el código fuente se recopiló directamente en la máquina de registro mediante compiladores optimizados, la eficiencia se duplicó. Esto muestra que la arquitectura de la pila y sus compiladores no optimizados estaban desperdiciando más de la mitad de la potencia del hardware subyacente.
Los archivos de registro son buenas herramientas para la informática porque tienen un gran ancho de banda y una latencia muy baja, en comparación con las referencias de memoria a través de cachés de datos. En una máquina simple, el archivo de registro permite leer dos registros independientes y escribir un tercero, todo en un ciclo de ALU con latencia de un ciclo o menos. Mientras que la caché de datos correspondiente puede iniciar solo una lectura o una escritura (no ambas) por ciclo, y la lectura normalmente tiene una latencia de dos ciclos de ALU. Eso es un tercio del rendimiento con el doble del retraso del oleoducto. En una máquina compleja como Athlon que completa dos o más instrucciones por ciclo, el archivo de registro permite leer cuatro o más registros independientes y escribir otros dos, todo en un ciclo ALU con latencia de un ciclo. Mientras que la caché de datos de doble puerto correspondiente puede iniciar solo dos lecturas o escrituras por ciclo, con múltiples ciclos de latencia. Nuevamente, eso es un tercio del rendimiento de los registros. Es muy costoso construir un caché con puertos adicionales.
Dado que una pila es un componente de la mayoría de los programas de software, incluso cuando el software utilizado no es estrictamente una máquina de pila, una máquina de pila de hardware podría imitar más fielmente el funcionamiento interno de sus programas. Los registros del procesador tienen un alto costo térmico y una máquina apilada podría presumir de una mayor eficiencia energética. [22]
Responder a una interrupción implica guardar los registros en una pila y luego pasar al código del controlador de interrupciones. A menudo, las máquinas apiladas responden más rápidamente a las interrupciones, porque la mayoría de los parámetros ya están en una pila y no es necesario enviarlos allí. Algunas máquinas de registro se ocupan de esto teniendo múltiples archivos de registro que pueden intercambiarse instantáneamente [36] pero esto aumenta los costos y ralentiza el archivo de registro.
Los intérpretes para máquinas de pila virtuales son más fáciles de construir que los intérpretes para máquinas de registro; la lógica para manejar los modos de dirección de memoria está en un solo lugar en lugar de repetirse en muchas instrucciones. Las máquinas apiladas también tienden a tener menos variaciones de código de operación; un código de operación generalizado manejará tanto casos frecuentes como casos oscuros de referencias de memoria o configuración de llamadas a funciones. (Pero la densidad del código a menudo se mejora agregando formas cortas y largas para la misma operación).
Los intérpretes de máquinas de pila virtual suelen ser más lentos que los intérpretes de otros estilos de máquinas virtuales. [37] Esta desaceleración es peor cuando se ejecuta en máquinas host con canales de ejecución profundos, como los chips x86 actuales.
En algunos intérpretes, el intérprete debe ejecutar un salto de interruptor de N vías para decodificar el siguiente código de operación y pasar a sus pasos para ese código de operación en particular. Otro método para seleccionar códigos de operación es el código subproceso . Los mecanismos de captación previa de la máquina host no pueden predecir ni recuperar el objetivo de ese salto indexado o indirecto. Por lo tanto, el proceso de ejecución de la máquina host debe reiniciarse cada vez que el intérprete alojado decodifica otra instrucción virtual. Esto sucede con más frecuencia en las máquinas de pila virtual que en otros estilos de máquinas virtuales. [38]
Un ejemplo es el lenguaje de programación Java . Su máquina virtual canónica se especifica como una máquina de pila de 8 bits. Sin embargo, la máquina virtual Dalvik para Java utilizada en los teléfonos inteligentes Android es una máquina de registro virtual de 16 bits, una elección realizada por razones de eficiencia. Las instrucciones aritméticas obtienen o almacenan directamente variables locales a través de campos de instrucciones de 4 bits (o más). [39] De manera similar, la versión 5.0 de Lua reemplazó su máquina de pila virtual con una máquina de registro virtual más rápida. [40] [41]
Desde que la máquina virtual Java se hizo popular, los microprocesadores han empleado predictores de rama avanzados para saltos indirectos. [42] Este avance evita la mayoría de los reinicios de canalización a partir de saltos de N vías y elimina gran parte de los costos de recuento de instrucciones que afectan a los intérpretes de pila.