Milhares de coisas para corrigir em Java a partir da versão um: uma ótima entrevista com Sergey Kuksenko da Oracle


Sergey Kuksenko é um engenheiro de desempenho que viu o Java ainda na versão 1.0. Durante esse período, ele conseguiu participar do desenvolvimento de aplicativos móveis, clientes, servidores e máquinas virtuais. Desde 2005, o Java atua no desempenho e atualmente trabalha na Oracle para melhorar o desempenho do JDK. Um dos palestrantes mais populares da Joker e JPoint.


Este habrapost é uma ótima entrevista com Sergey, dedicada aos seguintes tópicos:


  • Culto da Performance;
  • Quando e o que precisa ser otimizado, o design inicial da linguagem e da biblioteca;
  • Áreas promissoras para maior otimização;
  • Como participar do desenvolvimento e o que pode ser quebrado por otimizações;
  • Truques do compilador, registro de posicionamento;
  • É possível montar um gato a partir de carne picada;
  • Quando os testes funcionam por cinco dias consecutivos e outras rotinas domésticas;
  • Como se tornar um engenheiro de desempenho;
  • Preparando um relatório para o próximo Coringa.

Sobre o culto à produtividade


Oleg: Você é nosso antigo orador e esta não é a nossa primeira entrevista. Diga-me um pouco, quem é você agora, o que você está fazendo?


Sergey: Eu sou o mesmo de muitos anos atrás e estou fazendo a mesma coisa. Trabalho na equipe Java Performance e sou responsável pelo desempenho das máquinas Java Oracle, OpenJDK.


Oleg: Então, eu tenho uma pergunta meio troll: aqui você é um engenheiro de desempenho e seus relatórios são sobre todos os tipos de desempenho. Você não acha que o problema de desempenho é superestimado? Todo mundo corre com ela, mas isso é mesmo necessário?


Sergey: Esta é uma boa pergunta. Tudo depende do outro. Esse tipo de atenção do público pode ser considerado excessivo. A produtividade dos negócios, por outro lado, é dinheiro.


Esse é o dinheiro real que as pessoas gastam em hardware, em algum tipo de nuvem na Amazônia. Se você não processar suas solicitações com rapidez suficiente - é isso, você perde clientes, perde dinheiro, perde todo o resto. Portanto, a solicitação de desempenho, é claro, está sempre lá. A questão é quão importante é em cada caso. Fico calado sobre o comércio de alta frequência.


Oleg: A propósito, você acha que o Java é adequado para isso?


Sergey: Você teve a oportunidade de conhecer uma pessoa como Peter Lawrey ?


Oleg: Quem é o CEO da Chronicle Software, os desenvolvedores do OpenHFT?


Sergey: Este é um amigo muito famoso de Londres que viaja muito em conferências. Eles trabalham em Java em negociações de alta frequência, vivem muito bem.


Oleg: Eles estão fazendo isso em Java ou é chamado código nativo de Java? Ainda assim, há uma diferença.


Sergey: Eu não sei nesse nível, ele não contou. Em princípio, se desejado, tudo o que é necessário pode ser alcançado no próprio Java.


Oleg: Interessante. Se você tomar, por exemplo, uma comunidade de pythonists, eles terão muito menos culto à produtividade. Como é exatamente isso que acontece em nossa comunidade? Talvez você tenha provocado o culto ao desempenho com seus relatórios? Você, Shipilev, Pangin, Ivanov e assim por diante.


Sergey: Eu não sei como aconteceu. O culto à produtividade na conferência russa é muito maior do que na americana. Talvez isso reflita o próprio público. Em nós, as pessoas querem se envolver mais em produtividade, é interessante para elas. E na América, eles querem fazer mais pelo que pagam mais. Mas isso é uma hipótese, conjectura. Isso aconteceu.



Quando e o que precisa ser otimizado


Oleg: Você disse que ainda há um pedido de desempenho. Em que momento você precisa começar a pensar em desempenho? Quando o trovão atacará?


