Comparison of performance of def and lambda functions. So all the same speed or readability?

Idea for code

Reading pep8, I came across an item about using anonymous functions – according to pep’s version, they reduce readability, if you use a variable with a function value as a function, it is better to use def. I decided to compare def and lambda in another parameter – performance. I assumed that lambda, sharpened for one-liners, would be faster to execute and create. In this study, I will test it.

Libraries

Since there will be many time measurements, we will undoubtedly need a time library, as well as a turtle, to draw all sorts of charts. I know this is impractical, but matprolib takes too long (10 seconds) to import. So:

from turtle import *
from time import time

Common functions

In our code, to measure performance, we need … a function to measure performance. It will be the main one for all derivatives. First of all, we will not measure the execution time once, the error is too large. The function will accept as arguments the function for which the measurement is performed, as well as the number of repetitions of this function.

For the measurement itself, we will use the time difference between the start of execution and the end. The code is formed from the description:

def speed_test(func, n):
    start = time()
    for i in range(n):
        func()
    stop = time()
    return stop - start

In total, we will have 2 diagrams – full and average. Each has 2 graphs – for def and lambda functions. In total we need 4 turtles.

The list of values ​​for 1 and 2 of the graph is obvious – several results of the speed measurement. With 3 and 4, everything is more difficult – you need to find the arithmetic mean of one of the first 2 graphs. In order not to bother too much so that the graph does not get out anywhere, we will find the difference between each element of each graph and the average value between the arithmetic means from 1 and 2 of the graph. As a result, on the chart we will see not the total value, but the difference.

We will put all the graphs into a common dictionary so as not to create many variables. The dictionary is pre-declared outside of the function

def graph_data(func1, func2, mult1, mult2, arr_len):
    l['l1'] = [func1(mult1)*mult2 for i in range(arr_len)]
    l['l2'] = [func2(mult1)*mult2 for i in range(arr_len)]
    l1_av = sum(l['l1']) // arr_len
    l2_av = sum(l['l2']) // arr_len
    av = sum((l1_av, l2_av)) / 2
    l['l3'] = [l1_av - av for i in range(arr_len)]
    l['l4'] = [l2_av - av for i in range(arr_len)]
    for i in range(arr_len):
        l['l1'][i] -= av
        l['l2'][i] -= av

Functions to make life easier

Who wants to repeat the same action, but with different parameters? Nobody. Therefore, I wrote some helper functions to draw a graph according to the given parameters, to create a turtle. Speaking of the latter, the skulls are also entered into the general dictionary.

def draw(arr, t, x, mult=30):
    n = len(arr)
    t.up()
    t.goto(-n*mult/2, 0)
    for i, j in enumerate(arr):
        t.goto(x+(-n*mult/2+i*mult), j)
        t.down()
    t.up()
def add_turtle(name, color="#000000", width=2):
    t[name] = Turtle()
    t[name].pencolor(color)
    t[name].width(width)
    t[name].hideturtle()
    t[name].speed('fastest')

Derived functions

At this point, the faint-hearted people who hate multi-level nesting should not read.

For the previously described common functions, you can create an infinite number of different ones.

For the derivative of the speed measurement, the structure is as follows:

def название(количество_повторений):
    def функция_для_замера():
        '''действия'''
    return speed_test(функция_для_замера,
                      количество_повторений)

And the derivative for the plotting function is the same function with specific arguments.

We will check the speed of creation and the speed of execution of various types of functions.

Let’s go back to the first one. In the case of checking the speed of creating a function, function_for_sampling () will have one goal – to create a def or lambda function inside itself. We will call this function many times, and each time it will re-create the same function. In other words, the function of the second nesting level is used for multiple calls and creation of 3 nesting levels during each function. I hope you got what I meant.

I could have made it easier, but wanted to keep the structure for all derived functions.

The first two derivatives are for creating empty functions that return False. For def, I could write using return or pass, but in lambda this is not possible.

def test_empty_def(n):
    def adding_def_func():
        def test(): return False
    return speed_test(adding_def_func, n)

def test_empty_lambda(n):
    def adding_lambda_func():
        test = lambda: False
    return speed_test(adding_lambda_func, n)

The next two are for the same functions, but with a simple expression:

def test_def(n):
    def adding_def_func():
        def test(): return sum((2, 3, 4)) ** 0.5
    return speed_test(adding_def_func, n)

def test_lambda(n):
    def adding_lambda_func():
        test = lambda: sum((2, 3, 4)) ** 0.5
    return speed_test(adding_lambda_func, n)

Two more – to assess the speed of their creation + the speed of execution:

def test_def2(n):
    def adding_def_func():
        def test(): return sum((2, 3, 4)) ** 0.5
        test()
    return speed_test(adding_def_func, n)

def test_lambda2(n):
    def adding_lambda_func():
        test = lambda: sum((2, 3, 4)) ** 0.5
        test()
    return speed_test(adding_lambda_func, n)

These functions will be used in derivatives of graph_data:

def for_empty_func(arr_len):
    graph_data(test_empty_def, test_empty_lambda, 10000, 20000, arr_len)
def for_one_eval_func(arr_len):
    graph_data(test_def, test_lambda, 10000, 20000, arr_len)
