De hecho, las personas que escriben Java parecen no tener nada que ver con la CPU. A lo sumo, tiene algo que ver con cómo ejecutar la CPU y cómo establecer el número de hilos que mencionamos anteriormente. Sin embargo, ese algoritmo es solo una referencia. Muchos escenarios diferentes requieren medios prácticos para resolverlo. Además, después de ejecutar la CPU, también consideraremos cómo hacer que la CPU no sea tan llena. Jaja, humanos, eso es todo. Jaja, OK, este artículo se trata de otras cosas. Tal vez casi no escribas código en Java. Presta atención a la CPU, porque satisfacer el negocio es lo primero importante. Si desea alcanzar el nivel de marco y proporcionar el marco con muchos cachés de datos compartidos, debe haber muchos problemas de solicitud de datos en el medio. Por supuesto, Java proporciona muchas clases de paquetes concurrentes, y puede usarlo, pero cómo se hace internamente, debe comprender los detalles para usarlo mejor, de lo contrario, es mejor no usarlo. Es posible que este artículo no explique estos contenidos como el foco, porque al igual que la fiesta del título: queremos hablar sobre CPU, jaja.
Se dice lo mismo, parece que Java no tiene nada que ver con la CPU, así que hablemos de lo que está sucediendo ahora;
1. Al encontrar un elemento compartido, nuestra primera idea es garantizar operaciones de lectura consistentes a través de volátiles, es decir, una visibilidad absoluta. La llamada visibilidad significa que cada vez que desee usar estos datos, la CPU no usará ningún contenido de caché y obtendrá datos de la memoria. Este proceso sigue siendo válido para múltiples CPU, lo que significa que la CPU y la memoria están sincronizadas en este momento. La CPU emitirá una instrucción de ensamblaje similar al bloqueo ADDL 0 como un bus, +0 pero no hará nada relativo a nada. Sin embargo, una vez que se completa la instrucción, las operaciones posteriores ya no afectarán el acceso de otros hilos de este elemento, que es la visibilidad absoluta que puede lograr, pero no puede implementar operaciones consistentes. Es decir, lo que el volátil no puede lograr es la consistencia de operaciones como I ++ (concurrencia en múltiples hilos), porque las operaciones I ++ se descomponen en:
int tmp = i; tmp = tmp + 1; i = tmp;
Estos tres pasos se completan. Desde este punto, también puede ver por qué I ++ puede hacer otras cosas primero y luego agregar 1 a sí misma, porque se le asigna el valor a otra variable.
2. Si queremos usar la consistencia de concurrencia multiproceso, necesitamos usar el mecanismo de bloqueo. En la actualidad, cosas como Atomic* pueden cumplir básicamente estos requisitos. Muchos métodos de clase inseguros se proporcionan internamente. Al comparar constantemente los datos de visibilidad absoluta, podemos asegurarnos de que los datos adquiridos estén actualizados; A continuación, continuaremos hablando sobre otros asuntos de CPU.
3. En el pasado, no pudimos ejecutar la CPU para llenarla, pero no estábamos satisfechos sin importar cómo comenzamos a ignorar el retraso entre la memoria y la CPU. Como lo mencionamos hoy, hablaremos brevemente sobre la demora. En términos generales, la CPU actual tiene una caché de tres niveles, y los retrasos son diferentes en diferentes edades, por lo que el número específico solo puede ser más o menos justo. Las CPU de hoy generalmente tienen un retraso de 1-2NS, el caché de segundo nivel generalmente es de unos pocos ns a aproximadamente diez Ns, y el caché de tercer nivel generalmente es entre 30ns y 50ns, y el acceso a la memoria generalmente alcanzará 70ns o incluso más (la computadora se está desarrollando muy rápido, y este valor es solo para los datos en algunos CPU, para una referencia de alcance); Aunque este retraso es muy pequeño, todo está a nivel de nanosegundos, encontrará que cuando su programa se divide en operaciones de instrucciones, habrá muchas interacciones de CPU. Si el retraso de cada interacción es tan grande, el rendimiento del sistema cambiará en este momento;
4. Vuelve al volátil mencionado en este momento. Cada vez que obtiene datos de la memoria, abandona el caché. Por supuesto, si se vuelve más lento en algunas operaciones de un solo hilo, se volverá más lento. A veces tenemos que hacer esto. Incluso las operaciones de lectura y escritura requieren consistencia, e incluso todo el bloque de datos está sincronizado. Solo podemos reducir la granularidad de la cerradura hasta cierto punto, pero no podemos tener cerraduras en absoluto. Incluso el nivel de CPU en sí tendrá restricciones de nivel de instrucción.
5. Las operaciones atómicas a nivel de CPU generalmente se llaman barreras, con barreras de lectura, barreras de escritura, etc. Generalmente se desencadenan por un punto. Cuando se envían múltiples instrucciones del programa a la CPU, algunas instrucciones pueden no ejecutarse en el orden del programa, y algunas deben ejecutarse en el orden del programa, siempre que puedan garantizar que sean consistentes en el orden final del programa. En términos de clasificación, JIT cambiará durante el tiempo de ejecución, y el nivel de instrucción de la CPU también cambiará. La razón principal es optimizar las instrucciones de tiempo de ejecución para que el programa se ejecute más rápido.
6. El nivel de CPU operará la línea de caché en la memoria. La llamada línea de caché se leerá continuamente un pedazo de memoria, que generalmente está relacionado con el modelo y la arquitectura de la CPU. Hoy en día, muchas CPU generalmente leerán memoria continua cada vez, y las primeras tendrán 32 bytes, por lo que será más rápido al atravesar algunas matrices (es muy lento en función del recorrido de la columna), pero esto no es completamente correcto. Lo siguiente comparará algunas situaciones opuestas.
7. Si la CPU cambia los datos, tenemos que hablar sobre el estado de la CPU que modifica los datos. Si se leen todos los datos, se puede leer en paralelo por múltiples hilos en múltiples CPU. Al escribir operaciones en los bloques de datos, es diferente. Los bloques de datos tendrán estados exclusivos, modificados, de invalidación y otros estados, y los datos fallarán naturalmente después de la modificación. Cuando múltiples hilos modifican el mismo bloque de datos en múltiples CPU, se producirá la copia de datos del bus (QPI) entre las CPU. Por supuesto, si lo modificamos a los mismos datos, no tenemos otra opción, pero cuando volvemos a la línea de caché en el punto 6, el problema es más problemático. Si los datos están en la misma matriz, y los elementos en la matriz se almacenarán en caché a una CPU al mismo tiempo, el QPI de múltiples hilos será muy frecuente. A veces, este problema ocurrirá incluso si los objetos ensamblados en la matriz se ensamblan, como:
clase inputinteger {private int value; public inputinteinteger (int i) {this.value = i;}} inputInteger [] Integers = new InputInteger [size]; for (int i = 0; i <size; i ++) {Integers [i] = new InputInteger (i);} En este momento, puede ver que todo en enteros es objetos, y solo hay referencias a objetos en la matriz, pero la disposición de los objetos es teóricamente independiente y no se almacenará continuamente. Sin embargo, cuando Java asigna la memoria del objeto, a menudo se asigna continuamente en el área del Edén. Cuando está en el bucle for, si no se accede a otros hilos, estos objetos se almacenarán juntos. Incluso si son GC para el área antigua, es muy probable que se unan. Por lo tanto, la forma de modificar la matriz completa confiando en objetos simples para resolver la línea de caché no parece confiable, porque int es 4 bytes. Si en modo 64, este tamaño es de 24 bytes (4 bytes se llenan), y la compresión del puntero es de 16 bytes; Es decir, la CPU puede coincidir con 3-4 objetos cada vez. Cómo hacer la caché de la CPU, pero no afecta el QPI del sistema. No piense en completarlo separando los objetos, porque es probable que el proceso de copia de memoria del proceso GC se copie juntos. La mejor manera es llenarlo. Aunque es un poco de desperdicio de memoria, este es el método más confiable, que es llenar el objeto a 64 bytes. Si la compresión del puntero no está habilitada, hay 24 bytes y hay 40 bytes en este momento. Solo necesita agregar 5 largos dentro del objeto.
Clase inputinteger {public int value; privado long a1, a2, a3, a4, a5;} Jaja, este método es muy rústico, pero funciona muy bien. A veces, cuando se compila JVM, encuentra que estos parámetros no se han hecho, por lo que se mata directamente para usted. La optimización no es válida. El método más el método es simplemente operar estos 5 parámetros en un cuerpo de método (los usó todos), pero este método nunca lo llamará.
8. En el nivel de la CPU, a veces puede no ser posible hacer lo primero que debe hacer. Es el rey. En la operación de AtomicIntegerFieldUpdater, si llama a Getanteset (verdadero) en un solo hilo, encontrará que funciona bastante rápido y comienza a disminuir la velocidad bajo una CPU de múltiples núcleos. ¿Por qué se dice claramente arriba? Debido a que GetAndset se modifica y compara, y luego cambialo primero, el QPI será muy alto, por lo que en este momento, es mejor obtener operaciones primero y luego modificarlo; Y también es una buena manera de obtenerlo una vez. Si no se puede obtener, cede y deje que otros hilos hagan otras cosas;
9. A veces, para resolver el problema de alguna CPU ocupada y no ocupada, habrá muchos algoritmos para resolver. Por ejemplo, NUMA es una de las soluciones. Sin embargo, no importa qué arquitectura sea más útil en ciertos escenarios, puede no ser efectivo para todos los escenarios. Existe un mecanismo de bloqueo de cola para completar la gestión del estado de la CPU, pero esto también tiene el problema de la línea de caché, porque el estado cambia con frecuencia, y los núcleos de varias aplicaciones también producirán algunos algoritmos para hacer para cooperar con la CPU, de modo que la CPU se pueda usar de manera más efectiva, como las cuentas de CLH.
Hay muchos detalles sobre esto, como la superposición de bucles variables ordinarios, el tipo volátil y la serie Atomic*, que son completamente diferentes; bucles de matriz multidimensionales, enrollando en orden hacia atrás en diferentes latitudes, y hay muchos detalles, y entiendo por qué hay inspiración en el proceso de optimización real; Los detalles de las cerraduras son demasiado delgados y mareados, y en el nivel inferior del sistema, siempre hay algunas operaciones atómicas livianas. No importa quién diga que su código no requiere bloqueo, el mejor puede ser tan simple como la CPU solo puede ejecutar una instrucción en cada momento. Las CPU de múltiples núcleos también tendrán un área compartida para controlar cierto contenido a nivel de bus, incluido el nivel de lectura, el nivel de escritura, el nivel de memoria, etc. En diferentes escenarios, la granularidad del bloqueo se reduce lo más posible. El rendimiento del sistema es evidente y es un resultado normal.