为什么打印字母"B"比打印符号"#"慢得多?

2985
我生成了两个1000 x 1000的矩阵:
第一个矩阵:O和#。 第二个矩阵:O和B。
使用以下代码,第一个矩阵完成所需时间为8.52秒:
Random r = new Random();
for (int i = 0; i < 1000; i++) {
    for (int j = 0; j < 1000; j++) {
        if (r.nextInt(4) == 0) {
            System.out.print("O");
        } else {
            System.out.print("#");
        }
    }
            
   System.out.println("");
 }

使用这段代码,第二个矩阵完成所需时间为259.152秒:

Random r = new Random();
for (int i = 0; i < 1000; i++) {
    for (int j = 0; j < 1000; j++) {
        if (r.nextInt(4) == 0) {
            System.out.print("O");
        } else {
            System.out.print("B"); // only line changed
        }
    }
                
    System.out.println("");
}

什么是导致运行时间戏剧性差异的原因?

根据评论中的建议,只打印System.out.print("#");花费7.8871秒,而System.out.print("B");则给出仍在打印...

正如其他人指出的那样,他们平时使用这个方法,我尝试了例如Ideone.com,发现两段代码执行速度相同。

测试条件:

  • 我从Netbeans 7.2运行了这个测试,并将输出显示在其控制台上
  • 我使用System.nanoTime()进行测量

66
尝试将 rand.nextInt(4) == 0 改为 i < 250,以消除随机生成器的影响。这样做可能会消耗熵值,从而减慢随机生成速度。 - fejese
3
在我的电脑上,这两者似乎运行时间相同,约为4秒。 - Sotirios Delimanolis
172
如果你在暗示打印B比打印#需要更多时间的话,为什么不尝试打印所有的B和所有的#,而不是依赖于随机变量r? - Kakarot
21
根据被采纳的答案,显然您没有尝试将输出重定向到文件或/dev/null。 - Barmar
29
@fejese,Random() 不是密码学随机数生成器,因此不会消耗熵池中的熵。 - Divide
显示剩余4条评论
3个回答

4249
纯属猜测,你正在使用一个终端尝试进行换行而不是字符换行,并将B视为单词字符但将#视为非单词字符。因此,当它到达一行的末尾并搜索换行的位置时,它几乎立即看到了#并愉快地在那里换行;而对于B,它必须继续搜索更长时间,并且可能有更多要换行的文本(这可能在某些终端上很昂贵,例如输出退格符,然后输出空格以覆盖被换行的字母)。但这只是纯属猜测。

356
精彩的推断。但是我们应该从这个教训中进行概括,并且始终使用输出量来衡量性能,无论是将其消除、定向到/dev/null(在Windows上为NUL),还是至少定向到一个文件中。在任何类型的控制台上显示通常是非常昂贵的I/O操作,并且总是会扭曲时间——即使不像这样令人困惑和混乱。 - Bob Kerns
20
@BobKerns:当然,这仍然会产生一个合适的问题。问题在于打印B需要更长的时间,而不打印它们并不是解决问题的方法,只是帮助调试问题的技巧。 - Chris
44
System.out.println 不会自动换行,它所输出的位置进行了自动换行(同时也是阻塞的,所以 System.out.println 必须等待)。 - T.J. Crowder
47
@Chris -- 实际上,我会认为不打印它们才是解决算法准确计时问题的方法。每次你打印到控制台(任何类型的控制台),你都会调用与你测试性能无关的各种外部处理。这是你测量过程中的一个错误,纯粹而简单。另一方面,如果你将问题视为理解差异,那么不打印就是一种调试技巧。归根结底,你要解决哪个问题? - Bob Kerns
15
感谢BobKerns在这个讨论串中注入了一些理智!很多人似乎不理解这一点。你需要小心,确保你只测量你试图测量的东西。 - UpAndAdam
显示剩余3条评论

257

我对Eclipse和Netbeans 8.0.2进行了测试,两者都使用Java版本1.8; 我使用System.nanoTime()进行测量。

Eclipse:

在这两种情况下,我得到了相同的时间 - 大约1.564秒

Netbeans:

  • 使用“#”: 1.536秒
  • 使用“B”: 44.164秒

因此,看起来Netbeans在向控制台打印输出方面的性能较差。

经过更多的研究,我意识到问题是Netbeans的最大缓冲区的line-wrapping(它不仅限于System.out.println命令),通过以下代码证明:

    for (int i = 0; i < 1000; i++) {
        long t1 = System.nanoTime();
        System.out.print("BBB......BBB"); // <- contains 1000 "B"s
        long t2 = System.nanoTime();
        System.out.println(t2 - t1);
        System.out.println("");
    }

每次迭代的时间结果都小于1毫秒,除了每五次迭代,时间结果约为225毫秒。类似于(以纳秒为单位):
BBB...31744
BBB...31744
BBB...31744
BBB...31744
BBB...226365807
BBB...31744
BBB...31744
BBB...31744
BBB...31744
BBB...226365807
.
.
.

等等。

摘要:

  1. Eclipse与"B"完美配合。
  2. Netbeans存在换行问题,可以解决(因为这个问题在Eclipse中不会出现)(无需在B后面添加空格("B "))。

42
你能详细解释一下你的研究策略,以及最终是什么让你发现换行是罪魁祸首吗?(我很好奇你的侦探技巧!) - silph
1
Netbeans中的自动换行功能可以被禁用。 - Thorbjørn Ravn Andersen
2
"我使用了System.nanoTime()"...总的来说,没有一个专门的基准测试工具,进行微基准测试是毫无意义的。" - scottb

21
是的,罪魁祸首肯定是自动换行。当我测试了你的两个程序时,NetBeans IDE 8.2给出了以下结果。
1. 第一个矩阵:O和# = 6.03秒 2. 第二个矩阵:O和B = 50.97秒
仔细看了你的代码后发现,在第一个循环的末尾你使用了一个换行符。但在第二个循环中却没有使用任何换行符。因此,在第二个循环中你将打印一个包含1000个字符的单词。这导致了自动换行的问题。如果我们在B之后使用一个非单词字符" ",编译程序只需要5.35秒。而如果我们在第二个循环中传递100个值或者50个值后使用一个换行符,分别只需要8.56秒和7.05秒。
Random r = new Random();
for (int i = 0; i < 1000; i++) {
    for (int j = 0; j < 1000; j++) {
        if (r.nextInt(4) == 0) {
            System.out.print("O");
        } else {
            System.out.print("B");
        }

        if (j % 100 == 0) { // Adding a line break in second loop      
            System.out.println();
        }                    
    }

    System.out.println("");                
}

另一个建议是更改NetBeans IDE的设置。首先,进入NetBeans的“工具”菜单,点击“选项”。然后,点击“编辑器”,进入“格式化”选项卡。接下来,在“换行”选项中选择“任何位置”。这样编译程序所需的时间将减少近6.24%。

NetBeans Editor Settings


4
“编译程序所需的时间将减少近6.24%。”你说的应该是执行时间,而不是编译时间。 - eis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接