2012-05-09 9 views

Respuesta

5

Java fue diseñado para ser portátil desde cero. ¿Pero cómo puede mantener su bytecode portátil si depende de ciertos registros presentes en la plataforma en la que lo está ejecutando? Especialmente teniendo en cuenta que originalmente estaba destinado a ejecutarse (también) en decodificadores, que tienen una arquitectura de procesador muy diferente de las PC convencionales.

Es solo el tiempo de ejecución que la JVM conoce realmente los registros disponibles y otras cosas específicas del hardware. Entonces el compilador JIT puede (y lo hará) optimizar para estos según corresponda.

+7

Estrictamente hablando, los registros virtuales no tienen que estar mapeados en los registros físicos. Desde el otro POV, el soporte de hardware para stack tampoco está garantizado, ¿verdad? – Vlad

+2

ActionScript VM es, por ejemplo, basado en registro. Creo que la razón de este diseño no es tan simple y al final se reduce a una serie de concesiones que los diseñadores de Oak (creo que fueron ellos, antes de la adquisición de Sun) hicieron en relación con su visión específica de la aplicación del lenguaje . –

+0

Prefiero interpretar la pregunta original como * las ventajas de la máquina virtual basada en pila sobre otros medios para construir máquina virtual, como la máquina virtual basada en registro virtual *. Sin embargo, parece que OP puede vivir con la respuesta elegida (u olvidar su propia motivación original). – smwikipedia

2

Al diseñar una máquina virtual, es mucho más fácil colocar una pila en lugar de un conjunto de registros virtuales. Si el equipo host tiene una pila, como todos lo hacen, no hay nada que hacer, y si tiene registros, puede usarlos para otras cosas: temporales, etc. Si, por otro lado, la máquina host no tiene registros , solo una pila, y usted diseña su VM alrededor de los registros, usted tiene un gran problema de implementación en sus manos, ya que tiene que usar la pila nativa, de manera que interferirá con su uso como la pila virtual.

6

Tengo que estar en total desacuerdo con las respuestas anteriores.

La suposición de existencia de una pila de expresión no es mejor una suposición que la existencia de registros. Normalmente, las máquinas de registro no pueden ejecutar códigos de operación de pila directamente, y las máquinas de pila no pueden ejecutar códigos de operación de registro directamente. Ellos deben ser mapeados.

EJP dice "Si la máquina tiene una pila, como todos lo hacen, no hay nada que hacer". Esta es una declaración falsa. Proponer que todas las máquinas tengan pilas capaces de ejecutar cálculos muestra confusión sobre lo que realmente es una máquina de pila.

  1. Una máquina basada en pila tiene un conjunto de instrucciones con operandos implícitos en la parte superior de una "pila de expresión". No es una pila de propósito general. Una pila de propósito general no hace una pila de máquina. Pueden guardar/restaurar valores. Todas las plataformas tienen eso. Pero no pueden ejecutar cálculos.
  2. Una máquina basada en registro ejecuta códigos de operación típicos con operandos que son explícitos, registros virtuales , codificados en la secuencia de instrucciones. Esas máquinas virtuales todavía tienen pilas de propósitos generales y códigos de operación para guardar y restaurar registros. No se puede map operaciones de cálculo de la pila a una pila de datos. Los operandos del conjunto de instrucciones básicas son registros, direcciones de memoria o inmediatos (codificados dentro del código de operación).

Por lo tanto, hay más que "nada que hacer" para mapear códigos de operación de una arquitectura de máquina a otra. A menos que esté en un chip con aceleración nativa de código de operación Java, es mejor no asumirlo.

No defiendo el argumento de que una máquina apiladora es una buena opción para la portabilidad. Estoy diciendo que hay "algo que hacer" para mapear las instrucciones desde la pila a registrar o register-to-stack. Ambos son posibles

Sin embargo, mucho hablar es académico. En práctica, en 2014, la plataforma prevalente es el registro.Incluso los "procesadores" basados ​​en stack son a menudo chips blandos implementados en la parte superior de un chip de registro. Consulte la especificación Java Card 3 y mire las implementaciones y descubra qué procesadores se usan realmente. Lo dejo como un ejercicio.

