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 ); } } } } |
您可以自己检查:比较将一个大文本文件转储到控制台,并将相同的文本文件管道传输到另一个文件:
1 2 | cat largeTextFile.txt cat largeTextFile.txt > temp.txt |
读写是相似的,与文件的大小成比例(O(N)),唯一的区别是,目的地不同(控制台与文件相比)。这与
底层操作系统操作(在控制台窗口上显示字符)很慢,因为
输入和输出操作需要很长时间,Java并不是唯一的。"打印或写入一个
这就是为什么你的"把所有东西都移到一根绳子上"是最快的。你的大串是建立起来的,但你只打印一次。当然,这是一个巨大的打印,但你花时间实际打印,而不是与
正如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 ('
').
号
所以在执行
看看我的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/