mason_24's blog

By mason_24, history, 2 years ago, In English

Experiment

Let suppose you have million of digits that you want to print on the screen one next to each other using Java. There are two approaches that I identified :

  • You can either choose to print each digit at the time (one digit next to each other) like so :
import java.util.Random;
public class Generate_Million_Digits {
	public static void main(String[] args) {
		Random rand = new Random();
		long start = System.currentTimeMillis();
		for (int i = 1; i <= 1000000; i++) {
			System.out.print(rand.nextInt(10));
		}	
		System.out.println();
		long end = System.currentTimeMillis();
		System.out.println("Elapsed Time in milli seconds: " + (end - start));
	}
}

The elapsed Time in milli seconds is : 5248

  • Or you can decide to concatenate all the digit you want to print in a special dat structure like a String or String Builder and then just print the string builder once like so :
import java.util.Random;
public class Generate_Million_Digits {
	public static void main(String[] args) {
		Random rand = new Random();
		long start = System.currentTimeMillis();
		StringBuilder sb = new StringBuilder();
		for (int i = 1; i <= 1000000; i++) {
			sb.append(rand.nextInt(10));
		}	
		System.out.println(sb.toString());
		long end = System.currentTimeMillis();
		System.out.println("Elapsed Time in milli seconds: "+ (end - start));
	}
}

The elapsed Time in milli seconds: 53

Story

The story is that I was recently solving a problem on Codeforces.com using Java 1.8.0_241. As part of my solution I needed to print a series of digits. My first intuition was to use the first approach I described above because it avoided me using additional data structures. It felt into the Time limit Exceeded issue with one of the test case. So, I asked myself how to fix the TLE. I then tried the second approach. Here, the test that was failing with TLE passed with the second approach. Therefore, I was able to solve the problem. The good thing is that in order to solve the problem, I had to introduce a data structure (here a StringBuilder) to avoid making many prints. There could be many more interesting technics out there.

Lesson

In Java, when you have to print many digits one next to each other, please prioritize concatenating all the digits into a data structure (StringBuilder, it is mutable) and print the bulk once, instead of printing one digit at the time one after each other.

Based on the conversation that is currently going on this article, you can also consider using the OutputStream I/O for the printing matter, because OutputStream is proved to give better result in terms of time of printing. Check the comments sections to get more insights.

Happy Coding !!!

  • Vote: I like it
  • +31
  • Vote: I do not like it

| Write comment?
»
2 years ago, # |
  Vote: I like it +20 Vote: I do not like it

Or you can use BufferedWriter

»
2 years ago, # |
  Vote: I like it 0 Vote: I do not like it

You should read about Buffered I/O in Java. StringBuilder is not the best and not the fastest way to output the data. The fastest I personally know is: https://github.com/EgorKulikov/yaal/tree/master/lib/main/net/egork/io

Try to benchmark with that.

  • »
    »
    2 years ago, # ^ |
      Vote: I like it 0 Vote: I do not like it

    Hi @prituladima. Thank you for your observation. I tried a benchmark with the following code snippet and I got he following time :

    The elapsed Time in milli seconds: 73.

    Code snippet :

    import java.util.Random;
    import java.io.PrintWriter;
    import java.io.FileOutputStream;
    import java.io.OutputStream;
    import java.io.BufferedWriter;
    import java.io.OutputStreamWriter;
    
    public class Generate_Million_Digits {
    	public static void main(String[] args) {
    		Random rand = new Random();
    		long start = System.currentTimeMillis();
    		try {
    			OutputStream outputStream = new FileOutputStream("sample1Test.in");
    			PrintWriter writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(outputStream)));
    			for (int i = 1; i <= 1000000; i++) {
    				writer.print(rand.nextInt(10));
    			}	
    			writer.println();
    		} catch(Exception ex) {
    			ex.printStackTrace();
    		}
    		long end = System.currentTimeMillis();
    		System.out.println("The elapsed Time in milli seconds: " + (end - start));
    	}
    }
    

    Buffered I/O seems better for sure, but not much compare to StringBuilder. I know that I/O data strutures are recommended for printing I/O purposes. The case that I tried to address with StringBuilder was specific to the problem and I was just showing an approach to leverage a specific case of TLE.

    • »
      »
      »
      2 years ago, # ^ |
        Vote: I like it 0 Vote: I do not like it

      I did a mistake, sorry. Here the fastest output i'm currently using.

      https://gist.github.com/prituladima/76fb858843468204d8152d388455edf8

      Please compare with that.

      • »
        »
        »
        »
        2 years ago, # ^ |
        Rev. 2   Vote: I like it 0 Vote: I do not like it

        Yes, you are right. This is the result below. I printed the digits one after each other and no need to concatenate them into a temporary data structure. It is absolutely faster with the OutputStream. I would assume that it is because it is backed up by byte type.

        Am I correct? @Prituladima, can you please explain more the context of why your FastPrintStream have such great results ?

        import java.util.Random;
        
        public class Generate_Million_Digits {
        	public static void main(String[] args) {
        		Random rand = new Random();
        		long start = System.currentTimeMillis();
        		FastPrintStream printer = new FastPrintStream("sample_01.in");
        		for (int i = 1; i <= 1000000; i++) {
        			printer.print(rand.nextInt(10));
        		}	
        		printer.println(rand.nextInt(10));
        		System.out.println();
        		long end = System.currentTimeMillis();
        		System.out.println("The elapsed time in milli seconds: " + (end - start));
        	}
        }
        

        The elapsed time in milli seconds: 31

        • »
          »
          »
          »
          »
          2 years ago, # ^ |
            Vote: I like it 0 Vote: I do not like it

          Well, I don't think I may call it "mine". First time I see it on uwi submissions.

          Why it's fast? due to some low level optimisations like:

          • countDigits.

          • all [0-9A-Za-z] is fit in byte type. So we can allocate less memory.

          • FileOutputStream native void writeBytes works very fast since it's written on C++. And it's called only divRoundUp(n / BUF_SIZE) times.

          • »
            »
            »
            »
            »
            »
            2 years ago, # ^ |
              Vote: I like it +1 Vote: I do not like it

            In the PrintWriter, write(int x) is write(String.valueOf(x)). String.valueOf(x) prepares fitted byte array, sets chars from x and then calls new String(). new String takes slightly long time.

            So, it seems faster to set chars to the buffer directly given int x. That's my code.

            For this problem, japanese Java coders competed fast I/O. My I/O is obtained by that.

            https://judge.yosupo.jp/problem/many_aplusb