为什么System.out.println这么慢?

时间:2010-12-14 09:37:48

标签: java performance

这是所有编程语言的共同点吗?进行多次打印然后使用println似乎更快但将所有内容移动到字符串并且只是打印似乎最快。为什么呢?

编辑:例如,Java可以在不到一秒的时间内找到所有素数高达100万 - 但是打印然后在他们自己的println上全部打印可能需要几分钟!打印时间高达100亿小时!

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 ){
                    testingWith = testingWith + testing;
                    if ( testingWith >= upTo){
                    }
                    else{
                        primes[testingWith] = false;
                    }

                }
            }
        }
        for( int b = 2; b < upTo; b++){
            if( primes[b] ){
                System.out.println( b );
            }
        }
    }
}

7 个答案:

答案 0 :(得分:24)

println并不慢,它是由托管操作系统提供的与控制台连接的基础PrintStream

您可以自己检查:将大型文本文件转储到控制台,并将相同的文本文件传输到另一个文件中:

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

读取和写入类似于文件大小(O(n)),唯一的区别是目标不同(控制台与文件比较)。这与System.out基本相同。


底层操作系统操作(在控制台窗口上显示字符)很慢,因为

  1. 必须将字节发送到控制台应用程序(应该非常快)
  2. 每个字符必须使用(通常)真正的字体呈现(这很慢,关闭抗锯齿可以提高性能,顺便说一句)
  3. 可能需要滚动显示的区域,以便在可见区域附加一个新行(最好的情况:位块传输操作,最坏的情况:重新渲染整个文本区域)

答案 1 :(得分:5)

System.out是一个静态PrintStream类。 PrintStream除其他外,还有您可能非常熟悉的方法,例如print()println()等。

输入和输出操作需要很长时间才是Java的独特之处。 “长。”打印或写入PrintStream只需要几分之一秒,但是这种打印的超过100亿个实例可以累积很多!

这就是为什么你的“将所有内容移动到字符串”是最快的。您构建了巨大的String,但只打印一次。当然,这是一个巨大的打印,但你花时间实际打印,而不是与print()println()相关的开销。

正如Dvd Prd所提到的,字符串是不可改变的。这意味着无论何时将新String分配给旧String但重用引用,实际上都会销毁对旧String的引用并创建对新String的引用。因此,通过使用可变的StringBuilder类,您可以使整个操作更快。这将减少与构建您最终打印的字符串相关的开销。

答案 2 :(得分:4)

我认为这是因为buffering。引文来自文章:

  

缓冲问题的另一个方面   文本输出到终端窗口。通过   默认情况下,System.out(一个PrintStream)是   line buffered,意思是输出   换行时刷新缓冲区   遇到了字符。这是   对交互性很重要,在哪里   你想要一个输入提示   在实际输入之前显示   输入

解释来自维基百科的缓冲区:

  

在计算机科学中,缓冲是一种   暂时使用的记忆区域   在移动数据时保持数据   一个地方到另一个。通常,   数据按原样存储在缓冲区中   从输入设备中检索(例如   作为鼠标)或在发送之前   到输出设备(如扬声器)

public void println()
  

通过写入终止当前行   行分隔符字符串。这条线   分隔符字符串由。定义   系统属性line.separator,是   不一定是一个换行符   字符('\ n')。

当你执行println时,缓冲区被刷新,这意味着必须分配新内存等,这会使打印变慢。您指定的其他方法需要较少的缓冲区刷新,因此更快。

答案 3 :(得分:3)

查看我的System.out.println replacement

默认情况下,System.out.print()只是行缓冲,并且与Unicode处理有很多相关的工作。由于其缓冲区大小较小,System.out.println()不适合在批处理模式下处理许多重复输出。每条线都立即冲洗。如果您的输出主要是基于ASCII的,那么通过删除与Unicode相关的活动,总体执行时间会更好。

答案 4 :(得分:1)

你遇到的问题是显示在屏幕上是非常昂贵的,特别是如果你有一个图形窗口/ X-windows环境(而不是纯文本终端)只是用字体渲染一个数字远比你正在做的计算。当您将数据发送到屏幕的速度超过显示数据时,它会缓冲数据并快速阻止。即使写入文件与计算相比也很重要,但它比屏幕上显示的速度快10倍到100倍。

BTW:math.sqrt()非常昂贵,并且使用循环比使用模数(即%)来确定数字是否是倍数要慢得多。 BitSet的效率比boolean []

高8倍

如果我将输出转储到文件中,它很快,但写入控制台很慢,如果我写入控制台,写入文件的数据需要大约相同的时间。

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

代码

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();

答案 5 :(得分:1)

如果您要打印到控制台窗口而不是文件,那将是杀手。

每个角色都必须被绘制,并且在每一行上都必须滚动整个窗口。 如果窗口部分与其他窗口重叠,则还必须进行裁剪。

这比你的计划所做的周期要多得多。

通常这不是一个糟糕的代价,因为控制台输出应该是为了您的阅读乐趣:)

答案 6 :(得分:0)

这里的大多数答案都是正确的,但它们没有涵盖最重要的一点:系统调用。这是导致更多开销的操作。

当您的软件需要访问某些硬件资源(例如,您的屏幕)时,它需要询问OS(或虚拟机管理程序)是否可以访问硬件。这花费很多:

这是有关syscall的有趣博客,最后一个致力于syscall和Java

http://arkanis.de/weblog/2017-01-05-measurements-of-system-call-performance-and-overhead http://www.brendangregg.com/blog/2014-05-11/strace-wow-much-syscall.html https://blog.packagecloud.io/eng/2017/03/14/using-strace-to-understand-java-performance-improvement/