A menos que estemos diseñando una VM para una plataforma muy específica (como nos han pedido que diseñemos una JVM que solo se ejecute en un procesador GreenSpaces, lo que no veo que ocurra), entonces supongo que el contexto es la aplicación general, aplicaciones integradas, decodificadores, controladores de firmware, juguetes e incluso tarjetas inteligentes. Para todo esto, veo procesadores de 8-32 bits como ARM, ya sean usados ​​o disponibles. La JVM principal está escrita en C. C hace posible diseñar un intérprete con códigos de pila virtual o de registro virtual. En la década de los 90 hubo mucha discusión sobre los procesadores Java basados ​​en pila que soportan directamente los códigos de operación de JVM. En 2014, vemos estas implementaciones en hardware basado en registro, o como instrucciones adicionales como Jazelle (aceleración de Java) en el ARM926EJ-S donde también hay registros y soporte para el conjunto de instrucciones ARM.

Para aplicación general, las máquinas virtuales basadas en pila ciertamente no se asignan a las pilas en la práctica. Todas estas máquinas usan instrucciones primarias basadas en registros; y las operaciones de la pila son para guardar y restaurar registros o llamar a la pila, sin hacer cálculos, lógica o ramificación. Las VM de la pila JIT al conjunto de instrucciones nativas de la máquina, ya sea una pila o un conjunto de instrucciones de registro. Desde 1980, prácticamente todas las arquitecturas de procesadores nuevos se han registrado, de acuerdo con Hennessy And Patterson - "Computer Architecture A Quantitative Approach". Eso significa registros de registro, registro de memoria o registro: conjuntos de instrucciones inmediatos. No puede agregar 2 valores en la pila en una máquina que no tiene un agregado basado en la pila. En x86, sus códigos de operación en base de la pila por una operación de adición se pueden traducir a partir de:

push a 
push b 
add 

al código de registro nativo:

mov eax, [addra] 
mov ebx, [addrb] 
add eax, ebx 

En segundo lugar, independientemente de si el flujo de código de operación se apile o registrarse, un JIT puede compilarlo al código nativo. Entonces, la elección del modelo VM es simplemente un modelo de software. Las máquinas de registro son igual de virtuales. No codifican ninguna información de registro nativo, los registros en los códigos de operación son símbolos virtuales.

Ahora cuando se diseñó Java, se pensó en los códigos de operación pequeños y la compatibilidad del procesador de 8 bits. Los códigos de operación basados ​​en pila son más pequeños que los códigos de operación de registro. Entonces tiene sentido. Estoy seguro de que leí en alguna parte que fue una de las principales razones por las que James Gosling eligió eso para Oak (nombre original de Java), además de la simplicidad. Simplemente no tengo referencia por mano. En eso, estoy de acuerdo con Péter Török.

  • Existen beneficios observables para apilar opcode. Los flujos de código a menudo son más pequeños/más densos. Con respecto a JVM y CLR, los códigos de byte basados ​​en pila observados pueden ser un 15-20% más pequeños que otras máquinas. Los bytecodes de pila se pueden codificar fácilmente en < = 8 bits. (Las cuatro máquinas pueden tener tan solo 20 códigos de operación). El opstream es más fácil de codificar/decodificar. Intente escribir un ensamblador o desensamblador para Java y luego para x86.
  • Existen beneficios observables para registrar el código de operación. Menos códigos de operación para codificar una expresión = mejor IPC = menos ramificación de alto nivel en el intérprete. También puede asignar directamente un pequeño número de registros (de 8 a 16) a prácticamente todos los procesadores modernos. El uso de registros logra un rendimiento mucho mayor debido a una mejor localidad de referencia de caché. Por el contrario, las máquinas de pila usan mucho ancho de banda de memoria.