Sergey: Esta é uma pergunta abstrata geral. É melhor recorrer novamente à palestra de Alexey Shipilev em uma das conferências anteriores, onde ele pintou tudo isso o suficiente.


Oleg: Sim, eu lembro da "curva do nome Sh".


Sergey: Você precisa fazer o desempenho imediatamente, mas dependendo de qual nível. Não é necessário escrever referências imediatamente. Sabe-se, por exemplo, que a restrição banal do nível de arquitetura da API entre Set como um conjunto e SortedSet já nos impõe restrições algorítmicas fundamentais.


Se inserirmos um SortedSet na API (embora ninguém precise desse classificado) e, em seguida, ele se espalhar por todo o sistema, essa coisa terá que ser realizada com muito esforço.


A questão começa no próprio nível de design - é uma questão de restrições mínimas. As menores restrições possíveis devem ser usadas para que você possa brincar com elas mais tarde. Por exemplo, quando torci vários pedaços de Java, vieram à mente palavrões extremamente ruins. Gostaria de fazer algo com uma das classes base, mas não posso fazer nada, porque a API é fixa, você não pode mais alterá-la, ela já foi rastreada. Mas, para fazer alguns truques e fazer overclock, você precisa ocultar alguns detalhes.


Estudo de caso: eu costumava me agachar na classe java.math.BigDecimal. Houve um grande pedido de lados diferentes para dispersá-lo de alguma forma. Existe um caso bom e especial quando o nosso BigDecimal não é "Grande", é apenas o decimal e você precisa lê-los.


Agora, é claro, um wrapper apropriado foi criado para isso. Mas se não houvesse um construtor público saindo do BigDecimal, mas alguns métodos e fábricas estáticos, seria possível tornar o BigDecimal abstrato e cuspir duas implementações diferentes que funcionavam conforme necessário. Mas isso é impossível, porque o construtor se destaca. Por esse motivo, você já precisa fazer uma verificação desnecessária do tempo de execução, o que permite seguir um caminho rápido em alguns casos.


Oleg: Daqui resulta que, ao desenvolver uma biblioteca padrão, vale a pena abandonar os designers e fazer construtores em todos os lugares?


Sergey: Está ficando tarde.


Oleg: Se não fosse tarde demais, seria uma boa ideia?


Sergey: Ela daria mais espaço para manobra. Veja: estamos escrevendo novo, e esse novo está do lado de fora do construtor. Duas operações são obtidas: primeiro criamos um objeto, depois chamamos o construtor que o preenche. E, às vezes, seria muito útil ocultar a própria criação do objeto e criar o objeto errado que temos lá fora. Essa é uma restrição de idioma, o original, desde os primeiros dias do Java.


Oleg: Bem, agora todo mundo usa estruturas DI que permitem que você torça proxies como quiser e adicione qualquer coisa, ignorando essa limitação. No design original do idioma, você poderia adicionar algo assim, o contêiner de injeção de dependência embutido?


Sergey: Eu tenho uma opinião muito específica sobre o design inicial da linguagem. Se você se lembra da história do Java 1.0, saiu uma pressão de tempo bastante séria, tudo tinha que ser feito rapidamente.


Existem milhares de coisas que eu pessoalmente gostaria de ver corrigidas desde a primeira versão. Mas receio que, mesmo que um dentre esses mil seja escolhido, um-dois-três, e eles tivessem começado a ser feitos no momento do lançamento do primeiro Java, o Java não teria sido lançado. Este é um exemplo padrão de que o melhor é o inimigo do bem.



O que mais pode ser otimizado em Java


Oleg: As pessoas comuns podem consertar algo apenas em seu projeto, e você, como engenheiros de desempenho no JDK, afeta imediatamente centenas de milhares de projetos. Surge a pergunta: ao longo de mais de 20 anos de desenvolvimento Java, houve alguma área no JDK em que a intervenção dos engenheiros principais pode levar a um efeito perceptível? E quão perceptível é esse "efeito perceptível"?


