Java Lambda Performance — With a little bit Haskell as well…
Hi there folks!
Today I´m going to test how the Java lambda performs over with their implementation of lambda functions.
Lambda is the Greek letter λ, it is used in a variety of fields of knowledge but in our case it has to do with Lambda Calculus, that is a form of mathematics made just for computational problems and it’s the base of all Functional Programming. The principal difference between a functional and a non functional language is that functions are First-class Citizen, meaning that they can be type, object, entity or values. As a consequence the functional languages can have Higher-order Functions, those are functions that can receive a function as a parameter and/or return one as a result.
Starting with Java 8 the language offered some of the functional programming style features with lambdas and Functional Interface. For the ones that worked with Anonymous Classes will find it pretty similar, in reality they are a new form to implement those.
How it works: There is a new annotation, @FunctionalInterface, it indicates that the annotated Interface has only one abstract method that can be expressed as a lambda and the JVM will infer the Interface for you. There is no necessity to use the annotation, but you still need to implement only one abstract method.
Although the language assumes some of the functional paradigm it does not include others, like imutable state for example.
As I said before, the JVM now has the ability to infer the type of a lambda expression. With that dynamic behavior comes a cost for sure, but how much of it?
I designed a simple experiment to see how much it costs the JVM to use lambda over pure Java. I’m not trying to say that we never should use lambda or we should throw away pure Java code to be more functional. The objective is to create tools to test and have a more accurate decision of how to implemente those, even more because having the functional style can bring our code to another level of sophistication and a totally new set of solutions with High-order Functions that we would have a lot of work to make before or even would be impossible to do.
The algorithm is pretty simple, using JMH to make a benchmark of how the algorithm performs in different implementations.
1 — We’ll make a iteration over a number of times, from 10 to 1,000,000, increasing in power of 10.
2 — For each loop we’ll map the value to add 10
3 — Then we’ll be reduce and sum all the values
You can find the code here.
The results are the following:
When we just pass the lambda in over a Stream pipeline we see that it takes more than nanoseconds to finish the task with 10 iterations, 1 nanosecond is 10E-9, and it loses performance really fast as grows the number of iterations.
Let’s see the others methods:
As you can see there is no gain in the changes we made, they all have the same result.
Now let´s see how the pure Java goes:
The gain in performance is really impressive, we start with almost 1 nanosecond with 10E-8 and finish with 1 millisecond.
So the problem is functional programming?
Seeing those numbers that is a temptation to assume that functional programming is slow and maybe not worth.
Well… let’s see that.
Haskell for the rescue
I had told you that when we speak about pure functional language we speak of Haskell. So I found the criterion library that makes a micro benchmark like JMH and made the same algorithm on that, you can find it here.
Let’s see how it goes:
The execute_seq does the same algorithm as the Java one, we now used the same strategy as JMH and made 2 iterations of warm-up and 3 of execution. Let’s see the results of the execute_seq with parameter 10:
There are some variations in warm-up but the fact it does the algorithm in nanoseconds is already better than the pure Java implementation.
Let’s make a plot.
Surprisingly as the number of iteration grows Haskell do not grows exponentially.
Here is a plot comparing both results:
As you can see that Java grows the time needed to perform the task as the number of iterations grows, Haskell is so optimized for the task that it take a while for it to grow, although Java in a pure form does a lot better than the lambda counterpart.
So the Java lambda implementation really has problems with performance. So for some serious computation tasks, pure Java is the most performative one, but the problem is not the functional paradigm it was how Java implemented that because the most pure functional language we have in the market proved to be much more performative and constant than the Java counterpart.
There is still some place for further testing, I used the openjdk and in the future I’ll make an article using GraalVM and comparing both results to see if they have a better performance over the task and use the Clojure as it is a functional language running over the JVM.
I hope you people liked it, until next time!