En las máquinas virtuales, los códigos de bytes de registro a menudo usan un conjunto de registros virtuales grandes, que requieren códigos de bytes más grandes.En la mayoría de hardware real, los registros suelen estar codificados con aproximadamente 3 bits (Intel x86) a 5 bits (Sparc), por lo que la densidad puede variar de VM a VM o CPU a CPU o VM a CPU. Dalvik usa de 4 a 16 bits para representar registros, mientras que Parrot usó 8 bits por registro en todos los códigos de operación (al menos el formato de bytecode v2 que utilicé). Dalvik logra una mejor densidad promedio. En función de mi experiencia con la creación de ellos, es difícil construir una máquina de registro de propósito general dentro de un código de bytes de 8 bits a menos que se adhiera estrictamente a las primitivas y use un pequeño archivo de registro. Esto puede parecer poco intuitivo, pero generalmente un único código de operación en realidad tiene varias codificaciones con diferentes tipos de registro.

Un último punto: una gran parte de la optimización de núcleo blando posiblemente se apaga cuando el JIT entra en escena.

La excepción principal que tomo con el argumento de que las máquinas apiladas se asignan mejor al hardware es que ignora dónde se ejecuta la JVM y hacia dónde se dirige la tecnología. Fuera de Chuck Moore, no conozco a nadie que diseñe procesadores basados ​​en pila (IGNITE y GreenSpaces GA144), y la mayoría de los nuevos desarrollos son máquinas de registro. Los argumentos para las máquinas de pila son predominantemente académicos. Para cada argumento del procesador de pila de 8 bits, puedo mostrarle varias máquinas de registro (Hitachi H8 con registros, ARM926 con registros, Intel 8051) con un compilador de C. Es más probable que esté escribiendo en Forth en un procesador de pila pura, que Java. Para una nueva plataforma, tiene más sentido ir con un procesador ARM barato donde hay compiladores C, JVM completa, etc. Estos ejecutan conjuntos de instrucciones de registro.

Así que, si eso es cierto? ¿Importa? Mi opinión, basada en mi experiencia, es "no tanto como la gente piensa". Recuerde, bytecode es solo una representación intermedia. El núcleo puro interpretado de una máquina suele ser un trampolín, un puente, un núcleo predeterminado a prueba de fallas. El destino final es la eventual versión 2 con JITter para el rendimiento nativo. Entonces, el punto de vista de muchos que lo han hecho una o dos veces es que tiene sentido mantener el núcleo lo más simple posible y pasar al JIT, gastando el 90% de la optimización allí. Cualquier esfuerzo desaprovechado para ajustar el núcleo procesado podría verse como una optimización prematura. Si, por otro lado, no planifica un JITter, o JIT no es práctico debido a limitaciones de memoria, entonces optimice en el núcleo virtual o implemente la VM en un chip.

+0

Veo algo de confusión aquí también. En el n. ° 2, hablas de máquinas virtuales cuando te refieres a procesadores físicos. No veo dónde se explica cómo se puede implementar (fácilmente) una máquina virtual basada en registro en un procesador basado en pila, que es el punto en cuestión. No veo dónde declaras qué está mal con mi propia respuesta, aunque dices que no estás de acuerdo con ella. Finalmente, no veo ninguna razón para que patrocine el trabajo de aquellos que realmente lo han hecho. – EJP

+2

Si hubiera podido votar por esto, habría otorgado +50 votaciones al mismo tiempo. Tal conocimiento más profundo con la configuración de la arquitectura de fondo desde el momento del establecimiento es simplemente increíble de leer. Sin embargo, no pude entender varias líneas intermedias, pero la publicación tiene un contenido imprescindible. ¡Gracias @mrjoltcola por proporcionar información tan valiosa y criticar por las piezas equivocadas respetuosamente! –

+2

@EJP - Según lo solicitado, tengo una actualización para indicar claramente en qué estoy en desacuerdo con respecto a su propia respuesta. En cuanto a "patrocinar el trabajo de quienes realmente lo han hecho", he implementado 3 máquinas virtuales desde cero, dos de las cuales son de código abierto, disponibles para su descarga, una de las cuales está cubierta en varios libros por O'Reilly y otros editores. También estudié, programé o escribí JIT en Intel x86, Sparc, Motorola, PowerPC, PA-RISC, ARM, MIPS y CPU 8051, así que tengo una idea general sobre los procesadores. No estoy afirmando que hice grandes cosas. Pero apenas califico como alguien que no lo ha hecho. – codenheim

Cuestiones relacionadas