Sergey: Primeiro, agora o Java não funciona no hardware que, digamos, há 10 anos. Ferro agora e ferro há 10 anos são duas grandes diferenças, e é aconselhável fazer várias otimizações.


Em segundo lugar, é claro que é maravilhoso quando um engenheiro de desempenho se senta e acelera algo, obtém grandes números, se reporta a seus superiores, ganha dinheiro por um bônus após esses overclocking. Mas uma enorme quantidade de trabalho está em andamento em novos projetos. Um recurso é adicionado e a tarefa do engenheiro de desempenho não é fazer overclock no recurso, mas garantir que tudo esteja bem nesse recurso. Ou, se não estiver ok, crie algum tipo de correção.


Oleg: Como você pode ter certeza? Você não verifica o código formalmente. O que é um "certifique-se"?


Sergey: Garantir que está tudo bem do ponto de vista do desempenho é a opinião subjetiva subjetiva de um engenheiro de desempenho que escreverá um relatório e dirá que "tudo está normal nesse recurso". Dependendo do tamanho do recurso, isso implica algumas vezes em ação, outras em esforços diferentes. A partir do fato de que você só precisa se sentar estupidamente, observe o que está sendo feito lá, faça benchmarks nesta área, faça benchmarks, veja o que acontece na saída e tome uma decisão informada razoável.


Oleg: E do ponto de vista do desempenho e dos novos recursos - o Java geralmente avança? Existe algo aí? Porque nosso hardware não mudou muito, por exemplo, se falamos sobre a Intel.


Sergey: Por que período isso não mudou?


Oleg: Por exemplo, nos últimos 10 anos.


Sergey: Sim, existe um AVX-512 em hardware há uma década?


Oleg: Não. Ele, provavelmente, nem sempre está presente no moderno?


Sergey: Eu definitivamente não. Temos em nosso laboratório, mas tudo é ocupado por compiladores. Eles estão ferrando até agora, então eu não olhei.


Oleg: O suporte ao AVX-512 pode ser considerado um exemplo de recurso típico?


Sergey: Provavelmente possível. O que exatamente eu faço: tivemos uma grande camada de trabalho no fato de que existem requisitos modernos para adicionar novos algoritmos criptográficos. É algo em que os algoritmos de criptografia de dez anos simplesmente não podem ser invocados. Precisamos de novos algoritmos, chaves maiores. E a adição de novos algoritmos criptográficos ocorre, eu diria, constantemente.


Oleg: Eles de alguma forma aceleram o hardware?


Sergey: Tudo depende de algoritmos específicos. Existem algoritmos muito bem acelerados. A propósito, há 10 anos, isso não teria funcionado no hardware da Intel, mas em cerca de 5 a 6 o quão boas instruções apareceram, até unidades AES com acelerações. Tudo isso foi implementado com um intervalo de tempo mínimo.


Oleg: E a GPU, eles também são capazes de multiplicar matrizes?


Sergey: Sobre a GPU - uma conversa em separado. Temos para isso um projeto do Panamá no qual todo esse trabalho é realizado e, algum dia, ele alcançará a linha principal do Java com todas as vantagens.


Oleg: Eu tenho alguns conhecidos que estão envolvidos, condicionalmente, em matemática financeira. De algum ponto em diante, eles sempre mudam para C ++ para computação e afirmam que é muito inconveniente usar todas essas otimizações e hardware da plataforma gerenciada. Isso pode ser melhorado?


Sergey: Também temos um grande pedido para isso e há vários requisitos internos. Por exemplo, para fazer algo funcionar melhor no campo de aprendizado de máquina. Como regra, essa é uma multiplicação matricial banal, que pode ser descartada na GPU. O trabalho está em andamento, digamos assim.


Temos dois grandes projetos: Valhalla e Panamá, que devem coletar recursos como a GPU. Na junção de Valhalla e Panamá, fica uma API de vetor que funciona com nossas instruções SIMD / SSE / AVX diretamente do código Java, e a própria Valhalla com tipos embutidos é um grande passo nessa direção.



