Um LinkedList será mais rápido? Devo trocar o `for each` por um `iterador`? Este `ArrayList` deveria ser um `Array`? Este artigo surgiu em resposta a uma otimização tão malévola que ficou permanentemente gravada em minha memória.
Antes de entrarmos de frente em Java e nas maneiras de lidar com a interferência, seja do coletor de lixo ou da troca de contexto, vamos primeiro dar uma olhada nos fundamentos da escrita de código para você mesmo no futuro.
Otimização prematura é a raiz de todo o mal.
Você já ouviu isso antes; Otimização prematura é a raiz de todo o mal. Bem, às vezes. Ao escrever software, acredito firmemente em ser:
tão descritivo quanto possível ; você deve tentar narrar as intenções como se estivesse escrevendo uma história.
tão ideal quanto possível ; o que significa que você deve conhecer os fundamentos da linguagem e aplicá-los de acordo.
Seu código deve expressar intenção, e muito disso está relacionado à maneira como você nomeia métodos e variáveis.
int[10] array1; // bad int[10] numItems; // better int[10] backPackItems; // great
Apenas pelo nome da variável já é possível inferir a funcionalidade.
Embora numItems
seja abstrato, backPackItems
informa muito sobre o comportamento esperado.
Ou digamos que você tenha este método:
List<Countries> visitedCountries() { if(noCountryVisitedYet) return new ArrayList<>(0); } // (...) return listOfVisitedCountries; }
No que diz respeito ao código, isso parece mais ou menos ok.
Podemos fazer melhor? Nós definitivamente podemos!
List<Countries> visitedCountries() { if(noCountryVisitedYet) return Collections.emptyList(); } // (...) return listOfVisitedCountries; }
Ler Collections.emptyList()
é muito mais descritivo do que new ArrayList<>(0);
Imagine que você está lendo o código acima pela primeira vez e se depara com a cláusula guard que verifica se o usuário realmente visitou países. Além disso, imagine que isso esteja enterrado em uma classe longa, lendo Collections.emptyList()
é definitivamente mais descritivo do que new ArrayList<>(0)
, você também está garantindo que seja imutável, garantindo que o código do cliente não possa modificá-lo.
Conheça o seu idioma e use-o de acordo. Se você precisar de um double
, não há necessidade de envolvê-lo em um objeto Double
. O mesmo vale para usar um List
se tudo que você realmente precisa é de um Array
.
Saiba que você deve concatenar Strings usando StringBuilder
ou StringBuffer
se estiver compartilhando estado entre threads:
// don't do this String votesByCounty = ""; for (County county : counties) { votesByCounty += county.toString(); } // do this instead StringBuilder votesByCounty = new StringBuilder(); for (County county : counties) { votesByCounty.append(county.toString()); }
Saiba como indexar seu banco de dados. Antecipe gargalos e armazene o cache de acordo. Todos os itens acima são otimizações. São o tipo de otimizações que vocês devem conhecer e implementar como primeiros cidadãos.
Nunca esquecerei um hack que li alguns anos atrás. Verdade seja dita, o autor voltou atrás rapidamente, mas isso mostra como muito mal pode surgir de boas intenções.
// do not do this, ever! int i = 0; while (i<10000000) { // business logic if (i % 3000 == 0) { //prevent long gc try { Thread.sleep(0); } catch (Ignored e) { } } }
Um hack de coletor de lixo do inferno!
Você pode ler mais sobre por que e como o código acima funciona no artigo original e, embora a exploração seja definitivamente interessante, essa é uma daquelas coisas que você nunca deve fazer.
Thread.sleep(0)
não tem propósito neste bloco
Só comece a forjar algo um pouco mais complexo se, depois de escrever com todas as otimizações padrão que a linguagem fornece , você encontrar um gargalo. Mas evite misturas como as acima.
Se depois de tudo feito o Coletor de Lixo ainda é a peça que oferece resistência, estas são algumas das coisas que você pode tentar:
Se o seu serviço for tão sensível à latência que você não possa permitir o GC, execute com "Epsilon GC" e evite o GC completamente .
-XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC
Obviamente, isso aumentará sua memória até obter uma exceção OOM; portanto, ou é um cenário de curta duração ou seu programa está otimizado para não criar objetos
Se o seu serviço for um pouco sensível à latência, mas a tolerância permitida permitir alguma margem de manobra , execute GC1 e alimente-o com algo como -XX:MaxGCPauseTimeMillis=100
(o padrão é 250ms)
Se o problema surgir de bibliotecas externas , digamos que uma delas chame System.gc()
ou Runtime.getRuntime().gc()
que são coletores de lixo que param o mundo, você pode substituir o comportamento ofensivo executando com -XX:+DisableExplicitGC
-XX:+UnlockExperimentalVMOptions -XX:+UseZGC
. Você também pode querer verificar este benchmark JDK 21 GC .
INÍCIO DA VERSÃO | FIM DA VERSÃO | GC PADRÃO |
---|---|---|
Java 1 | Java4 | Coletor de lixo em série |
Java 5 | Java 8 | Coletor de lixo paralelo |
Java 9 | em andamento | Coletor de Lixo G1 |
Nota 1: desde o Java 15, ZGC
está pronto para produção , mas você ainda precisa ativá-lo explicitamente com -XX:+UseZGC
.
Nota 2: A VM considera as máquinas como de classe servidor se a VM detectar mais de dois processadores e um tamanho de heap maior ou igual a 1792 MB. Se não for de classe de servidor, o padrão será Serial GC .
Em essência, opte pelo ajuste do GC quando estiver claro que as restrições de desempenho do aplicativo estão diretamente ligadas ao comportamento da coleta de lixo e você tiver o conhecimento necessário para fazer ajustes informados. Caso contrário, confie nas configurações padrão da JVM e concentre-se na otimização do código no nível do aplicativo.
u/shiphe - você vai querer ler o comentário completo
Se você está otimizando por sentimento, sem qualquer benchmarking real, você está prestando um péssimo serviço a si mesmo. JMH é a biblioteca Java de fato para testar o desempenho de seus algoritmos. Use-o.
Fixar um processo em um núcleo específico pode melhorar os acessos ao cache. Dependerá do hardware subjacente e de como sua rotina está lidando com os dados. No entanto, esta biblioteca torna-a tão fácil de implementar que, se um método com uso intensivo de CPU estiver atrapalhando você, você vai querer testá-lo.
Essa é uma daquelas bibliotecas que, mesmo que você não precise, vai querer estudar. A ideia é permitir simultaneidade de latência ultrabaixa. Mas a forma como é implementado, desde a simpatia mecânica até o ring buffer, traz muitos conceitos novos. Ainda me lembro de quando o descobri, há sete anos, passando a noite toda para digeri-lo.
A premissa do jvmquake
é que quando as coisas dão errado com a JVM, você quer que ela morra e não trave. Alguns anos atrás, eu estava executando simulações em um cluster HTCondor que estava com restrições de memória restritas e, às vezes, os trabalhos ficavam travados devido a erros de "falta de memória".
Esta biblioteca força a morte da JVM, permitindo que você lide com o erro real. Neste caso específico, o HTCondor reprogramaria automaticamente o trabalho.
O código que me fez escrever este post? Já escrevi muito pior. Eu ainda faço. O melhor que podemos esperar é bagunçar cada vez menos.
Espero ficar descontente ao olhar para meu próprio código daqui a alguns anos.
E isso é um bom sinal.
visitedCountries()
e pela explicação detalhada.Também publicado em wasteofserver.com