【问题标题】:Why is System.out.println so slow?为什么 System.out.println 这么慢?
【发布时间】:2010-12-14 09:37:48
【问题描述】:

这是所有编程语言共有的吗?在 println 之后进行多次打印似乎更快,但是将所有内容都移动到一个字符串中并且只打印似乎最快的。为什么?

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

前:

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 的速度非常快……
  • @DasWood“好像”?请提供一些基准(代码+时间)。
  • 它在 *nix 上往往很快,在 Windows 上很慢。换句话说,这些操作系统的控制台实现才是这里的因素。
  • 基准我粘贴的示例,打印注释掉了。没有它。巨大的差异,它只通过数字线一次,而不是像实际的筛子那样 9999998 次。
  • 不要重复println,而是把它全部放到一个字符串中,然后再println。它更快。

标签: java performance


【解决方案1】:

println 并不慢,它是与控制台相连的底层PrintStream,由宿主操作系统提供。

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

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

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


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

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

【讨论】:

  • 好吧,那为什么底层的io操作这么慢呢?
  • 理论上是否可以在控制台输出的情况下进行优化?例如。要缓冲输出以显示“一页”,因此只需执行一次滚动?我知道输出缓冲的责任实际上在于控制台实现,只是好奇。
【解决方案2】:

System.out 是一个静态的PrintStream 类。 PrintStream 包含您可能非常熟悉的方法,例如 print()println() 等。

输入和输出操作需要很长时间并不是 Java 独有的。 “长。”打印或写入PrintStream 只需要几分之一秒,但超过 100 亿次打印的实例加起来相当多!

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

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

【讨论】:

  • 在您的答案和 Alfred 的答案之间,给出的答案非常完整。谢谢。
  • 创建字符串不是问题。如果您只是通过不打印它们来创建字符串,它将以接近相同的速度运行。
  • 查看我的回复,其中显示 toString 或写入文件所需的时间非常短,写入控制台的时间要长许多数量级(即使只是显示带有值的文件)
  • 我提到这将使它“更快”,正如 OP 所提到的,它将用于大约 100 亿个字符串连接。我绝不会说花费最多时间的是 String 的创建。
【解决方案3】:

我相信这是因为buffering。引用文章:

缓冲问题的另一个方面 文本输出到终端窗口。经过 默认情况下,System.out(一个 PrintStream)是 行缓冲,意味着输出 换行时刷新缓冲区 遇到字符。这是 对交互性很重要,其中 你想要一个输入提示 在实际输入任何内容之前显示 输入。

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

在计算机科学中,缓冲区是 用于临时的内存区域 在移动数据时保留数据 一个地方到另一个地方。通常情况下, 数据按原样存储在缓冲区中 从输入设备(例如 作为鼠标)或在发送之前 到输出设备(例如扬声器)

public void println()

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

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

【讨论】:

  • 缓冲到文件或控制台是相同的(它们都是下面的文件描述符),但是文件速度快 300 倍,这意味着缓冲成本是相当小的一部分。
  • @Peter 我认为你是对的。与屏幕相比,光盘的吞吐量要高得多?
【解决方案4】:

看看我的System.out.println replacement

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

【讨论】:

    【解决方案5】:

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

    必须绘制每个字符,并且必须在每一行上滚动整个窗口。 如果窗口部分与其他窗口重叠,它也必须进行剪切。

    这将比您的程序执行的周期多得多。

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

    【讨论】:

      【解决方案6】:

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

      顺便说一句: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();
      

      【讨论】:

        【解决方案7】:

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

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

        这里有一些关于系统调用的有趣博客,最后一篇是专门讨论系统调用和 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/

        【讨论】:

          猜你喜欢
          • 2010-10-31
          • 2021-09-03
          • 2016-09-28
          • 2020-02-08
          • 2012-07-17
          • 2011-11-07
          • 2015-08-24
          • 2013-08-06
          • 2014-07-16
          相关资源
          最近更新 更多