O que pode ser quebrado pela otimização, como participar do desenvolvimento


Oleg: Os guarda-chuvas que você mencionou são parecidos. É possível que um projeto afete outro, inclusive em termos de código e perfil de desempenho? Por exemplo, você refatorou algo, e o infeliz Ron Presler, derramando lágrimas, está fixando seus testes em um canto à noite?


Sergey: Isso acontece o tempo todo. Um exemplo concreto é a API Vector. Para que a API Vector funcione bem, nossos vetores nativos devem eventualmente se tornar tipos de valor ou, como agora é chamado em Java, tipos embutidos. Você pode fazer uma solução alternativa no hotspot e implementá-lo de alguma forma, mas eu quero ter uma solução geral. Por outro lado, o principal recurso dos tipos embutidos é precisamente não se preocupar com o layout desses dados, e o layout desses dados é extremamente importante para a API Vector.


Porque, de fato, corresponde diretamente ao AVX-512 e tudo mais. É claro que você precisa fazer alguns agachamentos, algumas otimizações que, por um lado, tornarão o tipo embutido um tipo normal, mas que terão um layout baseado em hardware. Naturalmente, ocorrem interseções. Se você olhar para os grupos de pessoas que movem o Panamá e movem Valhalla, eles se cruzam mais da metade.


Oleg: Puramente organizacional, aqui você tem um projeto, algum tipo de problema com o desempenho, mas está na junção de vários projetos. O que fazer depois? Como resolver isso? Acontece que isso já é uma troca entre projetos e pessoas, e não entre algumas tarefas abstratas.


Sergey: Tudo é muito simples aqui: se esse é um problema de desempenho com um recurso que está sendo projetado, você precisa ir para as pessoas que estão projetando e dizer: “e assim por diante, o que vamos fazer? Vamos fazer diferente. A discussão começa e o problema está resolvido.


Se o código já existe, ele já funciona. No caso ideal, você corrige esse problema ou, se não puder corrigi-lo completamente, solta o protótipo e, em seguida, chega ao proprietário do código e diz: "Aqui está o protótipo, o que faremos?" Além disso, resolvemos esse problema especificamente para cada caso.


Oleg: Temos pessoas interessadas aqui que não podem participar desse processo, esses são usuários finais.


Sergey: Eles não podem participar exatamente o suficiente para que não sejam pagos por seus salários na Oracle. Se você não precisa de um salário, vá ao OpenJDK e participe.


Oleg: Quão real é? O OpenJDK tem alguns gênios como você, onde as pessoas comuns estão e onde você está. Digamos que algo esteja diminuindo a velocidade para mim, o que devo fazer e como?


Sergey: Se você não conhece o problema, esta é uma pergunta separada, se alguém procurará uma solução para você, esta é uma questão como uma área, um exemplo e assim por diante. Mesmo que você não conheça o problema, talvez faça sentido escrever no OpenJDK e perguntar. Se isso é algo que alguém clica imediatamente na cabeça, as pessoas o agarram. Se não interessar a ninguém, ficará sem resposta.


Oleg: Suponha que eu conheça o problema e até saiba o que precisa ser corrigido.


Sergey: Se você conhece o problema, você vem ao OpenJDK, assina todos os pedaços de papel necessários, oferece um patch, ele é revisado e derramado.


Oleg: É simples assim?


Sergey: Bem, sim, um pouco de burocracia, espere um pouco. Ontem, Tagir ( lany ) pegou uma pequena correção que eu abandonei. Ele só quer ser levado até o fim. Ele começou a lembrá-lo por conta própria. Ele diz: "Droga, o que é isso, eu fiz tudo, planejado, ninguém está revisando". Bem, sim, ninguém está revisando. É julho, metade do escritório de Java está de férias. Eles sairão de férias e farão isso.


Oleg: Férias nos EUA são quase as mesmas datas que normalmente na Rússia?


