Tenho uma pergunta relacionada ao desempenho em relação ao uso do StringBuilder. Em um loop muito longo, estou manipulando um StringBuilder
e passando-o para outro método como este:
for (loop condition) {
StringBuilder sb = new StringBuilder();
sb.append("some string");
. . .
sb.append(anotherString);
. . .
passToMethod(sb.toString());
}
A instanciação StringBuilder
em cada ciclo de loop é uma boa solução? E chamar um delete é melhor, como o seguinte?
StringBuilder sb = new StringBuilder();
for (loop condition) {
sb.delete(0, sb.length);
sb.append("some string");
. . .
sb.append(anotherString);
. . .
passToMethod(sb.toString());
}
java
performance
string
stringbuilder
Pier Luigi
fonte
fonte
Na filosofia de escrever código sólido, é sempre melhor colocar o StringBuilder dentro do seu loop. Desta forma, não sai do código a que se destina.
Em segundo lugar, a maior melhoria no StringBuilder vem de dar a ele um tamanho inicial para evitar que cresça enquanto o loop é executado
fonte
Mais rápido, ainda:
Na filosofia de escrever código sólido, o funcionamento interno do método deve ser escondido dos objetos que usam o método. Portanto, não faz diferença da perspectiva do sistema se você redeclarar o StringBuilder dentro do loop ou fora do loop. Uma vez que declará-lo fora do loop é mais rápido e não torna o código mais complicado de ler, reutilize o objeto em vez de restabelecê-lo.
Mesmo se o código fosse mais complicado e você soubesse com certeza que a instanciação do objeto era o gargalo, comente-o.
Três corridas com esta resposta:
Três corridas com a outra resposta:
Embora não seja significativo, definir o
StringBuilder
tamanho do buffer inicial proporcionará um pequeno ganho.fonte
Ok, agora eu entendo o que está acontecendo e faz sentido.
Fiquei com a impressão de que
toString
acabei de passar o subjacentechar[]
para um construtor String que não tirou uma cópia. Uma cópia seria feita na próxima operação de "gravação" (por exemplodelete
). Acredito que foi esse o caso comStringBuffer
alguma versão anterior. (Não é agora.) Mas não -toString
apenas passa a matriz (e índice e comprimento) para oString
construtor público que faz uma cópia.Portanto, no caso de "reutilizar
StringBuilder
", criamos genuinamente uma cópia dos dados por string, usando o mesmo array char no buffer o tempo todo. Obviamente, criar um novo aStringBuilder
cada vez cria um novo buffer subjacente - e então esse buffer é copiado (um tanto sem sentido, em nosso caso particular, mas feito por razões de segurança) ao criar uma nova string.Tudo isso faz com que a segunda versão seja definitivamente mais eficiente - mas ao mesmo tempo, ainda diria que é um código mais feio.
fonte
Como acho que ainda não foi apontado, por causa das otimizações integradas ao compilador Sun Java, que cria automaticamente StringBuilders (StringBuffers pré-J2SE 5.0) quando vê concatenações de String, o primeiro exemplo na pergunta é equivalente a:
O que é mais legível, IMO, a melhor abordagem. Suas tentativas de otimizar podem resultar em ganhos em algumas plataformas, mas potencialmente em perdas em outras.
Mas se você realmente está enfrentando problemas de desempenho, então, com certeza, otimize. Eu começaria especificando explicitamente o tamanho do buffer do StringBuilder, de acordo com Jon Skeet.
fonte
A JVM moderna é muito inteligente sobre coisas como essa. Eu não iria duvidar e fazer algo hacky que é menos fácil de manter / legível ... a menos que você faça benchmarks adequados com dados de produção que validem uma melhoria de desempenho não trivial (e a documente;)
fonte
Com base na minha experiência com o desenvolvimento de software no Windows, eu diria que limpar o StringBuilder durante o loop tem melhor desempenho do que instanciar um StringBuilder com cada iteração. Limpá-lo libera a memória para ser sobrescrita imediatamente, sem a necessidade de alocação adicional. Não estou familiarizado o suficiente com o coletor de lixo Java, mas acho que a liberação e nenhuma realocação (a menos que sua próxima string aumente o StringBuilder) é mais benéfico do que a instanciação.
(Minha opinião é contrária ao que todo mundo está sugerindo. Hmm. É hora de compará-la.)
fonte
A razão pela qual fazer um 'setLength' ou 'deletar' melhora o desempenho é principalmente o código 'aprendendo' o tamanho certo do buffer, e menos para fazer a alocação de memória. Geralmente, eu recomendo deixar o compilador fazer as otimizações de string . No entanto, se o desempenho for crítico, geralmente pré-calcularei o tamanho esperado do buffer. O tamanho padrão do StringBuilder é de 16 caracteres. Se você crescer além disso, será necessário redimensionar. O redimensionamento é onde o desempenho está sendo perdido. Aqui está outro mini-benchmark que ilustra isso:
Os resultados mostram que reutilizar o objeto é cerca de 10% mais rápido do que criar um buffer do tamanho esperado.
fonte
LOL, a primeira vez que vi pessoas comparando o desempenho combinando string no StringBuilder. Para isso, se você usar "+", pode ser ainda mais rápido; D. O objetivo de usar StringBuilder para acelerar a recuperação de toda a string como o conceito de "localidade".
No cenário em que você recupera um valor String com frequência que não precisa de alteração frequente, o Stringbuilder permite um desempenho superior de recuperação de string. E esse é o propósito de usar o Stringbuilder .. por favor, não teste MIS o propósito principal disso ..
Algumas pessoas disseram: O avião voa mais rápido. Portanto, testei com minha bicicleta e descobri que o avião se move mais devagar. Você sabe como eu defino as configurações do experimento; D
fonte
Não significativamente mais rápido, mas em meus testes ele mostra ser, em média, alguns milissegundos mais rápido usando 1.6.0_45 64 bits: use StringBuilder.setLength (0) em vez de StringBuilder.delete ():
fonte
A maneira mais rápida é usar "setLength". Não envolverá a operação de cópia. A maneira de criar um novo StringBuilder deve estar completamente fora . A lentidão para StringBuilder.delete (int start, int end) é porque ele copiará a matriz novamente para a parte de redimensionamento.
Depois disso, o StringBuilder.delete () atualizará o StringBuilder.count para o novo tamanho. Enquanto StringBuilder.setLength () simplifica a atualização de StringBuilder.count para o novo tamanho.
fonte
O primeiro é melhor para os humanos. Se o segundo for um pouco mais rápido em algumas versões de algumas JVMs, e daí?
Se o desempenho for tão crítico, ignore o StringBuilder e escreva o seu próprio. Se você é um bom programador e leva em consideração como seu aplicativo está usando esta função, você deve ser capaz de torná-la ainda mais rápida. Que vale a pena? Provavelmente não.
Por que essa pergunta foi marcada como "pergunta favorita"? Porque a otimização de desempenho é muito divertida, não importa se é prática ou não.
fonte
Não acho que faça sentido tentar otimizar o desempenho dessa forma. Hoje (2019) os dois estados estão rodando cerca de 11 segundos para 100.000.000 de loops no meu laptop I5:
==> 11000 mseg (declaração dentro do loop) e 8236 mseg (declaração fora do loop)
Mesmo que eu esteja executando programas para dedublicação de endereços com alguns bilhões de loops, uma diferença de 2 segundos. para 100 milhões de loops não faz nenhuma diferença porque os programas ficam em execução por horas. Também esteja ciente de que as coisas são diferentes se você tiver apenas uma instrução append:
==> 3416 mseg (loop interno), 3555 mseg (loop externo) A primeira instrução que está criando o StringBuilder dentro do loop é mais rápida nesse caso. E, se você alterar a ordem de execução, é muito mais rápido:
==> 3638 mseg (loop externo), 2.908 mseg (loop interno)
Atenciosamente, Ulrich
fonte
Declare uma vez e atribua a cada vez. É um conceito mais pragmático e reutilizável do que uma otimização.
fonte