Friday, 23 January 2015

Java functional programming, Scaling out

In this post I just want to show a single benefit of using functional programming, I'll talk about functional programming in detail later, It does have many benefits and features.

Java 8 supports some of the many functional programming features. Although there are many arguments on the way Java 8 produces binary code for your functional code, but I just wrote a test to see if we can take advantage of functional programming in Java 8 or not.

Defiantly since at the heart of the Java everything is a class, the binary implementation of the functional part will also be somehow class related code, so we have another overhead rather than function calls. I said "function calls", just remind you that when you write a piece of code in a functional style (or lambda calculus) this part of the code will be nothing but calling many functions, and this means pushing parameters, return addresses to the stack, executing code, popping after return and ... This means using memory, many jumps, more CPU instructions and ... which costs much.

So obviously, if you write a piece of functional code in Java 8 it takes more time to get executed than normal code. But there is something in functional paradigm, which gives the advantage to functional programming even in Java. Before talking about this feature let us compare what I just talked about.

Look at the following piece of code:

List<String> myList = new ArrayList<String>();
SecureRandom random = new SecureRandom();
for (int i = 0; i < 10000000; i++)

We just create a list named myList with 10 million random String numbers. Now we want to check how many of them has the pattern "999" in it. If you want to write it in normal Java program style, we will do it something like this:

// 1: Counting in loop, takes 125 seconds  
int counter = 0;
for (int i = 0; i < myList.size(); i++)
    if (myList.get(i).contains("999"))

Now if you want to use lambda style to count the numbers contain "999" we can have it like the following which is absolutely more understandable:

// 2: Counting in functional style single thread, takes 160 seconds 
System.out.println( -> e.contains("999")).count());

If you run these pieces of the codes for a similar source list, the first one takes around 125 seconds and the seconds one takes around 160 seconds, so until now we can say that functional programming is slower than the ordinary procedural/object-oriented Java programming. (I ran the code in a Core i7-4790 CPU @3.60GHz with 12G RAM machine)

Scaling out
The big problem with distributing a job between multiple CPUs or even machines is how to find out if the jobs are dependent to another part of the code/environment or not. When you write a fully functional style code, you have to not have any mutation in your function, so in a sense each of you function is an atomic operation and they easily get run in parallel.

Java 8 has some features that let you run a job in parallel. I don't want to talk about the Java 8 functional features now, just let me explain how we can use this parallel features. Hopefully, the code we are trying to run does not depend to anything but the passed parameter, so we can use it in a parallel or multi-thread. 

We saw in previous example that List<T> allows you to use .stream() (single thread iteration) for functional programming. It also allows you to call .parallelStream() to get the passed function executed in parallel, see how we use it in the following piece of code:

// 3: Counting in functional style multi thread, takes 108 seconds 
System.out.println(myList.parallelStream().filter(e -> e.contains("999")).count());

And guess what? If you run it for the same list we had for our 1st and 2nd code, it takes just 108 seconds to execute!!

To finish this post, just let me say that not all of the test you write gives this result. I mean for a small size of the list defiantly the 1st piece of code takes less time, and perhaps even the parallel one takes more time than the single thread one. But there is a point at which a single thread gets saturated and can't do anything more, this is the place where the 3rd code shows its scaling out capabilities.