0

I have defined a class with information:

    public static class Node {
        Node prev;
        public Node() {
        }
        public Node(Node prev) {
            this.prev = prev;
        }
    }

Test case 1 :

   @Test
    public void test1() {
        long start = System.nanoTime();

        for (int i = 0; i < 10_000_000; i++) {
            Node newNode = new Node();
        }

        long timeRun = System.nanoTime() - start;
        System.out.println("nanoTime: " + timeRun);
    }

For the above test case, my machine complete needs nanoseconds 8434100 -> 0.008 second

Test case 2 :

    @Test
    public void test() {
        Node last = null;
        long start = System.nanoTime();

        for (int i = 0; i < 10_000_000; i++) {
            Node newNode = new Node();
            last = newNode ;
        }

        long timeRun = System.nanoTime() - start;
        System.out.println("nanoTime: " + timeRun);
    }

In the above case, I will create a storage variable last to store the last node object.

For the above test case, my machine complete needs nanoseconds 9522600 -> 0.009 second

Test case 3 :

Mutation in this case.

    @Test
    public void test() {
        Node last = null;
        long start = System.nanoTime();

        for (int i = 0; i < 10_000_000; i++) {
            Node newNode = new Node(last);
            last = newNode ;
        }

        long timeRun = System.nanoTime() - start;
        System.out.println("nanoTime: " + timeRun);
    }

Case 3, quite similar to case 2 but I will pass last object to constructor of Node. For the above test case, my machine complete needs nanoseconds 933890100 -> 0.9 second.

Amazingly, it's 100 times slower.

Test case 4 :

   @Test
    public void test() {
        List<Node> list = new ArrayList<>();
        Node last = null;
        long start = System.nanoTime();

        for (int i = 0; i < 10_000_000; i++) {
            Node newNode  = new Node(last);
            list.add(newNode);
            last = newNode ;
        }
        long timeRun = System.nanoTime() - start;
        System.out.println("nanoTime: " + timeRun);
    }

In case 4, I had a list to store newNode to list. For the above test case, my machine complete needs nanoseconds 360559900 -> 0.3 second. It is faster than case 3. Did java re-optimize it?

Questions

Do you know why it's slow? please let me know. I have researched and read a lot but no answer yet

Thành Lê
  • 25
  • 3
  • 5
    Micro benchmarks are misleading. The first two snippets are likely optimized away, the last one likely isn't because object references are kept. – Dave Newton Dec 04 '21 at 13:26
  • 2
    [**How do I write a correct micro-benchmark in Java?**](https://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java) – Andrew Henle Dec 04 '21 at 13:28

1 Answers1

1

Two issues:

  1. As Dave Newton said, microbenchmarks are misleading. More in this question's answers: How do I write a correct micro-benchmark in Java?

  2. But also probably: Because in cases 1 and 2, you don't build up ten million Node objects in memory, you have at most two at any given time. As you're going along, you release previous nodes. But in case 3, you keep all the Node objects you ever create, so you end up with ten million small objects in memory. That requires a lot of overhead in terms of memory management, slowing things down markedly.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • @ThànhLê - *"As far as I know, when I create a new object in java, it lives in the heap."* Not necessarily, after optimization. Objects that are never retained in memory after the method they were created in returns can be created on the stack if the JVM optimizes the method. But yes, usually. *"Was it still in there before it was cleaned up."* I don't know what you mean by that...? – T.J. Crowder Dec 04 '21 at 13:35
  • Can you help me see case 4 or not? It is faster than case 3 – Thành Lê Dec 04 '21 at 13:46
  • 1
    @ThànhLê - See #1 above. There's no point doing these naive microbenchmarks, they just waste your time. Use a proper benchmarking suite or (better) benchmark your actual code, where whatever this is is unlikely to be the bottleneck. – T.J. Crowder Dec 04 '21 at 13:54
  • @ThànhLê - FYI, if you even just put `test` in a loop (say, 100 iterations), case 4 is reliably slower than case 3 on average (at least in the environment where I tried it). Case 3's times are dramatically varied, but the first run is reliably slow. Case 4's times are less varied and longer on average. Which takes us back to #1 above. :-) (Because even my version with a loop is still too naive to trust.) – T.J. Crowder Dec 04 '21 at 14:04