-1

A few days ago I realized that I had never done any experimentation with measuring the time it takes for a program to run. I decided for fun just to test out some random lines of code for the heck of it using the System.currentTimeMillis() method. I decided to try out something simple, such as merely continuously reassigning a variable. So, I ran the following code:

public class Timer{
    public static void main(String[] args) {
        String s = null;

        final long startTime = System.currentTimeMillis(); // Start the timer

        for (int i = 1; i <= 999999999; i++) { 
            s = i + "Hello";  // This takes around 36 seconds!
        }

        System.out.println(s);

        final long endTime = System.currentTimeMillis(); // End the timer

        System.out.println("Total execution time: " + (endTime - startTime)); // Report total time elapsed
    }
}

I was amazed to see that just continuously reassigning a String variable would take so long. I know the number up to which I'm iterating is quite huge in and of itself, but when I ran other code that still involved the for-loop up to that huge number, the time it took for the program to run was significantly lower. For example:

import java.util.ArrayList;

public class TimerArrayList {
    public static void main(String[] args) {

        ArrayList<Integer> list = new ArrayList<Integer>();

        final long startTime = System.currentTimeMillis(); // Start the timer

        // Adding elements to an ArrayList is generally quick (around 4 seconds)
        for (int i = 1; i <= 999999999; i++) {
            list.add(i);
            if (list.size() == 50000)
                list.clear(); // To prevent OutOfMemoryError
        }
        final long endTime = System.currentTimeMillis(); // End the timer
        System.out.println("Total execution time: " + (endTime - startTime)); // Report total time elapsed
    }
}

Adding elements to the ArrayList took only about 4 seconds. Even faster yet was merely counting up. For instance, this code took on average 1.5 milliseconds to run:

public class TimerCounting{
    public static void main(String[] args) {
        final long startTime = System.currentTimeMillis(); // Start the timer

        // Just counting up is super quick (around 1 millisecond)
        int counter = 0;
        for (int i = 1; i <= 999999999; i++) {
            counter = i;
        }

        final long endTime = System.currentTimeMillis(); // End the timer

        System.out.println("Total execution time: " + (endTime - startTime)); // Report total time elapsed
    }
}

So, my question is: why does it take so long just to reassign a String variable?

  • 2
    Please read [Benchmarking inside Java code](https://stackoverflow.com/questions/8423789/benchmarking-inside-java-code) before you jump to any conclusions here. – Tim Biegeleisen Jun 09 '20 at 02:41
  • It's not just the concatentation, it is the generation of a billion - 1 garbage strings that have to be collected that is taking the time here. – user207421 Jun 09 '20 at 06:54
  • In addition to what everyone else has said, this code converts an integer to a string in every iteration. That's not free. Neither are the many young generation GC runs to clean up all the garbage that @DawoodibnKareem mentions. – Gene Jun 10 '20 at 03:40
  • Oh, I misread the code. Yeah, the concatenation is not the worst bit - it was only the worst bit in the code that I imagined. – Dawood ibn Kareem Jun 10 '20 at 03:48

2 Answers2

-1

Reassigning of strings is a costly operation and every time we reassign a string a couple of operations actually happen in the background due to the immutable nature of strings.

This is why concatenating Strings many, many times is a bad idea. Each time you concatenate, your application takes the hit of implicitly making a new String and for this exact purpose we have StringBuffer/StringBuilder

Sambit Nag
  • 38
  • 5
-2

Case 1:

for (int i = 1; i <= 999999999; i++) {
            counter = i;
}

You are not allocating any new memory to variable counter. Hence it just take 1 cpu cycle to run counter = i in each iteration.

Case 2:

for (int i = 1; i <= 999999999; i++) {
    list.add(i);
    if (list.size() == 50000)
       list.clear(); // To prevent OutOfMemoryError
}

It generally takes more than 1 cpu cycles to add an element in list. It first searches for a free space in memory, allocates the space for new element in list and then does some piece of code to actually add the new element in to end of linked list.

Actually, overall Time taken should be more than 4 seconds . but as you are doing this in batch (allowing old memory to be garbage collected after 50000th element), this will less time when finding new space for new element because of less page swappings.

Case 3:


 for (int i = 1; i <= 999999999; i++) { 
   s = i + "Hello";  // This takes around 36 seconds!
 }

This allocates memory from heap as new string is created each time with different value of i.

Probably in your case, It constanly adds to heap space when creating new string literal until the next garbage collector cycle runs. (Unlike the case of linked list where the memory was cleared on encoutering 50000th element.)

Hope this clears to some extent !

Sahil Garg
  • 167
  • 6
  • Case 3 does not allocate memory from the String pool. The String pool is already allocated, and using its contents does not constitute allocation. The line of code in question creates a *new* string, *not* in the String pool, by (i) conversion of `i` to a String, (ii) concatenation with the string literal from the string pool. It also generates tons of garbage that has to be wheeled out and collected on Thursday morning. – user207421 Jun 09 '20 at 06:53
  • @MarquisofLorne : thanks for explanation. I see string literals are used when compiler can knows the string value at compile time (no _variables_ used ). edited my answer. – Sahil Garg Jun 09 '20 at 09:04