0

In the last few days, doing some competitive programming exercises, I found some solutions to some problems where instead of printing out text in the console in a for loop, they constructed a StringBuilder by appending the texts after each other. First I was confused but I gave it a try and made some benchmarks.

First Test: this is the classic way to print something on the screen:

class TestClass {

    public static void main(String args[]) throws Exception {
        double startTime = System.currentTimeMillis();

        //code
        for (int i = 0; i < 1000000; i++) {
            System.out.print("test");
        }
        //code
        double endTime = System.currentTimeMillis();
        String result = String.format("for loop: %.5f", (endTime-startTime) / 1000);
        System.out.print("\n"+result);
    }

}

Second test: and this is the enhanced version with StringBuilder:

class TestClass {

    public static void main(String args[]) throws Exception {
        double startTime = System.currentTimeMillis();

        //code
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 1000000; i++) {
            sb.append("test");
        }
        System.out.print(sb);
        //code
        double endTime = System.currentTimeMillis();
        String result = String.format("for loop: %.5f", (endTime - startTime) / 1000);
        System.out.print("\n" + result);
    }

}

After running the tests several times(running them with Intel i5 6500 quad core 3.2 GHZ CPU, Win 10), the results are the following:

first test(for loop): 4,25s  
second test(StringBuilder): 0.23s

The results are not unexpected because out.print is synchronized so every time we call it, it will add some overhead. But being almost 20 times slower is somewhat strange.

My question is: is there a way we can improve this further?

xingbin
  • 27,410
  • 9
  • 53
  • 103
Csa77
  • 649
  • 13
  • 19
  • Read this re: benchmarking - https://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java – achAmháin Aug 23 '18 at 16:11
  • OK, thank you, I will take a look at it ASAP. – Csa77 Aug 23 '18 at 16:13
  • 1
    console output is very slow because the text needs to be graphically rendered which requires a full redraw of the console screen. It does not use the GPU because that would add more dependencies so you have to wait for the CPU to redraw the screen. This means you will wait for the CPU to iterate over every pixel to correctly update the screen after each output. This is probably hundreds of thousands to millions of CPU cycles per call to the output function. – Chris Rollins Aug 23 '18 at 16:28

1 Answers1

1

synchronized keyword should not be blamed in this case.

The first test case is slow, because System.out.print will bring frequent minimal I/O.

You can use BufferedWriter(it's write method is synchronized too) to reduce unecessary I/O:

BufferedWriter log = new BufferedWriter(new OutputStreamWriter(System.out));

//code
for (int i = 0; i < 1000000; i++) {
    log.write("test");
}

for loop: 0.21200

And, StringBuilder use byte[] for character storage internally. This byte array will grow when you keep appending contents, by copying old value into new byte[].

Give it a big enough capacity might bring some efficiency:

StringBuilder sb = new StringBuilder(1000000 * 10);
xingbin
  • 27,410
  • 9
  • 53
  • 103