Sergey: Não, o sistema de férias nos EUA é completamente diferente do da Rússia. Em primeiro lugar, eles são significativamente menores. E também, nos EUA, o sistema de férias está ligado às escolas. Quando você tem filhos em férias - depois em feriados. Assim que as férias começam, toda a América começa a se mover. E como as aulas aqui terminam em meados de junho e começam em meados de agosto, esse delta para férias não é tão grande - apenas dois meses.



Truques do compilador, registrar o posicionamento


Oleg: Já aconteceu que você otimizou algo em casa e depois disso os usuários tiveram que escrever código de maneira diferente? Em termos relativos, se a operação de selecionar uma substring costumava ter um intervalo e agora faz uma cópia completa, essa refatoração altera a maneira como você escreve o código.


Sergey: Certamente foi, mas não vou dar exemplos específicos agora. A questão é: o que as pessoas estabelecem ao escrever código. Se eles precisarem reduzir o desempenho máximo e, para isso, fazem todos os tipos de truques específicos do compilador, devem estar preparados para que o compilador evolua com o tempo e precisam modificar constantemente seu código de acordo com o estado atual do compilador. E isso é ótimo.


Suponhamos que, de repente, depois de 20 anos, Graal venha a ser o compilador principal do HotSpot - então esses pobres terão que reescrever tudo. Isso só acontece se você assumiu uma tarefa técnica - rastrear alterações no compilador. É muito mais simples escrever o código correto sem vínculos diretos, com implementações gerais mais ou menos normais.


A propósito, sobre compiladores - não apenas sobre compiladores Java, mas em geral. Existe a lei de Moore, que não é uma lei, mas apenas uma observação empírica de que o número de transistores dobra a cada ano e meio.


E existe exatamente a mesma lei ( Lei de Proebsting ) que o desempenho do código sem modificação aumenta em 4% a cada um ano e meio ou dois. Esse 4% é o que os usuários finais recebem de graça apenas com a evolução dos compiladores. Não é hardware, ou seja, compiladores.


Oleg: Gostaria de saber de onde vêm essas porcentagens. Isso é algum tipo de ineficiência inicial? Mas, algum dia, esse estoque de ineficiências terminará.


Sergey: Não, é apenas uma questão de desenvolvimento de tecnologia. Saí dos compiladores quando comecei a trabalhar no desempenho. Mas uma vez que eu estava noivo, e a maior descoberta para mim foi feita em 2005 ou 2006. Descobri isso em 2008 porque não li o artigo a tempo.


Uma tarefa muito importante de qualquer geração de código é a alocação de registros. Sabe-se que, em geral, esse problema é NP-completo. É muito difícil resolvê-lo e, portanto, todos os compiladores tentam conduzir algum tipo de algoritmo aproximado com vários graus de qualidade.


E aqui vem um artigo em que os caras provam que, em alguns casos, que cobrem um grande número de compiladores e um grande número de representações internas com certas restrições, existe um algoritmo polinomial exato para a tarefa de alocar a alocação de registros. Viva, vamos lá!


Isso aconteceu em 2005, os compiladores feitos anteriormente não sabiam disso.


Oleg: Agora você pode criar um novo alocador para Java?


Sergey: Agora que existe uma solução teórica, ela pode ser reescrita. Não entrei em detalhes, mas sei que os caras da Excelsior implementaram o algoritmo.


Oleg: Recentemente, fizemos uma entrevista com Cliff Click e ele falou sobre o alocador de gênios insanamente complexo e insano que ele escreveu para Java. Não quer escrever outro?


Sergey: Não.


Oleg: Existe algo normal?


Sergey: Não, ele não é normal. Do meu ponto de vista utilitarista, direi que procuro em assembler e às vezes vejo: "Bem, sim, aqui os registros ficaram ruins". Se eu recorrer a chutar nossos compiladores e reescrevermos o alocador, o que obteremos? Obteremos algum ganho, mas é improvável que eu o veja, exceto nos exemplos em que vi a alocação ineficiente de registros. Enquanto não houver grandes falhas nessa área, sempre haverá algo a ser feito e a obtenção de mais ganhos.


