Speed-up your Python applications using Lithops and Serverless Cloud resources

Josep Sampé
ITNEXT
Published in
7 min readJul 15, 2021

--

The rise of Serverless computing.

Most of the user or enterprise applications are parallel in nature. The advanced features of parallelism are usually used to enhance the performance of an application. That is, reduce the total time an application takes to finish, making use of most of the available resources.

This article aim to explain the differences between serial and parallel programming, and demonstrate how we can leverage “Serverless” cloud resources to speed-up our python applications.

Photo by Aron Visuals on Unsplash

From serial to parallel programming

When programming python applications, it is very common for novel users to start writing “serial” code, that is, one instruction after another in a sort of “spaghetti” application. In this case, when the program is executed, the code uses one of the cores in the computer to execute the application up to the end. Let’s see the next example:

import time

def f(x):
time.sleep(10)
return x*x

if __name__ == '__main__':
for i in [1, 2, 3]:
print(f(i))

The easiest way to know if an application is executed serially or in parallel is to measure the time it takes to finish.

Serial execution of 3 tasks

As you can see, this simple serial application takes 30 seconds to finish its execution. This is because each f(i) execution needs the previous one to finish for being started. Ok, but how can we speedup this code?

Nowadays most of computers have multiple cores. New processor architectures make it possible to integrate even 16 cores (or more) in a common laptop. This fact makes that you are letting go most of the compute power of you computer when executing “serial” applications. Thus, one can easily infer that by using all the cores of a computer we can drastically speed-up any application. In this sense, the python multiprocessing library makes it easier to take benefit of all (or only the desired) cores of a computer. Let’s see the next example:

import time
from multiprocessing import Pool

def f(x):
time.sleep(10)
return x*x

if __name__ == '__main__':
with Pool() as p:
print(p.map(f, [1, 2, 3]))

This application is the equivalent parallel version of the previous example. In this case, the Pool() will detect the available cores in our laptop (let’s assume our laptop has 4 cores), and create a pool of 4 processes ready to execute tasks. When a pool is created, the processes are kept idle waiting for tasks. To submit a task, we then need to call one of the available multiprocessing operations. In this example, we call the p.map() operation that spawns one f() function for each value in the list [1, 2, 3]. That is, this code submits f(1), f(2), and f(3) to the pool, and then, as the pool has 4 free processes, 3 of them will take one of these tasks each one and run them in parallel.

Parallel execution of 3 tasks

In this case, the execution took only 10 seconds. This is Great! This shows the benefits of the parallelism. But what if we increase the input list to, let’s say, 100 elements?

if __name__ == '__main__':
with Pool() as p:
print(p.map(f, range(100))

In this case, the p.map() operation will submit 100 tasks into the pool. However, the laptop has only 4 cores, so it can only process tasks 4 by 4. This means that, at the end, this application should take approximately 250 seconds to finish. Let’s check it:

Parallel execution of 100 tasks

Well, the execution took 280 seconds to finish. This is 30 seconds more than the expected. This is due how the multiprocessing library is internally built, showing that it has some overheads when there are more tasks than cores, which results on a bottleneck.

Up to now, we demonstrated how a simple application is moved from serial to parallel, showing the benefits of parallelism, and the drawbacks when the parallelism is low due to limited resources. Now it is time to dream bigger and wonder; What if an application is designed to process huge amounts of data? What if my laptop has few cores, and the speed-up obtained is low? What if even using all the cores of a computer, the applications still take hours to finish? Don’t worry, thousands of cores are waiting for you in the Cloud ;)

Lithops to the rescue

Lithops logo

Here is the moment to introduce Lithops, a python framework that allows to scale your multi-process applications to the Cloud. Lithops does this task totally transparent to you, using a Serverless compute platform in the background. The only requirement needed to make it working is to change one line of code in your applications.

Serverless? Serverless is a computing paradigm that allows to execute computational units tasks called functions into the Cloud. In Serverless computing, hundreds (or thousands) of cores are immediately available for you in a few seconds, paying only for the exact resources you use.

Transparent? Lithops delivers the user’s code and data into the cloud without requiring knowledge of how it is deployed and run into the Serverless platform.

Lithops is an open source project available on Github:

If you are new to Lithops, please also read this blog post:

Amazing! Let’s use Lithops.

First, we have to install Lithop and run a test to make sure that it is working properly. Lithops is shipped with a cli tool that allows to run some administrative tasks from the console:

$ pip3 install lithops
$ lithops test

Hello Josep! Lithops is working as expected :)

Ok, everything is working, so as I promised you, let’s change one line of code and run the previous example using Lithops:

import time
# from multiprocessing import Pool
from lithops.multiprocessing import Pool

def f(x):
time.sleep(10)
return x*x

if __name__ == '__main__':
with Pool() as p:
print(p.map(f, range(100)))

At the beginning, if no extra configuration is provided, Lithops uses local processes to execute the functions, so at this moment it is like a substitute of the multiprocessing library. This means that, if you execute this code, it will run using Lithops framework, in parallel, and in your computer.

Let me activate Lithops logs to demonstrate you that it is working ;)

Lithops local execution

Note that now we are using a list of 100 elements as an input. So, as we ran the code locally in our 4 cores laptop, the application took 250 seconds to finish… Wait a moment… 250 seconds? using the multiprocessing built-in python library it took 280 seconds… Yes, in some circumstances, using Lithops in Localhost mode it is faster than the built-in python multiprocessing library. This is so Cool!

Now its time to move the python script to the cloud and increase the parallelisms to improve the speed-up. To configure Lithops for running scripts in the Cloud, follow these detailed instructions and setup one Serverless compute backend and one storage backend in your preferred Cloud provider. Once the configuration file is created with the appropriate keys, Lithops automatically starts using the configured backends. So now we only need to re-execute the previous python example and…

Lithops cloud execution

voila! The script ran in the Cloud, and now it only took ~18 seconds to finish. very cool! You are now ready to move your applications to the cloud and use the impressive parallelism that the Serverless platforms offer to you.

Summary

This article provided an overview of the Python Lithops library, and demonstrated how by changing only one line of code we can move any python application to the cloud, making use of a large amount of both compute and storage resources.

With Lithops we can not only move, but also build completely new multiprocess-like applications that can leverage the awesome parallelism of the Cloud Serverless platforms. Even more, thanks to the Lithops’ multi-cloud nature, applications can be moved from one cloud to another by only changing the Lithops config file, without requiring any change to the user’s python code.

--

--

I am a research scientist at IBM Research, interested in distributed systems, cloud computing, data analytics and software engineering.