Wednesday, 3 August 2022

Jmh: be cautious with dead code

What is dead code?

Dead code is the one that never executed in run time.

 

DeadCodeEx.java

package com.sample.app;

public class DeadCodeEx {
    public static void main(String[] args) {
        final boolean b = true;
        
        if(b) {
            System.out.println("b is set to true");
        }else {
            System.out.println("b is set to false");
        }
    }
}

 

Notice above code snippet, the code is else block will never get executed, this is an example of dead code.

 

Now  a days compilers are smart enough to identify and delete the dead code. If the eliminated part was our benchmarked code, then we will get wrong benchmarking results.

 

Let me explain it with an example.

 

DeadcodeEliminationDemo.java

 

package com.sample.app;

import java.util.concurrent.TimeUnit;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class DeadcodeEliminationDemo {

    private double fib(int n) {
        if (n <= 1) {
            return n;
        }
        
        int fib = 1;
        int prevFib = 1;

        for (int i = 2; i < n; i++) {
            int temp = fib;
            fib += prevFib;
            prevFib = temp;
        }
        return fib;

    }

    @Benchmark
    public void baseline() {
        // do nothing
    }

    @Benchmark
    public void measureWrong() {
        fib(20);
    }

    @Benchmark
    public double measureRight() {
        // Here result is returned to the calling function.
        return fib(20);
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder().include(DeadcodeEliminationDemo.class.getSimpleName()).forks(1)
                .measurementIterations(2).warmupIterations(2).build();

        new Runner(opt).run();
    }

}

When I ran the application, I got below benchmark results.

Benchmark                             Mode  Cnt  Score   Error  Units
DeadcodeEliminationDemo.baseline      avgt    2  0.398          ns/op
DeadcodeEliminationDemo.measureRight  avgt    2  2.398          ns/op
DeadcodeEliminationDemo.measureWrong  avgt    2  0.570          ns/op

Even though both ‘measureWrong’ and ‘measureRight’ methods calling fib(20) internally, measureWrong method took 0.57 nano seconds per operation, where as measureRight  took 2.398 nano seconds per operation.

 


 

 

Why measureWrong taking very less time as compared to measureRight?

As you see the definition of ‘measureWrong’ method, it is not using the result of fib(20) anywhere, so compiler treat this as dead code and eliminate it.


public void measureWrong() {
        fib(20);
}

Be cautious, while measuring the benchmark with these kind of scenarios.

There are two approaches to handle this scenario.

 

Approach 1: Just return the created data.

public void measureWrong() {
        return fib(20);
}

 

Approach 2: Let the Blackhole consume the data.

@Benchmark
public void measureWrong(Blackhole blackhole) {
	blackhole.consume(fib(20));
}

 

 

Previous                                                 Next                                                 Home

No comments:

Post a Comment