Oleg: Existem áreas de trabalho no JDK em que todo o compilador do compartimento do motor ou a mágica do desempenho chega à superfície? Você diz que precisa escrever um código normal normal e tudo ficará bem, mas parece suspeito.


Sergey: Tudo ficará bem até você precisar de um super duper. Se você precisar muito rápido, esteja preparado para sempre reescrevê-lo. No momento, se você pegar um aplicativo grande e abstrato, em geral, como está escrito - geralmente não desempenha um papel em termos de desempenho.


Por um lado, assim que o coletor de lixo é acionado, ele consome 10-20%, por outro lado, a arquitetura do aplicativo começa a aparecer. O grande problema que eu vi no monte de aplicativos é que eles estão mudando os dados. Pegamos os dados daqui, transferimos para lá, fizemos algumas transformações lá. Em geral, qualquer programa faz exatamente isso. Ele transfere dados de um lugar para outro de alguma forma. Mas se você tiver muitas mudanças no programa, o compilador não ajudará.


Oleg: Você pode tentar rastrear algumas coisas simples, como: esse pedaço de memória está mudando de proprietário e se movendo entre objetos nessa direção.


Sergey: Não, isso é um problema de design. Mas eu não apenas mudo, mas mudo com modificações, faço algo com elas. O maior benefício em aplicativos reais e massivos pode ser obtido se você pensar a respeito: são necessárias muitas mudanças. Em vez de dez, fazer sete já é bom.




(Pode parecer que o mesmo vídeo foi acidentalmente duplicado aqui. Na verdade, tudo é mais simples, Habr armazenou a imagem errada do YouTube)


Coletamos um gato de carne picada


Oleg: Acabamos de ter uma conferência Hydra sobre computação distribuída. E muitas pessoas se preocupam muito com um modelo de custo, determinando o custo de cada operação - muito granular, muito preciso. As pessoas realmente querem escrever todas as instruções, somar o custo de cada uma delas e ver quantas barras seu código precisará. Eu me pergunto como essa abordagem funciona na realidade moderna. , ?


: , . , . , . , . , , , . ?


: : « ».


: , . , . , ? . — , . , , - , .


: , ?


: , ? , . ?


: , - ?


: , — , . - . ? , , , , . , .


: .


: , 1 . , , . , - , - .




performance-


: OpenJDK . ? C++ . — -?


: , , . . OpenJDK 15 . 15 .


: , , . .


: ! , , . , , , , , . , . : , , ( , ), — . , , .


: , ?


: , , Java.


: ?


: , , Valhalla.


: - , , — , ? ? .


: , . — . , , , . , , . . 2-3 , , , , — , .


, , inline-, Java Joker. . , Java- , runtime-. red flag: runtime- 0. ? , out of order- — .


, . , . ? . . baseline, , 5-6 , — . , , - 2 — - . 3% - , . 3% ?


: , ?


: , . performance-, . , , — .


, , . performance- — , - performance- - , . , , .


, , , , performance- — class libraries hotspot, .



«» performance


: , - performance-, . , , ?


: . ? , performance- performance-.


, : , , Oracle, Twitter, Netflix , - . , — . , performance- , .


- , , performance- , ? , , .


, performance- — . : - performance review, , , , , . — performance .


: « , ...», , — . , - — , , , .


Joker


: Joker . ?


: inline-, .


: , value-?


: , , . . , . value Java- . value, value, by value, - by value, value type. , .


. , , , Rémi Forax - , inline-. , , Kotlin inline- , value- Java, .


. value-, , , mvt (minimum value types), LW 1. LW 2 — , . , , , . , , , , performance- , , , , .


: , - -?


: , - . , , , , invokedynamic .


: , , , , .


: , , , . — , , . , ?


: , , , .


: . , , inline- generic- , inline-. , .


: , - ?


: : inline- LW2 . value-, generic, .


« Java -? Valhalla» Joker , - 25-26 2019 . , .

Source: https://habr.com/ru/post/pt463455/


All Articles