Hasan's Blog

Comparing procedural and functional java

code.jpg

I was watching a Youtube video about how java is good for everything when in the video a code snippet was reviewd. The code was a simple algorithm called “FizzBuzz” but the implementation in java used the beautiful functional stream API:

public static String fizzBuzz(int[] arr) {
    var preds = new HashMap<Predicate<Integer>, String>();
    preds.put(x -> x % 3 == 0, "Fizz");
    preds.put(x -> x % 5 == 0, "Buzz");
    return Arrays.stream(arr)
            .mapToObj(x -> preds.entrySet().stream()
                    .filter(entry -> entry.getKey().test(x))
                    .map(Map.Entry::getValue)
                    .reduce(String::concat)
                    .orElse(String.valueOf(x)))
            .collect(Collectors.joining("\n"));
}

As you can see the code is very concise and if you are familiar with the functional programming concepts you can easily understand the code. I wanted to implement the same code using procedural programming and I ended up with this:

public static String proceduralFizzBuzz(int[] arr) {
    List<String> result = new ArrayList<>();
    for (var x : arr) {
        StringBuilder sb = new StringBuilder();
        if (x % 3 == 0) {
            sb.append("Fizz");
        }
        if (x % 5 == 0) {
            sb.append("Buzz");
        }
        if (sb.isEmpty()) {
            sb.append(x);
        }
        result.add(sb.toString());
    }
    return String.join("\n", result);
}

This code is a bit more verbose and is littered with if statements but it is It is more readable. The problem with this code is that it goes against some “good practices”, for example, we have multiple if statements and the function is rather large. But the story is not over, I decided to benchmark both functions so I quickly did some research and found that the standard way to benchmark java code is to use the JMH library, here is my benchmark code:

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
@Fork(value = 1, warmups = 1)
@Warmup(iterations = 1, time = 1)
@Measurement(iterations = 3, time = 1)
public class FizzBuzzBenchmark {
    @Param({"1000", "10000", "100000"})
    private int length;

    int[] data;

    @Setup
    public void setup() {
        data = new int[length];
        for (int i = 0; i < length; i++) {
            data[i] = i;
        }
    }

    @Benchmark
    public void functionalFizzBuzz() {
        FizzBuzz.fizzBuzz(data);
    }

    @Benchmark
    public void proceduralFizzBuzz() {
        FizzBuzz.proceduralFizzBuzz(data);
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(FizzBuzzBenchmark.class.getSimpleName())
                .build();

        new Runner(opt).run();
    }
}

The result

The results were these:

Benchmark                                      (length)  Mode  Cnt   Score   Error  Units
fizzBuzz.FizzBuzzBenchmark.functionalFizzBuzz      1000  avgt   10   0.332 ± 0.023  ms/op
fizzBuzz.FizzBuzzBenchmark.functionalFizzBuzz     10000  avgt   10   2.786 ± 0.147  ms/op
fizzBuzz.FizzBuzzBenchmark.functionalFizzBuzz    100000  avgt   10  47.299 ± 5.843  ms/op

fizzBuzz.FizzBuzzBenchmark.proceduralFizzBuzz      1000  avgt   10   0.074 ± 0.012  ms/op
fizzBuzz.FizzBuzzBenchmark.proceduralFizzBuzz     10000  avgt   10   0.857 ± 0.097  ms/op
fizzBuzz.FizzBuzzBenchmark.proceduralFizzBuzz    100000  avgt   10   7.388 ± 1.315  ms/op

As you can see, the cleaner, smaller functional code is much slower than the procedural code (almost 5 times slower)!! This was not surprising to me and it reminded me of a demo made on youtube: “Clean” Code, Horrible Performance The reason why clean code is slower than procedural is because clean code prioritizes readability and maintainability. Clean code is meant for humans, CPUs don’t care about that, procedural code is faster because it is arranged in a way to suits the CPU.

This project is maintained by hasan-aga