En programas concurrentes, los programadores prestarán especial atención a la sincronización de datos entre diferentes procesos o subprocesos, especialmente cuando varios subprocesos modifican la misma variable, se debe tomar una sincronización confiable u otras medidas para garantizar que los datos se modifiquen correctamente. El punto aquí es El principio es: no asuma el orden en que se ejecutan las instrucciones. No puede predecir el orden en que se ejecutarán las instrucciones entre diferentes subprocesos.
Pero en un programa de un solo subproceso, generalmente nos resulta fácil suponer que las instrucciones se ejecutan secuencialmente; de lo contrario, podemos imaginar los terribles cambios que le sucederán al programa. El modelo ideal es: el orden en que se ejecutan varias instrucciones es único y ordenado. Este orden es el orden en que se escriben en el código, independientemente del procesador u otros factores. Este modelo se denomina modelo de coherencia secuencial. Es un modelo basado en el sistema von Neumann. Por supuesto, esta suposición es razonable en sí misma y rara vez ocurre de manera anormal en la práctica, pero de hecho, ninguna arquitectura multiprocesador moderna adopta este modelo porque es simplemente demasiado ineficiente. En la optimización de la compilación y la canalización de la CPU, casi todos implican el reordenamiento de instrucciones.
reordenación del tiempo de compilación
Un reordenamiento típico en tiempo de compilación consiste en ajustar el orden de las instrucciones para reducir la cantidad de lecturas y almacenamientos de registros tanto como sea posible sin cambiar la semántica del programa y reutilizar completamente los valores almacenados de los registros.
Supongamos que la primera instrucción calcula un valor y lo asigna a la variable A y lo almacena en un registro. La segunda instrucción no tiene nada que ver con A pero necesita ocupar un registro (suponiendo que ocupará el registro donde se encuentra A). La instrucción usa el valor de A y no tiene nada que ver con la segunda instrucción. Entonces, si según el modelo de coherencia secuencial, A se coloca en el registro después de ejecutar la primera instrucción, A ya no existe cuando se ejecuta la segunda instrucción y A se lee nuevamente en el registro cuando se ejecuta la tercera instrucción, y durante En este proceso, el valor de A no ha cambiado. Por lo general, el compilador intercambiará las posiciones de la segunda y tercera instrucción, de modo que A exista en el registro al final de la primera instrucción, y luego el valor de A se pueda leer directamente desde el registro, lo que reduce la sobrecarga de la lectura repetida.
La importancia del reordenamiento del oleoducto
Casi todas las CPU modernas utilizan el mecanismo de canalización para acelerar el procesamiento de instrucciones. En términos generales, una instrucción requiere varios ciclos de reloj de la CPU para procesarse y, mediante la ejecución paralela de la canalización, se pueden ejecutar varias instrucciones en el mismo ciclo de reloj. El método se indica simplemente. Simplemente divida las instrucciones en diferentes. El ciclo de ejecución, como lectura, direccionamiento, análisis, ejecución y otros pasos, se procesan en diferentes componentes. Al mismo tiempo, en la unidad de ejecución EU, la unidad funcional se divide en diferentes componentes, como componentes de suma y componentes de multiplicación. y cargar componentes, elementos de almacenamiento, etc., pueden realizar aún más la ejecución paralela de diferentes cálculos.
La arquitectura de canalización dicta que las instrucciones deben ejecutarse en paralelo, no como se considera en el modelo secuencial. El reordenamiento favorece el aprovechamiento completo del oleoducto, logrando así efectos superescalares.
Garantizar el orden
Aunque las instrucciones no necesariamente se ejecutan en el orden en que las escribimos, no hay duda de que en un entorno de un solo subproceso, el efecto final de la ejecución de las instrucciones debe ser consistente con su efecto en la ejecución secuencial; de lo contrario, esta optimización perderá importancia.
Por lo general, los principios anteriores se cumplirán ya sea que el reordenamiento de las instrucciones se realice en tiempo de compilación o en tiempo de ejecución.
Reordenamiento en el modelo de almacenamiento de Java
En el modelo de memoria Java (JMM), el reordenamiento es una sección muy importante, especialmente en la programación concurrente. JMM garantiza la semántica de ejecución secuencial a través de la regla de sucede antes. Si desea que el subproceso que realiza la operación B observe los resultados del subproceso que realiza la operación A, entonces A y B deben cumplir el principio de sucede antes. De lo contrario, la JVM puede realizar operaciones arbitrarias. operaciones sobre ellos.
La palabra clave volátil puede garantizar la visibilidad de las variables, porque todas las operaciones en volátil están en la memoria principal y todos los subprocesos comparten la memoria principal. El precio aquí es que se sacrifica el rendimiento y no se pueden usar registros o caché porque no son globales. , no se puede garantizar la visibilidad y pueden producirse lecturas sucias.
Otra función de volátil es evitar localmente el reordenamiento. Las instrucciones de operación en variables volátiles no se reordenarán, porque si se reordenan, pueden ocurrir problemas de visibilidad.
En términos de garantizar la visibilidad, los bloqueos (incluidos los bloqueos explícitos y los bloqueos de objetos) y la lectura y escritura de variables atómicas pueden garantizar la visibilidad de las variables. Sin embargo, los métodos de implementación son ligeramente diferentes. Por ejemplo, el bloqueo de sincronización garantiza que los datos se vuelvan a leer de la memoria para actualizar el caché cuando se libera el bloqueo, los datos se vuelven a escribir en la memoria para garantizar. que los datos son visibles, mientras que las variables volátiles simplemente leen y escriben en la memoria.