Eu sei que a saída para o console é uma operação cara. No interesse da legibilidade do código, às vezes, é bom chamar uma função para gerar texto duas vezes, em vez de ter uma longa sequência de texto como argumento.
Por exemplo, quanto menos eficiente é ter
System.out.println("Good morning.");
System.out.println("Please enter your name");
vs.
System.out.println("Good morning.\nPlease enter your name");
No exemplo, a diferença é apenas uma chamada, println()
mas e se for mais?
Em uma nota relacionada, as instruções que envolvem a impressão de texto podem parecer estranhas ao exibir o código-fonte se o texto a ser impresso for longo. Supondo que o texto em si não possa ser reduzido, o que pode ser feito? Deve ser este o caso em que várias println()
chamadas são feitas? Alguém me disse uma vez que uma linha de código não deveria ter mais de 80 caracteres (IIRC), então o que você faria com
System.out.println("Good morning everyone. I am here today to present you with a very, very lengthy sentence in order to prove a point about how it looks strange amongst other code.");
O mesmo vale para idiomas como C / C ++, pois cada vez que os dados são gravados em um fluxo de saída, uma chamada do sistema deve ser feita e o processo deve ir para o modo kernel (o que é muito caro)?
Respostas:
Existem duas 'forças' aqui, em tensão: desempenho versus legibilidade.
Vamos abordar o terceiro problema primeiro, longas filas:
A melhor maneira de implementar isso e manter a legibilidade é usar a concatenação de strings:
A concatenação constante de String ocorrerá no tempo de compilação e não terá efeito sobre o desempenho. As linhas são legíveis e você pode simplesmente seguir em frente.
Agora, sobre o:
vs.
A segunda opção é significativamente mais rápida. Vou sugerir cerca de 2X o mais rápido .... por quê?
Porque 90% (com uma ampla margem de erro) do trabalho não está relacionado ao descarte dos caracteres na saída, mas é uma sobrecarga necessária para garantir a saída da gravação.
Sincronização
System.out
é umPrintStream
. Todas as implementações de Java que eu conheço sincronizam internamente o PrintStream: Veja o código no GrepCode! .O que isso significa para o seu código?
Isso significa que, toda vez que você liga
System.out.println(...)
para sincronizar seu modelo de memória, você está verificando e aguardando um bloqueio. Quaisquer outros segmentos que chamam System.out também serão bloqueados.Em aplicativos de thread único, o impacto
System.out.println()
geralmente é limitado pelo desempenho de E / S do seu sistema, com que rapidez você pode gravar em um arquivo. Em aplicativos multithread, o bloqueio pode ser mais um problema do que o IO.Flushing
Cada impressão é liberada . Isso faz com que os buffers sejam limpos e aciona uma gravação no nível do console nos buffers. A quantidade de esforço realizado aqui depende da implementação, mas, geralmente, entende-se que o desempenho da liberação está apenas em pequena parte relacionado ao tamanho do buffer que está sendo liberado. Há uma sobrecarga significativa relacionada à liberação, onde os buffers de memória são marcados como sujos, a máquina virtual está executando E / S e assim por diante. Incorrer nessa sobrecarga uma vez, em vez de duas, é uma otimização óbvia.
Alguns números
Eu montei o seguinte pequeno teste:
O código é relativamente simples, imprime repetidamente uma cadeia curta ou longa para a saída. A cadeia longa possui várias linhas novas. Ele mede quanto tempo leva para imprimir 1000 iterações de cada.
Se eu executá-lo no prompt de comando do unix (Linux), redirecionar o
STDOUT
para/dev/null
e imprimir os resultados reaisSTDERR
, posso fazer o seguinte:A saída (no errlog) é semelhante a:
O que isto significa? Deixe-me repetir a última estrofe:
Isso significa que, para todos os efeitos, mesmo que a linha 'longa' seja cerca de cinco vezes maior e contenha várias linhas novas, leva apenas o tempo necessário para produzir a linha curta.
O número de caracteres por segundo a longo prazo é 5 vezes maior e o tempo decorrido é aproximadamente o mesmo ...
Em outras palavras, seu desempenho é escalável em relação ao número de impressoras que você possui, não ao que elas imprimem.
Atualização: o que acontece se você redirecionar para um arquivo, em vez de para / dev / null?
É muito mais lento, mas as proporções são as mesmas ....
fonte
"\n"
pode não ser o terminador de linha correto.println
terminará automaticamente a linha com o (s) caractere (s) certo (s), mas a inserção\n
direta de uma string pode causar problemas. Se você deseja fazer o que é certo, pode ser necessário usar a formatação de sequência ou aline.separator
propriedade do sistema .println
é muito mais limpo.Eu não acho que ter um monte de
println
s seja um problema de design. A meu ver, isso pode ser feito claramente com o analisador de código estático, se é realmente um problema.Mas isso não é um problema, porque a maioria das pessoas não faz IOs assim. Quando eles realmente precisam realizar muitas IOs, eles usam os armazenados em buffer (BufferedReader, BufferedWriter, etc.) quando a entrada é armazenada em buffer, você verá que o desempenho é semelhante o suficiente, e não precisa se preocupar em ter um monte de
println
ou poucosprintln
.Então, para responder à pergunta original. Eu diria que nada mal se você
println
imprimir algumas coisas como a maioria das pessoas usariaprintln
.fonte
Em linguagens de nível superior, como C e C ++, isso é menos problemático do que em Java.
Em primeiro lugar, C e C ++ definem a concatenação de strings em tempo de compilação, para que você possa algo como:
Nesse caso, concatenar a string não é apenas uma otimização que você pode usar, geralmente (etc.) depende do compilador a fazer. Em vez disso, é diretamente exigido pelos padrões C e C ++ (fase 6 da tradução: "Tokens literais de cadeias adjacentes são concatenados.").
Embora isso ocorra à custa de um pouco de complexidade extra no compilador e na implementação, C e C ++ fazem um pouco mais para ocultar a complexidade da produção eficiente de saída do programador. Java é muito mais parecido com a linguagem assembly - cada chamada se
System.out.println
traduz muito mais diretamente em uma chamada para a operação subjacente para gravar os dados no console. Se você deseja armazenar em buffer para melhorar a eficiência, isso deve ser fornecido separadamente.Isso significa, por exemplo, que em C ++, reescrevendo o exemplo anterior, para algo assim:
... normalmente 1 tem quase nenhum efeito sobre a eficiência. Cada uso de
cout
simplesmente depositaria dados em um buffer. Esse buffer seria liberado para o fluxo subjacente quando o buffer fosse preenchido ou o código tentasse ler a entrada do uso (como comstd::cin
).iostream
s também possuem umasync_with_stdio
propriedade que determina se a saída do iostreams é sincronizada com a entrada no estilo C (por exemplo,getchar
). Por padrão,sync_with_stdio
é definido como true; portanto, se, por exemplo, você gravar emstd::cout
, e ler porgetchar
, os dados para os quais você gravoucout
serão liberados quandogetchar
chamados. Você pode definirsync_with_stdio
como false para desativar isso (geralmente feito para melhorar o desempenho).sync_with_stdio
também controla um grau de sincronização entre os threads. Se a sincronização estiver ativada (o padrão), a gravação em um iostream a partir de vários encadeamentos poderá resultar na intercalação dos dados dos encadeamentos, mas impedirá quaisquer condições de corrida. IOW, seu programa executará e produzirá saída, mas se mais de um thread gravar em um fluxo por vez, a mistura arbitrária de dados dos diferentes threads normalmente tornará a saída bastante inútil.Se você desativar a sincronização, a sincronização do acesso de vários encadeamentos também se tornará inteiramente de sua responsabilidade. Gravações simultâneas de vários threads podem / levarão a uma corrida de dados, o que significa que o código tem um comportamento indefinido.
Sumário
O C ++ assume como padrão uma tentativa de equilibrar velocidade com segurança. O resultado é bem-sucedido no código de thread único, mas menos no código de multi-thread. O código multithreaded normalmente precisa garantir que apenas um thread grave em um fluxo por vez para produzir uma saída útil.
1. É possível desativar o buffer para um fluxo, mas na verdade isso é bastante incomum, e quando / se alguém o faz, é provavelmente por um motivo bastante específico, como garantir que toda a saída seja capturada imediatamente, apesar do efeito no desempenho . De qualquer forma, isso só acontece se o código o fizer explicitamente.
fonte
"2^31 - 1 = " + Integer.MAX_VALUE
é armazenado como uma única cadeia interna (JLS Sec 3.10.5 e 15.28 ).Embora o desempenho não seja realmente um problema aqui, a má legibilidade de várias
println
instruções aponta para um aspecto de design ausente.Por que escrevemos uma sequência de muitas
println
declarações? Se fosse apenas um bloco de texto fixo, como um--help
texto em um comando do console, seria muito melhor tê-lo como um recurso separado, lê-lo e gravá-lo na tela, mediante solicitação.Mas geralmente é uma mistura de partes dinâmicas e estáticas. Digamos que temos alguns dados de pedidos simples, por um lado, e algumas partes fixas de texto estático, por outro lado, e essas coisas precisam ser combinadas para formar uma folha de confirmação de pedido. Novamente, também neste caso, é melhor ter um arquivo de texto de recursos diferentes: O recurso seria um modelo, contendo algum tipo de símbolos (espaços reservados), que são substituídos no tempo de execução pelos dados reais do pedido.
A separação da linguagem de programação da linguagem natural tem muitas vantagens - entre elas a internacionalização: você pode precisar traduzir o texto se quiser se tornar multilíngue com o seu software. Além disso, por que uma etapa de compilação é necessária se você deseja apenas uma correção de texto, diga corrigir algum erro de ortografia.
fonte