关于java:为什么System.out.println这么慢?

Why is System.out.println so slow?

这是所有编程语言的共同点吗?多个打印后面跟着一个println似乎更快,但将所有内容移动到一个字符串中,并只打印看起来最快的内容。为什么?

编辑:例如,Java可以在不到一秒钟的时间内找到所有的质数多达100万个,但是打印出来的每一个都可以在自己的打印纸上花上几分钟!最多可打印100亿罐小时!

前任:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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 );
            }
        }
    }
}


println不是慢的,它的底层PrintStream与主机相连,由主机操作系统提供。

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

1
2
cat largeTextFile.txt
cat largeTextFile.txt > temp.txt

读写是相似的,与文件的大小成比例(O(N)),唯一的区别是,目的地不同(控制台与文件相比)。这与System.out基本相同。

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

  • 必须将字节发送到控制台应用程序(应该非常快)
  • 每个字符都必须使用(通常)真正的字体来呈现(这非常慢,关闭消除混叠可以提高性能,btw)
  • 可能需要滚动显示区域才能将新行附加到可见区域(最佳情况:位块传输操作,最坏情况:重新呈现整个文本区域)

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

    输入和输出操作需要很长时间,Java并不是唯一的。"打印或写入一个PrintStream只需要一秒钟的时间,但是超过100亿次的打印可以加起来相当多!"

    这就是为什么你的"把所有东西都移到一根绳子上"是最快的。你的大串是建立起来的,但你只打印一次。当然,这是一个巨大的打印,但你花时间实际打印,而不是与print()println()相关的开销。

    正如dvd prd所提到的,字符串是不可变的。这意味着,每当您将一个新的字符串分配给一个旧的字符串,但重用引用时,您实际上会销毁对旧字符串的引用,并创建对新字符串的引用。所以,通过使用StringBuilder类,可以使整个操作更加快速,这是可变的。这将减少与构建最终将要打印的字符串相关联的开销。


    我认为这是因为缓冲。文章引述:

    Another aspect of buffering concerns
    text output to a terminal window. By
    default, System.out (a PrintStream) is
    line buffered, meaning that the output
    buffer is flushed when a newline
    character is encountered. This is
    important for interactivity, where
    you'd like to have an input prompt
    displayed before actually entering any
    input.

    解释维基百科缓冲区的引用:

    In computer science, a buffer is a
    region of memory used to temporarily
    hold data while it is being moved from
    one place to another. Typically, the
    data is stored in a buffer as it is
    retrieved from an input device (such
    as a Mouse) or just before it is sent
    to an output device (such as Speakers)

    1
    public void println()

    Terminate the current line by writing
    the line separator string. The line
    separator string is defined by the
    system property line.separator, and is
    not necessarily a single newline
    character ('
    ').

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


    看看我的system.out.println替换。

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


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

    每一个角色都必须被描绘,每一行的整个窗口都必须滚动。如果窗口部分覆盖了其他窗口,则还必须进行剪裁。

    这需要比你的程序所做的更多的周期。

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


    你所面临的问题是,在屏幕上显示是非常特殊的,特别是如果你有一个图形化的Windows/X-Windows环境(而不是纯文本终端),仅仅以一种字体呈现一个数字要比你正在做的计算昂贵得多。当您向屏幕发送数据的速度超过它的显示速度时,它会缓冲数据并快速阻止。与计算相比,即使是写入文件也是很重要的,但是它比在屏幕上显示快10倍到100倍。

    btw:math.sqrt()非常昂贵,使用循环比使用模数(即%来确定数字是否是倍数)慢得多。位集的效率比布尔值高8倍[]

    如果我将输出转储到一个文件,它很快,但是写入控制台的速度很慢;如果我将写入文件的数据写入控制台,则所需的时间大约相同。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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

    代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    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();


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

    当软件需要访问某些硬件资源(例如屏幕)时,它需要询问操作系统(或管理程序)是否可以访问硬件。这要花很多钱:

    以下是关于SysChanes的有趣的博客,最后一个是专门用于SysCurrand和Java的

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