O termo "escala da Web" foi aumentado recentemente, e as pessoas também estão tornando seus sistemas mais "domínio amplo", expandindo sua arquitetura de aplicativos. Mas o que exatamente é um domínio completo? Ou como garantir todo o domínio?
A carga de escala mais animada é o domínio mais popular. Por exemplo, os sistemas que suportam acesso ao usuário único também podem suportar 10, 100 ou até 1 milhão de acessos de usuário. Idealmente, nosso sistema deve permanecer o mais "sem estado" possível. Mesmo que um estado deva existir, ele pode ser convertido e transmitido em diferentes terminais de processamento da rede. Quando a carga se torna um gargalo, pode não haver atraso. Portanto, para uma única solicitação, é aceitável levar de 50 a 100 milissegundos. Isso é chamado de escalar.
A extensão tem desempenho completamente diferente na otimização total do domínio. Por exemplo, um algoritmo que garante que um dados seja processado com sucesso também possa processar com sucesso 10, 100 ou até 1 milhão de dados. A complexidade dos eventos (grandes símbolos O) é a melhor descrição, independentemente de esse tipo métrico ser viável ou não. A latência é um assassino de escala de desempenho. Você tentará o seu melhor para processar todas as operações na mesma máquina. Isso é chamado de escalar.
Se a torta puder cair do céu (é claro que isso é impossível), podemos combinar expansão horizontal e expansão vertical. No entanto, hoje vamos apresentar apenas as seguintes maneiras simples de melhorar a eficiência.
Tanto o ForkJoinpool no Java 7 quanto os fluxos de dados paralelos no Java 8 (ParallelStream) são úteis para o processamento paralelo. É particularmente óbvio ao implantar programas Java em processadores com vários núcleos, pois todos os processadores podem acessar a mesma memória.
Portanto, o benefício fundamental desse processamento paralelo é eliminar quase completamente a latência em comparação com a escala em diferentes máquinas entre as redes.
Mas não se confunda com os efeitos do processamento paralelo! Lembre -se dos dois pontos a seguir:
Reduzir a complexidade do algoritmo é sem dúvida a maneira mais eficaz de melhorar o desempenho. Por exemplo, para um método HashMap Instância Lookup (), a complexidade de eventos O (1) ou a complexidade do espaço O (1) são os mais rápidos. Mas essa situação é frequentemente impossível, e muito menos alcançá -la facilmente.
Se você não puder reduzir a complexidade do algoritmo, também poderá melhorar o desempenho encontrando pontos -chave no algoritmo e melhorando os métodos. Suponha que tenhamos o seguinte diagrama de algoritmo:
A complexidade total do tempo desse algoritmo é O (n3) e, se calculada em uma ordem de acesso separada, também pode ser concluído que a complexidade é O (n x o x p). De qualquer forma, encontraremos alguns cenários estranhos quando analisarmos este código:
Sem a referência de dados de produção, podemos tirar conclusões facilmente de que devemos otimizar "operações de alta sobrecarga". Mas as otimizações que fizemos não tiveram nenhum efeito nos produtos entregues.
A regra de ouro otimizada nada mais é do que o seguinte:
Isso é tudo para a teoria. Supondo que descobrimos que o problema ocorra no ramo certo, é provável que o processamento simples no produto perca a resposta devido a uma grande quantidade de tempo (assumindo que os valores de N, O e P sejam muito grandes), observe que a complexidade do tempo da filial esquerda mencionada no artigo é O (n3). Os esforços feitos aqui não podem ser estendidos, mas podem economizar tempo para os usuários e adiar as melhores melhorias de desempenho até mais tarde.
Aqui estão 10 dicas para melhorar o desempenho do Java:
1. Use StringBuilder
O StingBuilder deve ser usado por padrão em nosso código Java, e o operador + deve ser evitado. Talvez você tenha opiniões diferentes sobre o açúcar de sintaxe do StringBuilder, como:
String x = "a" + args.length + "b";
Será compilado como:
0 novo java.lang.stringbuilder [16] 3 dup4 ldc <string "a"> [18] 6 Invokespecial java.lang.stringbuilder (java.lang.string) [20] 9 aload_0 [args] 10 ArrayLength11 [23] 14 LDC <String "B"> [27] 16 InvokeVirtual java.lang.stringbuilder.append (java.lang.string): java.lang.stringbuilder [29] 19 Invokevirtual
Mas o que exatamente aconteceu? Você precisa usar as seguintes seções para melhorar a string a seguir?
String x = "a" + args.length + "b"; if (args.length == 1) x = x + args [0];
Agora usamos o segundo StringBuilder, que não consome memória extra na pilha, mas pressiona o GC.
Stringbuilder x = new stringbuilder ("a"); x.append (args.length); x.append ("b"); if (args.length == 1); x.append (args [0]);No exemplo acima, se você confiar no compilador Java para gerar implicitamente a instância, o efeito compilado não tem nada a ver com se a instância do StringBuilder é usada. Lembre -se: no ramo Nope, o tempo gasto em cada ciclo da CPU é gasto no GC ou alocando espaço padrão para o StringBuilder, e estamos desperdiçando o tempo n x o x p.
De um modo geral, o uso do StringBuilder é melhor do que usar o operador +. Se possível, selecione StringBuilder se você precisar passar referências em vários métodos, porque a string consome recursos adicionais. Jooq usa esse método ao gerar instruções SQL complexas. Apenas um StringBuilder é usado durante toda a passagem do SQL da árvore de sintaxe abstrata.
O que é ainda mais trágico é que, se você ainda estiver usando o StringBuffer, use StringBuilder em vez do StringBuffer. Afinal, realmente não há muitos casos em que as cordas precisam ser sincronizadas.
Expressões regulares dão às pessoas a impressão de que elas são rápidas e fáceis. Mas o uso de expressões regulares em Nope Branch seria a pior decisão. Se você precisar usar expressões regulares em código intensivo em computação, pelo menos cache o padrão para evitar compilar repetidamente o padrão.
Padrão final estático pesado_regex = padrony.compile ("((((x)*y)*z)*");Se apenas uma expressão regular simples como a seguinte for usada:
String [] partes = ipaddress.split ("//.");É melhor usar uma matriz normal de char [] ou operação baseada em índice. Por exemplo, o código a seguir com baixa legibilidade na verdade desempenha o mesmo papel.
int length = ipaddress.Length (); int offset = 0; int parte = 0; para (int i = 0; i <comprimento; i ++) {if (i == comprimento - 1 || ipaddress.charat (i+1) == '.O código acima também mostra que a otimização prematura não tem sentido. Embora comparado com o método Split (), esse código é menos sustentável.
Desafio: amigos inteligentes podem criar algoritmos mais rápidos?
Expressões regulares são muito úteis, mas também precisam pagar um preço quando usadas. Especialmente quando você está no fundo de ramos, deve evitar o uso de expressões regulares a todo custo. Também tenha cuidado com vários métodos de string jdk que usam expressões regulares, como string.replaceall () ou string.split (). Você pode optar por usar uma biblioteca de desenvolvimento mais popular, como o Apache Commons Lang, para executar operações de string.
Essa sugestão não se aplica a ocasiões gerais, mas apenas a cenários profundos em Nope Branch. Mesmo assim, você deve entender algo. O método de escrita de loop de formato Java 5 é tão conveniente que podemos esquecer o método de loop interno, como:
para (String Value: Strings) {// Faça algo útil aqui}Sempre que o código é executado nesse loop, se a variável Strings for um iterável, o código criará automaticamente uma instância do iterador. Se você estiver usando o ArrayList, a máquina virtual alocará automaticamente 3 tipo de memória do tipo inteiro no objeto na pilha.
classe privada ITR implementa o iterador <E> {int cursor; int lastret = -1; int esperamodCount = modCount; // ...Você também pode usar o seguinte método de loop equivalente para substituir o loop acima para o loop, apenas "desperdiçando" um pedaço de cirurgia plástica na pilha, o que é bastante econômico.
int size = strings.size (); para (int i = 0; i <tamanho; i ++) {String Valor: strings.get (i); // Faça algo útil aqui}Se o valor da string no loop não mudar muito, você também poderá usar matrizes para implementar o loop.
para (String Value: StringArray) {// Faça algo útil aqui}Seja da perspectiva da leitura e da escrita fáceis, ou da perspectiva do design da API, iteradores, interfaces iteráveis e loops para cada um é muito útil. Mas, ao custo, ao usá -los, um objeto adicional é criado na pilha para cada criança do loop. Se o loop for executado muitas vezes, tenha cuidado para evitar gerar instâncias sem sentido. É melhor usar o método básico de loop de ponteiro para substituir o iterador mencionado acima, interface iterável e loop foreach.
Algumas opiniões que estão se opondo ao acima (especialmente substituindo os iteradores por ponteiros) são discutidos no Reddit para obter detalhes.
Alguns métodos são caros. Tomando o ramo não um exemplo, não mencionamos os métodos relevantes das folhas, mas este pode ser encontrado. Suponha que nosso driver JDBC precise superar todas as dificuldades para calcular o valor de retorno do método ResultSet.wasnull (). A estrutura do SQL que implementamos para nós mesmos pode ser assim:
if (type == Integer.class) {resultado = (t) eranull (rs, inteiro.valueof (rs.getInt (index)));} // e então ... estático final <t> t wasnull (Resultset rs, valor t) lança sqLexception {return rs.wasnull ()? nulo: valor;}Na lógica acima, o método ResultSet.wasNull () é chamado toda vez que o valor int é obtido no conjunto de resultados, mas o método de getint () é definido como:
Tipo de retorno: valor variável; Se o resultado da consulta SQL for nulo, retorne 0.
Portanto, uma maneira simples e eficaz de melhorá -lo é a seguinte:
Final estático <t estende o número> t wasnull (resultado rs, valor t) lança sqlexception {return (value == null || (value.intValue () == 0 && rs.wasnull ()))? nulo: valor;}Isso é uma brisa.
Método de cache chama as chamadas para substituir métodos altos nos nós nos nós foliares ou evitar chamar métodos de alto nível alto se a convenção do método permitir.
O acima introduz o uso de um grande número de genéricos no exemplo do Jooq, resultando no uso de classes de byte, curto, int e de wrapper. Mas pelo menos os genéricos não devem ser uma limitação de código até serem especializados nos projetos Java 10 ou Valhalla. Porque pode ser substituído pelo seguinte método:
// armazenado no número inteiro i = 817598;
... se você escrever desta forma:
// armazenar na pilha int i = 817598;
As coisas podem piorar ao usar matrizes:
// Três objetos foram gerados no inteiro heap [] i = {1337, 424242};... se você escrever desta maneira:
// apenas um objeto é gerado no heap int [] i = {1337, 424242};Quando estamos no fundo do ramo, devemos fazer o possível para evitar o uso de aulas de embalagem. A desvantagem de fazer isso é que ele coloca muita pressão sobre o GC. O GC estará muito ocupado para limpar objetos gerados pela classe de embalagem.
Portanto, um método de otimização eficaz é usar tipos de dados básicos, matrizes de comprimento fixo e usar uma série de variáveis divididas para identificar a posição do objeto na matriz.
Trove4J, que segue o protocolo LGPL, é uma biblioteca de classe de coleta Java, que nos fornece uma melhor implementação de desempenho do que a matriz de modelagem Int [].
A exceção a seguir é para esta regra: como os tipos booleanos e bytes não são suficientes para permitir que o JDK forneça um método de cache para ele. Podemos escrever desta maneira:
Booleano A1 = true; // ... Açúcar de sintaxe para: boolean A2 = boolean.valueof (true); byte b1 = (byte) 123; // ... Açúcar de sintaxe para: byte b2 = byte.valueof ((byte) 123);
Outros tipos básicos de números inteiros também têm situações semelhantes, como char, curta, int e longa.
Não abaixe automaticamente esses tipos primitivos inteiros ao ligar para construtores ou ligue para o método thetype.valueof ().
Além disso, não ligue para os construtores nas classes do invólucro, a menos que você queira obter uma instância que não seja criada na pilha. A vantagem de fazer isso é dar a você uma enorme piada do Dia dos Fools de April para seus colegas.
Obviamente, se você deseja experimentar a biblioteca de funções fora do heap, embora isso possa ser misturado com muitas decisões estratégicas, em vez da solução local mais otimista. Para um artigo interessante sobre o armazenamento não-heap escrito por Peter Lawrey e Ben Cotton, clique em: OpenJDK e Hashmap-Deixe os veteranos dominarem com segurança (armazenamento não heap!) Novas técnicas.
Atualmente, linguagens de programação funcionais como o Scala incentivam a recursão. Porque a recursão geralmente significa recompensa da cauda que pode ser decomposta em indivíduos otimizados individualmente. Seria melhor se a linguagem de programação que você usa pudesse apoiá -la. Mas, mesmo assim, tenha cuidado para que o ligeiro ajuste do algoritmo transforme a recursão da cauda em recursão comum.
Espero que o compilador possa detectar isso automaticamente, caso contrário, teríamos desperdiçado muitos quadros de pilha para coisas que podem ser feitas com apenas algumas variáveis locais.
Não há nada a dizer nesta seção, exceto que iterando o máximo possível no ramo não, em vez de recursão.
Quando queremos iterar sobre um mapa salvo em pares de valor-chave, precisamos encontrar um bom motivo para o seguinte código:
para (K -Key: Map.KeySet ()) {V Valor: map.get (key);}Sem mencionar o seguinte método de escrita:
para (entrada <k, v> entrada: map.entryset ()) {k key = entradas.getKey (); v value = entradas.getValue ();}Quando usamos o NOPE Branch, devemos usar o mapa com cautela. Porque muitas operações de acesso que parecem ter uma complexidade de tempo de O (1) são realmente compostas por uma série de operações. E o acesso em si não é gratuito. Pelo menos, se você precisar usar o mapa, precisará usar o método de entrada () para iterar! Dessa forma, queremos acessar apenas uma instância do map.entry.
Quando for necessário iterar sobre o mapa na forma de pares de valor-chave, não se esqueça de usar o método de entrada ().
8. Use enumset ou enummap
Em alguns casos, como ao usar um mapa de configuração, podemos conhecer antecipadamente o valor da chave salvo no mapa. Se esse valor de chave for muito pequeno, devemos considerar o uso de enumset ou enummap em vez de usar o hashset ou hashmap que geralmente usamos. O código a seguir fornece uma explicação clara:
objeto transitório privado [] vals; public v put (k key, v valor) {// ... int index = key.ordinal (); vals [index] = MaskNull (value); // ...}A principal implementação do código anterior é que usamos matrizes em vez de tabelas de hash. Especialmente ao inserir novos valores no mapa, tudo o que você precisa fazer é obter um número de sequência constante gerado pelo compilador para cada tipo de enumeração. Se houver uma configuração global de mapa (por exemplo, apenas uma instância), o Enummap alcançará um desempenho mais pendente do que o hashmap sob a pressão do aumento da velocidade de acesso. O motivo é que o Enummap usa um pouco menos de memória de heap do que o hashmap, e o hashmap chama o método hashcode () e o método Equals () em cada valor de chave.
Enum e Enummap são amigos íntimos. Quando usamos valores-chave semelhantes às estruturas semelhantes a enum, devemos considerar declarar esses valores-chave como tipos de enumeração e usá-los como teclas de enummap.
No caso em que o enummap não possa ser usado, pelo menos os métodos hashCode () e Equals () devem ser otimizados. Um bom método hashcode () é necessário porque impede chamadas desnecessárias para o método de alta sobre ponta ().
Na estrutura de herança de cada classe, são necessários objetos simples facilmente aceitáveis. Vamos dar uma olhada em como o jooq's org.jooq.table é implementado?
O método de implementação mais simples e mais rápido HashCode () é o seguinte:
// abstracttable Uma implementação básica de uma tabela geral: @OverridePublic int hashCode () {// [#1938] em comparação com as paradas padrão, essa é uma implementação HashCode () mais eficiente. Retornar name.hashcode ();}Nome é o nome da tabela. Nem precisamos considerar o esquema ou outros atributos da tabela, porque os nomes de tabela geralmente são únicos no banco de dados. E o nome da variável é uma string, que por si só já armazenou em cache um valor hashcode ().
Os comentários neste código são muito importantes porque o AbstractTable herdado do abstratoQueryPart é a implementação básica de qualquer elemento de árvore de sintaxe abstrato. Os elementos de árvores de sintaxe abstrata comuns não têm nenhum atributo, portanto, eles não podem ter nenhuma fantasia sobre otimizar a implementação do método hashcode (). O método HashCode () substituído é o seguinte:
// AbstractQueryPart Uma árvore de sintaxe abstrata geral Implementação básica: @OverridePublic int hashCode () {// Esta é uma implementação padrão em funcionamento. // A subclasse de implementação específica deve substituir esse método para melhorar o desempenho. Return create (). renderinlined (this) .hashcode ();}Em outras palavras, todo o fluxo de trabalho de renderização do SQL é acionado para calcular o código de hash para um elemento de árvore de sintaxe abstrato normal.
O método iguals () é ainda mais interessante:
// Implementação básica de tabela geral abstrataTable: @OverridePublic boolean é igual a (objeto que) {if (this == that) {return true;} // [#2144] antes de chamar o método de abstrato de abstrato de abstratePeryPart.Equals (), // você pode saber se os objetos não são iguais. if (essa instância de abstractTable) {if (stringUtils.equals (nome, ((((abstractTable <?>) que) .name)))) {return super.equals (that);} retorna false;} retornar false;}Primeiro, não use o método iguals () muito cedo (não apenas em não), se:
NOTA: Se usarmos a instância muito cedo para verificar os tipos compatíveis, as seguintes condições realmente incluem argumento == null. Já expliquei isso no meu blog anterior, consulte 10 práticas recomendadas de codificação Java.
Após o término da comparação das situações acima, devemos ser capazes de tirar algumas conclusões. Por exemplo, o método tabela.equals () do jooq é usado para comparar se as duas tabelas são as mesmas. Independentemente do tipo de implementação específico, eles devem ter o mesmo nome de campo. Por exemplo, os dois elementos a seguir não podem ser os mesmos:
Se pudermos determinar facilmente se o parâmetro recebido é igual à própria instância (isso), podemos desistir da operação se o resultado for falso. Se o resultado do retorno for verdadeiro, podemos julgar ainda mais a super implementação da classe pai. No caso em que a maioria dos objetos comparados não é igual, podemos encerrar o método o mais cedo possível para salvar o tempo de execução da CPU.
Alguns objetos têm maior semelhança que outros.
No JoOQ, a maioria das instâncias da tabela é gerada pelo gerador de código de Jooq, e o método Equals () dessas instâncias foi profundamente otimizado. Dezenas de outros tipos de tabela (tabelas derivadas), funções com valor de tabela, tabelas de matriz, tabelas unidas, tabelas de pivô, expressões de tabela comuns etc., mantendo a implementação básica do método iguals ().
Finalmente, há outra situação que pode ser aplicada a todos os idiomas e não apenas ao Java. Além disso, o ramo não que estudamos anteriormente também será útil no entendimento de O (N3) a O (n log n).
Infelizmente, muitos programadores usam algoritmos locais simples para considerar o problema. Eles estão acostumados a resolver problemas passo a passo. Esta é a forma "sim/ou" de imperativa. Esse estilo de programação é fácil de modelar "quadro geral" ao converter da programação imperativa pura para a programação orientada a objetos para a programação funcional, mas esses estilos não têm o que só existe em SQL e R:
Programação declarativa.
No SQL, podemos declarar o efeito necessário ao banco de dados sem considerar a influência do algoritmo. O banco de dados pode adotar o melhor algoritmo de acordo com o tipo de dados, como restrições, chaves, índices etc.
Em teoria, primeiro tivemos idéias básicas após o SQL e os cálculos relacionais. Na prática, os fornecedores de SQL implementaram otimizadores eficientes baseados em despesas gerais (otimistas baseados em custos) nas últimas décadas. Então, na versão de 2010, finalmente descobrimos todo o potencial do SQL.
Mas não precisamos implementar o SQL usando o método set. Todos os idiomas e bibliotecas suportam conjuntos, coleções, bolsas e listas. O principal benefício do uso do conjunto é que ele pode tornar nosso código conciso e claro. Por exemplo, o seguinte método de escrita:
Someset cruzar um pouco de mãe
Em vez de
// o método de escrita anterior do Java 8 set resultado = new HashSet (); para (candidato a objeto: SomEset) if (sometherset.contains (candidato)) result.add (candidato); // mesmo que o java 8 seja usado, não é muito útil.Someset.Stream ().
Algumas pessoas podem ter opiniões diferentes sobre programação funcional e Java 8, que podem nos ajudar a escrever algoritmos mais simples e simples. Mas essa visão não é necessariamente verdadeira. Podemos converter o loop imperativo Java 7 na coleção de fluxos do Java 8, mas ainda usamos o mesmo algoritmo. Mas as expressões no estilo SQL são diferentes:
Someset cruzar um pouco de mãeO código acima pode ter 1000 implementações diferentes em diferentes motores. O que estamos estudando hoje é converter automaticamente dois conjuntos em enumset antes de chamar a operação intersect. Até podemos executar operações de cruzamento paralelo sem chamar o método de fluxo subjacente.parallel ().
Neste artigo, discutimos otimizações sobre não ramificação. Por exemplo, profundamente em algoritmos de alta complexidade. Como desenvolvedores da JoOQ, estamos felizes em otimizar a geração SQL.
Jooq está no "fundo da cadeia alimentar" porque é a última API chamada pelo nosso programa de computador ao deixar a JVM e entrar no DBMS. Localizado na parte inferior da cadeia alimentar, significa que qualquer linha leva o tempo n x o x p quando é executado no Jooq, então quero otimizá -la o mais rápido possível.
Nossa lógica de negócios pode não ser tão complicada quanto a Nope Branch. No entanto, a estrutura básica pode ser muito complexa (estrutura local SQL, bibliotecas locais etc.). Portanto, precisamos seguir os princípios mencionados hoje, usar o controle de missão Java ou outras ferramentas para verificar se existe alguma área que precise de otimização.
Link original: Jaxenter Tradução: Importnew.com - Sempre no link de tradução da estrada: http://www.importnew.com/16181.html