Por que o System.out.println é tão lento?

Isso é algo comum a todas as linguagens de programação? Fazer várias impressões seguidas por um println parece mais rápido, mas movendo tudo para uma string e imprimindo que parece mais rápido. Por quê?

EDIT: Por exemplo, Java pode encontrar todos os números primos até 1 milhão em menos de um segundo – mas a impressão, em seguida, todos para fora em sua própria impressão pode levar alguns minutos! Até 10 bilhões de horas podem ser impressas!

EX:

package sieveoferatosthenes; public class Main { public static void main(String[] args) { int upTo = 10000000; boolean primes[] = new boolean[upTo]; for( int b = 0; b < upTo; b++ ){ primes[b] = true; } primes[0] = false; primes[1] = false; int testing = 1; while( testing <= Math.sqrt(upTo)){ testing ++; int testingWith = testing; if( primes[testing] ){ while( testingWith = upTo){ } else{ primes[testingWith] = false; } } } } for( int b = 2; b < upTo; b++){ if( primes[b] ){ System.out.println( b ); } } } } 

println não é lento, é o PrintStream subjacente que está conectado ao console, fornecido pelo sistema operacional de hospedagem.

Você pode verificar você mesmo: compare o despejo de um arquivo de texto grande no console com o mesmo arquivo de texto em outro arquivo:

 cat largeTextFile.txt cat largeTextFile.txt > temp.txt 

Leitura e escrita são semelhantes e proporcionais ao tamanho do arquivo (O (n)), a única diferença é que o destino é diferente (console comparado ao arquivo). E isso é basicamente o mesmo com o System.out .


A operação subjacente do SO (exibindo chars em uma janela de console) é lenta porque

  1. Os bytes devem ser enviados para o aplicativo de console (deve ser bem rápido)
  2. Cada char deve ser renderizado usando (geralmente) uma fonte de tipo verdadeiro (isso é muito lento, desligar anti-aliasing pode melhorar o desempenho, btw)
  3. A área exibida pode ter que ser rolada para acrescentar uma nova linha à área visível (melhor caso: operação de transferência de bloco de bits, no pior caso: nova renderização da área de texto completa)

Eu acredito que isso é por causa do buffer . Uma citação do artigo:

Outro aspecto do buffering diz respeito à saída de texto para uma janela de terminal. Por padrão, System.out (um PrintStream) é armazenado em buffer de linha, o que significa que o buffer de saída é liberado quando um caractere de nova linha é encontrado. Isso é importante para a interatividade, onde você gostaria de ter um prompt de input exibido antes de inserir qualquer input.

Uma citação explicando os buffers da wikipedia:

Na ciência da computação, um buffer é uma região de memory usada para manter dados temporariamente enquanto ele está sendo movido de um lugar para outro. Normalmente, os dados são armazenados em um buffer, uma vez que são recuperados de um dispositivo de input (como um mouse) ou apenas antes de serem enviados para um dispositivo de saída (como, por exemplo, alto-falantes)

 public void println() 

Encerre a linha atual escrevendo a string do separador de linha. A cadeia do separador de linha é definida pela propriedade do sistema line.separator e não é necessariamente um único caractere de nova linha (‘\ n’).

Assim, o buffer é liberado quando você faz println que significa que a nova memory deve ser alocada, etc, o que torna a impressão mais lenta. Os outros methods que você especificou exigem menor liberação de buffers, portanto, são mais rápidos.

System.out é uma class estática do PrintStream . PrintStream tem, entre outras coisas, os methods com os quais você provavelmente está bastante familiarizado, como print() e println() e outros.

Não é exclusivo do Java que as operações de input e saída demorem muito tempo. “longo.” imprimir ou gravar em um PrintStream leva uma fração de segundo, mas mais de 10 bilhões de instâncias dessa impressão podem resultar em muito!

É por isso que o seu “mover tudo para uma String” é o mais rápido. Sua enorme String é construída, mas você só imprime uma vez . Claro, é uma cópia enorme, mas você gasta tempo na impressão, não na sobrecarga associada à print() ou println() .

