Temos que criar Strings o tempo todo para a saída do log e assim por diante. Nas versões do JDK, aprendemos quando usarStringBuffer
(muitos anexos, segurança de thread) e StringBuilder
(muitos anexos, não-thread-safe).
Qual é o conselho sobre o uso String.format()
? É eficiente ou somos forçados a manter a concatenação para one-liners onde o desempenho é importante?
por exemplo, feio estilo antigo,
String s = "What do you get if you multiply " + varSix + " by " + varNine + "?";
vs. novo estilo organizado (String.format, que é possivelmente mais lento),
String s = String.format("What do you get if you multiply %d by %d?", varSix, varNine);
Nota: meu caso de uso específico são as centenas de seqüências de log 'one-liner' em todo o meu código. Eles não envolvem um loop, por isso StringBuilder
é muito pesado. Estou interessado String.format()
especificamente.
Respostas:
Eu escrevi uma turma pequena para testar qual o melhor desempenho dos dois e + vem à frente do formato. por um fator de 5 a 6. Tente você mesmo
A execução do acima para N diferente mostra que ambos se comportam linearmente, mas
String.format
são de 5 a 30 vezes mais lentos.O motivo é que, na implementação atual,
String.format
primeiro analisa a entrada com expressões regulares e depois preenche os parâmetros. A concatenação com plus, por outro lado, é otimizada pelo javac (não pelo JIT) e usaStringBuilder.append
diretamente.fonte
Tomei hhafez código e acrescentou um teste de memória :
Eu executo isso separadamente para cada abordagem, o operador '+', String.format e StringBuilder (chamando toString ()), para que a memória usada não seja afetada por outras abordagens. Adicionei mais concatenações, criando a string como "Blah" + i + "Blah" + i + "Blah" + i + "Blah".
O resultado é o seguinte (média de 5 execuções cada):
Tempo de Aproximação (ms) Memória alocada (longa)
operador '+' 747 320,504
String.format 16484 373,312
StringBuilder 769 57,344
Podemos ver que String '+' e StringBuilder são praticamente idênticos em termos de tempo, mas StringBuilder é muito mais eficiente no uso de memória. Isso é muito importante quando temos muitas chamadas de log (ou quaisquer outras instruções que envolvem cadeias) em um intervalo de tempo suficientemente curto para que o Garbage Collector não consiga limpar as muitas instâncias de cadeias resultantes do operador '+'.
E uma observação, BTW, não se esqueça de verificar o nível de log antes de construir a mensagem.
Conclusões:
fonte
+
operador compila para oStringBuilder
código equivalente . Marcas de micropreenchimento como essa não são uma boa maneira de medir o desempenho - por que não usar o jvisualvm, ele está no jdk por um motivo.String.format()
será mais lento, mas devido ao tempo para analisar a sequência de formatação, em vez de qualquer alocação de objeto. Adiar a criação de artefatos de registro até que você tenha certeza de que eles são necessários é um bom conselho, mas se isso tiver um impacto no desempenho, ele estará no lugar errado.And a note, BTW, don't forget to check the logging level before constructing the message.
não é um bom conselho. Supondo que estamos falandojava.util.logging.*
especificamente, verificar o nível de log é quando você está falando sobre fazer processamento avançado que causaria efeitos adversos em um programa que você não desejaria quando um programa não tivesse o log ativado no nível apropriado. A formatação de string não é esse tipo de processamento. A formatação faz parte dajava.util.logging
estrutura, e o próprio criador de logs verifica o nível de criação de log antes que o formatador seja chamado.Todos os benchmarks apresentados aqui apresentam algumas falhas , portanto, os resultados não são confiáveis.
Fiquei surpreso que ninguém usasse o JMH para fazer benchmarking, então usei .
Resultados:
Unidades são operações por segundo, quanto mais, melhor. Código fonte de referência . Foi utilizada a Java Virtual Machine do OpenJDK IcedTea 2.5.4.
Portanto, o estilo antigo (usando +) é muito mais rápido.
fonte
Seu velho estilo feio é compilado automaticamente pelo JAVAC 1.6 como:
Portanto, não há absolutamente nenhuma diferença entre isso e o uso de um StringBuilder.
String.format é muito mais pesado, pois cria um novo Formatador, analisa sua string de formato de entrada, cria um StringBuilder, acrescenta tudo a ele e chama toString ().
fonte
+
e deStringBuilder
fato. Infelizmente, há muitas informações erradas em outras respostas neste tópico. Estou quase tentado a mudar a perguntahow should I not be measuring performance
.O String.format do Java funciona assim:
se o destino final desses dados for um fluxo (por exemplo, renderizar uma página da Web ou gravar em um arquivo), você poderá montar os pedaços de formato diretamente no seu fluxo:
Especulo que o otimizador otimizará o processamento da string de formato. Nesse caso, você terá um desempenho amortizado equivalente para desenrolar manualmente seu String.format em um StringBuilder.
fonte
String.format
loops internos (executando milhões de vezes) resultou em mais de 10% do meu tempo de execução gastojava.util.Formatter.parse(String)
. Isso parece indicar que, em loops internos, você deve evitar chamarFormatter.format
ou qualquer coisa que o chamar, incluindoPrintStream.format
(uma falha na lib padrão do Java, IMO, especialmente porque você não pode armazenar em cache a sequência de formato analisada).Para expandir / corrigir a primeira resposta acima, não é a tradução com a qual String.format ajudaria.
O que o String.format ajudará é quando você estiver imprimindo uma data / hora (ou um formato numérico, etc.), onde existem diferenças de localização (l10n) (ou seja, alguns países imprimirão 04Feb2009 e outros imprimirão Feb042009).
Com a tradução, você está apenas falando sobre mover quaisquer strings externalizáveis (como mensagens de erro e outras coisas) para um pacote configurável de propriedades, para que você possa usar o pacote correto para o idioma certo, usando ResourceBundle e MessageFormat.
Observando tudo o que foi dito acima, eu diria que a concatenação String.format versus planilha se resume ao que você preferir. Se você preferir olhar as chamadas para .format do que a concatenação, faça isso de qualquer maneira.
Afinal, o código é lido muito mais do que está escrito.
fonte
No seu exemplo, o desempenho provavelmente não é muito diferente, mas há outros problemas a serem considerados: a fragmentação da memória. Até a operação concatenada está criando uma nova string, mesmo que seja temporária (leva tempo para GC e é mais trabalho). String.format () é apenas mais legível e envolve menos fragmentação.
Além disso, se você estiver usando muito um determinado formato, não esqueça que pode usar a classe Formatter () diretamente (tudo o que String.format () faz é instanciar uma instância do Formatter de uso único).
Além disso, você deve estar ciente de outra coisa: tenha cuidado ao usar substring (). Por exemplo:
Essa cadeia grande ainda está na memória, porque é assim que as substrings Java funcionam. Uma versão melhor é:
ou
A segunda forma é provavelmente mais útil se você estiver fazendo outras coisas ao mesmo tempo.
fonte
Geralmente você deve usar String.Format porque é relativamente rápido e suporta a globalização (supondo que você esteja realmente tentando escrever algo que é lido pelo usuário). Também facilita a globalização se você estiver tentando traduzir uma string em vez de 3 ou mais por instrução (especialmente para idiomas que possuem estruturas gramaticais drasticamente diferentes).
Agora, se você nunca planeja traduzir nada, confie na conversão incorporada de Java dos operadores + em
StringBuilder
. Ou useStringBuilder
explicitamente o Java .fonte
Outra perspectiva apenas do ponto de vista de registro.
Eu vejo muitas discussões relacionadas ao logon neste tópico, então pensei em adicionar minha experiência em resposta. Pode ser que alguém ache útil.
Eu acho que a motivação do log usando o formatador vem de evitar a concatenação de strings. Basicamente, você não deseja ter uma sobrecarga de concat de strings se não quiser registrá-lo.
Você realmente não precisa concat / formatar, a menos que queira fazer logon. Digamos que se eu definir um método como este
Nesta abordagem, o cancelador / formatador não é realmente chamado se for uma mensagem de depuração e debugOn = false
Embora ainda seja melhor usar o StringBuilder em vez do formatador aqui. A principal motivação é evitar nada disso.
Ao mesmo tempo, não gosto de adicionar o bloco "if" para cada instrução de log desde
Portanto, prefiro criar uma classe de utilitário de log com métodos como acima e usá-la em qualquer lugar sem se preocupar com o desempenho atingido e quaisquer outros problemas relacionados a ela.
fonte
Acabei de modificar o teste de Hhafez para incluir o StringBuilder. O StringBuilder é 33 vezes mais rápido que o String.format usando o cliente jdk 1.6.0_10 no XP. O uso da opção -server reduz o fator para 20.
Embora isso possa parecer drástico, considero relevante apenas em casos raros, porque os números absolutos são bastante baixos: 4 s para 1 milhão de chamadas String.format simples é uma espécie de ok - desde que eu os use para registrar ou gostar.
Atualização: Como apontado por sjbotha nos comentários, o teste StringBuilder é inválido, pois está faltando uma final
.toString()
.O fator de aceleração correto de
String.format(.)
aStringBuilder
é 23 na minha máquina (16 com a-server
chave).fonte
Aqui está a versão modificada da entrada hhafez. Inclui uma opção do construtor de cadeias.
}
Time after for loop 391 Time after for loop 4163 Time after for loop 227
fonte
A resposta para isso depende muito de como o seu compilador Java específico otimiza o bytecode gerado. Strings são imutáveis e, teoricamente, cada operação "+" pode criar uma nova. Mas seu compilador quase certamente otimiza as etapas intermediárias na construção de seqüências longas. É perfeitamente possível que ambas as linhas de código acima gerem exatamente o mesmo bytecode.
A única maneira real de saber é testar o código iterativamente no seu ambiente atual. Escreva um aplicativo QD que concatene seqüências de caracteres nos dois sentidos de forma iterativa e veja como elas se esgotam.
fonte
Considere usar
"hello".concat( "world!" )
para um pequeno número de seqüências de caracteres na concatenação. Poderia ser ainda melhor para desempenho do que outras abordagens.Se você tiver mais de três seqüências, considere usar StringBuilder ou apenas String, dependendo do compilador que você usa.
fonte