def for_doing_func(arr_len):
    graph_data(test_def2, test_lambda2, 10000, 20000, arr_len)

Algorithm

Let’s name the window:

title('Сравнение def и lambda функций по скорости')

Let’s create 4 turtles for drawing a graph:

t = {}
add_turtle('t1', '#c80000')
add_turtle('t2', '#00c800')
add_turtle('t3', '#c80000')
add_turtle('t4', '#00c800')

Let’s define the length of the diagram at the vertices:

arr_len = 20

Let’s prepare the data for the graphs and build them:

l = {}
for i in range(5):
    производная_от_graph_data(arr_len)
    draw(l['l1'], t['t1'], -300)
    draw(l['l2'], t['t2'], -300)
    draw(l['l3'], t['t3'], 300)
    draw(l['l4'], t['t4'], 300)

Let’s not forget to add a window close event:

exitonclick()
Final algorithm
title('Сравнение def и lambda функций по скорости')

t = {}
add_turtle('t1', '#c80000')
add_turtle('t2', '#00c800')
add_turtle('t3', '#c80000')
add_turtle('t4', '#00c800')

arr_len = 20
l = {}
for i in range(5):
    for_one_eval_func(arr_len)
    draw(l['l1'], t['t1'], -300)
    draw(l['l2'], t['t2'], -300)
    draw(l['l3'], t['t3'], 300)
    draw(l['l4'], t['t4'], 300)

exitonclick()
Complete code
from turtle import *
from time import time

def speed_test(func, n):
    start = time()
    for i in range(n):
        func()
    stop = time()
    return stop - start

def test_empty_def(n):
    def adding_def_func():
        def test(): return False
    return speed_test(adding_def_func, n)

def test_empty_lambda(n):
    def adding_lambda_func():
        test = lambda: False
    return speed_test(adding_lambda_func, n)

def test_def(n):
    def adding_def_func():
        def test(): return sum((2, 3, 4)) ** 0.5
    return speed_test(adding_def_func, n)

def test_lambda(n):
    def adding_lambda_func():
        test = lambda: sum((2, 3, 4)) ** 0.5
    return speed_test(adding_lambda_func, n)

def test_def2(n):
    def adding_def_func():
        def test(): return sum((2, 3, 4)) ** 0.5
        test()
    return speed_test(adding_def_func, n)

def test_lambda2(n):
    def adding_lambda_func():
        test = lambda: sum((2, 3, 4)) ** 0.5
        test()
    return speed_test(adding_lambda_func, n)

def add_turtle(name, color="#000000", width=2):
    t[name] = Turtle()
    t[name].pencolor(color)
    t[name].width(width)
    t[name].hideturtle()
    t[name].speed('fastest')

def draw(arr, t, x, mult=30):
    n = len(arr)
    t.up()
    t.goto(-n*mult/2, 0)
    for i, j in enumerate(arr):
        t.goto(x+(-n*mult/2+i*mult), j)
        t.down()
    t.up()

def graph_data(func1, func2, mult1, mult2, arr_len):
    l['l1'] = [func1(mult1)*mult2 for i in range(arr_len)]
    l['l2'] = [func2(mult1)*mult2 for i in range(arr_len)]
    l1_av = sum(l['l1']) // arr_len
    l2_av = sum(l['l2']) // arr_len
    av = sum((l1_av, l2_av)) / 2
    l['l3'] = [l1_av - av for i in range(arr_len)]
    l['l4'] = [l2_av - av for i in range(arr_len)]
    for i in range(arr_len):
        l['l1'][i] -= av
        l['l2'][i] -= av

def for_empty_func(arr_len):
    graph_data(test_empty_def, test_empty_lambda, 10000, 20000, arr_len)

def for_one_eval_func(arr_len):
    graph_data(test_def, test_lambda, 10000, 20000, arr_len)

def for_doing_func(arr_len):
    graph_data(test_def2, test_lambda2, 10000, 20000, arr_len)


title('Сравнение def и lambda функций по скорости')

t = {}
add_turtle('t1', '#c80000')
add_turtle('t2', '#00c800')
add_turtle('t3', '#c80000')
add_turtle('t4', '#00c800')

arr_len = 20
l = {}
for i in range(5):
    for_one_eval_func(arr_len)
    draw(l['l1'], t['t1'], -300)
    draw(l['l2'], t['t2'], -300)
    draw(l['l3'], t['t3'], 300)
    draw(l['l4'], t['t4'], 300)

exitonclick()

Tests

Moving on to the main thing – which is faster? Green on the chart denotes lambda, red – def

The first test is for the speed of creating an empty (almost) function:

the speed of creating an empty (almost) function
the speed of creating an empty (almost) function

The second test is for the speed of creating speed with the expression:

speed generating speed with expression
speed generating speed with expression

The third test is for the speed of creation and execution:

on the speed of creation and execution
on the speed of creation and execution

In all cases, lambda functions lead.

conclusions

To improve readability, use def anyway, but if speed is a priority, don’t use python, lol. But seriously, the article may be useful to someone, because Python is ideal for some tasks, so why not optimize these tasks?

Similar Posts

Leave a Reply