Como o DVD Prd mencionou, as cordas são imutáveis. Isso significa que sempre que você atribuir uma nova String a uma antiga, mas reutilizar referências, na verdade destruirá a referência à antiga String e criará uma referência à nova. Assim, você pode tornar toda essa operação ainda mais rápida usando a class StringBuilder, que é mutável. Isso diminuirá a sobrecarga associada à construção da sequência que você imprimirá.

Dê uma olhada na minha substituição System.out.println .

Por padrão, System.out.print () é apenas com buffer de linha e faz muito trabalho relacionado ao manuseio de Unicode. Devido ao seu pequeno tamanho do buffer, System.out.println () não é adequado para lidar com muitas saídas repetitivas em um modo em lote. Cada linha é liberada imediatamente. Se sua saída for baseada principalmente em ASCII, removendo as atividades relacionadas a Unicode, o tempo de execução geral será melhor.

Se você estiver imprimindo na janela do console, não em um arquivo, isso será o assassino.

Cada caractere deve ser pintado e, em cada linha, toda a janela deve ser rolada. Se a janela estiver parcialmente sobreposta com outras janelas, ela também terá que recorte.

Isso vai levar muito mais ciclos do que o seu programa está fazendo.

Normalmente, não é um preço ruim a pagar, já que a saída do console deve ser para o seu prazer de ler 🙂

O problema que você tem é que a exibição na canvas é muito necessária, especialmente se você tiver um ambiente gráfico windows / X-windows (em vez de um terminal de texto puro) Apenas renderizar um dígito em uma fonte é muito mais caro que os cálculos estão fazendo. Quando você envia dados para a canvas mais rápido do que pode exibi-los, eles armazenam os dados e bloqueiam rapidamente. Mesmo a gravação em um arquivo é significativa em comparação com os cálculos, mas é 10x a 100x mais rápida do que a exibida na canvas.

BTW: math.sqrt () é muito caro, e usar um loop é muito mais lento do que usar módulo, ou seja,% para determinar se um número é um múltiplo. BitSet pode ser 8x mais eficiente que boolean []

Se eu despejo a saída em um arquivo, ele é rápido, mas gravar no console é lento e, se eu gravar no console, os dados que foram gravados em um arquivo levam aproximadamente o mesmo tempo.

 Took 289 ms to examine 10,000,000 numbers. Took 149 ms to toString primes up to 10,000,000. Took 306 ms to write to a file primes up to 10,000,000. Took 61,082 ms to write to a System.out primes up to 10,000,000. time cat primes.txt real 1m24.916s user 0m3.619s sys 0m12.058s 

O código

 int upTo = 10*1000*1000; long start = System.nanoTime(); BitSet nonprimes = new BitSet(upTo); for (int t = 2; t * t < upTo; t++) { if (nonprimes.get(t)) continue; for (int i = 2 * t; i <= upTo; i += t) nonprimes.set(i); } PrintWriter report = new PrintWriter("report.txt"); long time = System.nanoTime() - start; report.printf("Took %,d ms to examine %,d numbers.%n", time / 1000 / 1000, upTo); long start2 = System.nanoTime(); for (int i = 2; i < upTo; i++) { if (!nonprimes.get(i)) Integer.toString(i); } long time2 = System.nanoTime() - start2; report.printf("Took %,d ms to toString primes up to %,d.%n", time2 / 1000 / 1000, upTo); long start3 = System.nanoTime(); PrintWriter pw = new PrintWriter(new BufferedOutputStream(new FileOutputStream("primes.txt"), 64*1024)); for (int i = 2; i < upTo; i++) { if (!nonprimes.get(i)) pw.println(i); } pw.close(); long time3 = System.nanoTime() - start3; report.printf("Took %,d ms to write to a file primes up to %,d.%n", time3 / 1000 / 1000, upTo); long start4 = System.nanoTime(); for (int i = 2; i < upTo; i++) { if (!nonprimes.get(i)) System.out.println(i); } long time4 = System.nanoTime() - start4; report.printf("Took %,d ms to write to a System.out primes up to %,d.%n", time4 / 1000 / 1000, upTo); report.close();