De fato, as pessoas que escrevem Java parecem não ter nada a ver com a CPU. No máximo, tem algo a ver com como executar a CPU e como definir o número de threads que mencionamos anteriormente. No entanto, esse algoritmo é apenas uma referência. Muitos cenários diferentes exigem meios práticos para resolvê -lo. Além disso, depois de executar a CPU, também consideraremos como tornar a CPU não tão cheia. Haha, humanos, é isso. Haha, ok, este artigo é sobre outras coisas. Talvez você quase não escreva código em java. Preste atenção à CPU, porque satisfazer o negócio é a primeira coisa importante. Se você deseja alcançar o nível da estrutura e fornecer à estrutura muitos caches de dados compartilhados, deve haver muitos problemas de requisição de dados no meio. Obviamente, o Java fornece muitas classes de pacotes simultâneos e você pode usá -lo, mas como é feito internamente, você precisa entender os detalhes para usá -lo melhor, caso contrário, é melhor não usá -lo. Este artigo pode não explicar esses conteúdos como foco, porque, como a festa do título: queremos falar sobre a CPU, haha.
O mesmo é dito, parece que Java não tem nada a ver com a CPU, então vamos falar sobre o que está acontecendo agora;
1. Ao encontrar um elemento compartilhado, nossa primeira idéia é garantir operações de leitura consistentes através da Volátil, ou seja, visibilidade absoluta. A chamada visibilidade significa que toda vez que você deseja usar esses dados, a CPU não usará nenhum conteúdo do cache e obtém dados da memória. Esse processo ainda é válido para várias CPUs, o que significa que a CPU e a memória são sincronizadas no momento. A CPU emitirá uma instrução de montagem semelhante ao bloqueio Addl 0 como um barramento, +0, mas não fará nada em relação a nada. No entanto, uma vez que a instrução for concluída, as operações subsequentes não afetarão mais o acesso de outros threads desse elemento, que é a visibilidade absoluta que ele pode alcançar, mas não pode implementar operações consistentes. Ou seja, o que o volátil não pode alcançar é a consistência de operações como I ++ (simultaneidade em vários threads), porque as operações I ++ são decompostas em:
int tmp = i; tmp = tmp + 1; i = tmp;
Essas três etapas são concluídas. A partir deste ponto, você também pode ver por que I ++ pode fazer outras coisas primeiro e depois adicionar 1 a si mesmo, porque é atribuído o valor a outra variável.
2. Se queremos usar consistência de simultaneidade com vários threads, precisamos usar o mecanismo de bloqueio. Atualmente, coisas como Atomic* podem basicamente atender a esses requisitos. Muitos métodos de classe inseguros são fornecidos internamente. Ao comparar constantemente os dados de visibilidade absoluta, podemos garantir que os dados adquiridos estejam atualizados; Em seguida, continuaremos a falar sobre outros assuntos da CPU.
3. No passado, não conseguimos executar a CPU para preenchê -la, mas não ficamos satisfeitos, não importava como começamos a ignorar o atraso entre a memória e a CPU. Como mencionamos hoje, falaremos brevemente sobre o atraso. De um modo geral, a CPU atual possui cache de três níveis e os atrasos são diferentes em diferentes idades, portanto o número específico pode ser apenas apenas. As CPUs de hoje geralmente têm um atraso de 1-2ns, o cache de segundo nível geralmente é de poucos ns a cerca de dez ns, e o cache de terceiro nível é geralmente entre 30ns e 50ns, e o acesso à memória geralmente atinge 70ns ou ainda mais (o computador está se desenvolvendo muito rápido, e esse valor é apenas para os dados de alguns CPUs, para um intervalo para um intervalo); Embora esse atraso seja muito pequeno, tudo está no nível de nanossegundos, você descobrirá que, quando seu programa for dividido em operações de instrução, haverá muitas interações da CPU. Se o atraso de cada interação for tão grande, o desempenho do sistema mudará no momento;
4. Volte para o volátil mencionado agora. Toda vez que obtém dados da memória, ele abandona o cache. Obviamente, se se tornar mais lento em algumas operações de tiro único, ficará mais lento. Às vezes temos que fazer isso. Até as operações de leitura e gravação requerem consistência e até todo o bloco de dados é sincronizado. Só podemos reduzir a granularidade da fechadura até certo ponto, mas não podemos ter bloqueios. Até o nível da CPU terá restrições no nível de instrução.
5. Operações atômicas no nível da CPU são geralmente chamadas de barreiras, com barreiras de leitura, barreiras de escrita, etc. elas geralmente são desencadeadas por um ponto. Quando várias instruções do programa são enviadas à CPU, algumas instruções não podem ser executadas na ordem do programa, e algumas devem ser executadas na ordem do programa, desde que possam ser garantidas para serem consistentes na ordem final do programa. Em termos de classificação, o JIT mudará durante o tempo de execução e o nível de instrução da CPU também mudará. O principal motivo é otimizar as instruções de tempo de execução para tornar o programa mais rápido.
6. O nível da CPU operará a linha de cache na memória. A chamada linha de cache lerá uma peça de memória continuamente, que geralmente está relacionada ao modelo e arquitetura da CPU. Atualmente, muitas CPUs geralmente leem a memória contínua a cada vez, e as primeiras terão 32byte, por isso será mais rápido ao atravessar algumas matrizes (é muito lento com base na travessia da coluna), mas isso não está completamente correto. O seguinte comparará algumas situações opostas.
7. Se a CPU alterar os dados, precisamos falar sobre o estado da CPU modificando os dados. Se todos os dados forem lidos, eles podem ser lidos em paralelo por vários threads sob várias CPUs. Ao escrever operações nos blocos de dados, é diferente. Os blocos de dados terão invalidação exclusiva, modificada e outros estados, e os dados falharão naturalmente após a modificação. Quando vários threads estão modificando o mesmo bloco de dados sob várias CPUs, ocorrerá cópia de dados de barramento (QPI) entre as CPUs. Obviamente, se o modificarmos para os mesmos dados, não temos escolha, mas quando retornarmos à linha de cache no ponto 6, o problema é mais problemático. Se os dados estiverem na mesma matriz e os elementos da matriz serão armazenados em cache em uma CPU ao mesmo tempo, o QPI de multi-threads será muito frequente. Às vezes, esse problema ocorre mesmo que os objetos montados na matriz sejam montados, como:
classe inputInteger {private int value; public inputInteger (int i) {this.value = i;}} inputInteger [] integers = new inputInteger [size]; para (int i = 0; i <tamanho; i ++) {integers [i] = novo inputInteger (i);} Neste momento, você pode ver que tudo em números inteiros são objetos e há apenas referências a objetos na matriz, mas o arranjo de objetos é teoricamente independente e não será armazenado continuamente. No entanto, quando o Java aloca a memória do objeto, geralmente é alocada continuamente na área do Éden. Quando no loop for, se nenhum outro threads for acessado, esses objetos serão armazenados juntos. Mesmo que sejam GC para a área antiga, é muito provável que seja montado. Portanto, a maneira de modificar toda a matriz, confiando em objetos simples para resolver a linha de cache parece não confiável, porque int é 4 bytes. Se no modo 64, esse tamanho é de 24 bytes (4bytes são preenchidos) e a compressão do ponteiro é de 16 bytes; Ou seja, a CPU pode corresponder a 3-4 objetos a cada vez. Como fazer o cache da CPU, mas isso não afeta o QPI do sistema. Não pense em concluí -lo separando os objetos, porque o processo de cópia da memória do GC provavelmente será copiado juntos. A melhor maneira é preenchê -lo. Embora seja um pouco de desperdício de memória, esse é o método mais confiável, que é preencher o objeto para 64 bytes. Se a compactação do ponteiro não estiver ativada, existem 24bytes e existem 40 bytes no momento. Você só precisa adicionar 5 longos dentro do objeto.
classe inputInteger {public int value; private A1, A2, A3, A4, A5;} Haha, esse método é muito rústico, mas funciona muito bem. Às vezes, quando a JVM é compilada, descobre que esses parâmetros não foram feitos, por isso é morto diretamente para você. A otimização é inválida. O método mais o método é simplesmente operar esses 5 parâmetros em um corpo de método (usado todos eles), mas esse método nunca o chamará.
8. No nível da CPU, às vezes pode não ser possível fazer a primeira coisa a fazer. É o rei. Na operação do AtomicIntegerFieldUpDater, se você ligar para o Getanteset (true) em um único thread, você descobrirá que ele funciona muito rápido e começa a desacelerar sob uma CPU multi-núcleo. Por que é dito claramente acima? Como o GetAndSet é modificado e comparado e, em seguida, altere -o primeiro, o QPI será muito alto; portanto, neste momento, é melhor fazer as operações primeiro e depois modificá -lo; E também é uma boa maneira de obtê -lo uma vez. Se não puder ser obtido, desista e deixe que outros tópicos façam outras coisas;
9. Às vezes, para resolver o problema de alguma CPU ocupada e não ocupada, haverá muitos algoritmos para resolver. Por exemplo, o NUMA é uma das soluções. No entanto, não importa qual arquitetura seja mais útil em certos cenários, pode não ser eficaz para todos os cenários. Há um mecanismo de bloqueio de fila para concluir o gerenciamento do estado da CPU, mas isso também tem o problema da linha de cache, porque o estado muda com frequência, e os núcleos de várias aplicações também produzirão alguns algoritmos a serem feitos para cooperar com a CPU, para que a CPU possa ser usada com mais eficiência, como as filas de CLH.
Existem muitos detalhes sobre isso, como a superposição de loops variáveis comuns, tipo volátil e séries atômicas*, que são completamente diferentes; Loops de matriz multidimensional, loops em ordem atrasada em diferentes latitudes, e há muitos detalhes, e entendo por que há inspiração no processo de otimização real; Os detalhes dos bloqueios são muito finos e tonto e, no nível inferior do sistema, sempre existem algumas operações atômicas leves. Não importa quem diga que seu código não requer bloqueio, o melhor pode ser tão simples quanto a CPU pode executar apenas uma instrução a cada momento. As CPUs com vários núcleos também terão uma área compartilhada para controlar algum conteúdo no nível do barramento, incluindo nível de leitura, nível de gravação, nível de memória etc. Em diferentes cenários, a granularidade da trava é reduzida o máximo possível. O desempenho do sistema é evidente e é um resultado normal.