Writing faster code – Writing faster code and not hating your job as a software developer – Writing faster Python



Writing faster code – Writing faster code and not hating your job as a software developer – Writing faster Python

2 11


europython2016

Presentation for EuroPython 2016

On Github switowski / europython2016

Writing faster code

Writing faster code and not hating your job as a software developer

Writing faster Python

@SebaWitowski

Python was not made to be fast...

...but to make developers fast.

It was nice to learn Python;            a nice afternoon

Donald Knuth

Would you like your FIRST program EVER to be like:

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, world!");
    }
}
                        

or

print("Hello, world!")
                        

Source: https://www.shoop.io/en/blog/25-of-the-most-popular-python-and-django-websites

OPTIMIZATION

1. Don't

2. Don't... yet

  • Finish your code
  • Have tests
  • Now

3. Profile

  • cProfile
  • pstats
  • RunSnakeRun, SnakeViz

Levels of optimization

  • Design
  • Algorithms and data structures
sum = 0
for x in range(1, N + 1):
    sum += x
print sum
                        

print N * (1 + N) / 2
                            

Levels of optimization

  • Design
  • Algorithms and data structures
  • Source code
  • Build level
  • Compile level
  • Runtime level

Optimization is all about the speed

... and memory

... and disk space

... disk I/O

... network I/O

... power consumption

... and more.

Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live

John Woods

Writing fast Python

a.k.a source code optimization

Setup

Python 3.5.1 (IPython 1.2.1)

def ultimate_answer_to_life():
    return 42
                        

 

>>> %timeit ultimate_answer_to_life()
10000000 loops, best of 3: 87.1 ns per loop
                        

 

2.72 × 1021 times faster than in The Hitchhiker's Guide to the Galaxy ;-)

#1 Count elements in a list

how_many = 0
for element in ONE_MILLION_ELEMENTS:
    how_many += 1
print how_many
                        

26.5 ms

print len(ONE_MILLION_ELEMENTS)
                        

96.7 ns

274 000 times faster

And collections module.

#2 Filter a list

output = []
for element in MILLION_NUMBERS:
    if element % 2:
        output.append(element)
                    

222 ms

list(filter(lambda x: x % 2, MILLION_NUMBERS))
                        

234 ms

[item for item in MILLION_NUMBERS if item % 2]
                        

127 ms 75% faster

#3 Permissions or forgiveness ?

class Foo(object):
    hello = 'world'
foo = Foo()
                        
if hasattr(foo, 'hello'):
    foo.hello
                        

149 ns

try:
    foo.hello
except AttributeError:
    pass
                        

43.1 ns 3.5 times faster

#3 Permissions or forgiveness ?

if (hasattr(foo, 'foo') and hasattr(foo, 'bar')
    and hasattr(foo, 'baz')):
    foo.foo
    foo.bar
    foo.baz
                        

401 ns

try:
    foo.foo
    foo.bar
    foo.baz
except AttributeError:
    pass
                        

110 ns 3.64 times faster

#3 Permissions or forgiveness ?

class Bar(object):
    pass
bar = Bar()
                        
if hasattr(bar, 'hello'):
    bar.hello
                        

428 ns

try:
    bar.hello
except AttributeError:
    pass
                        

536 ns 25% slower

#4 Membership testing

def check_number(number):
    for item in MILLION_NUMBERS:
        if item == number:
            return True
    return False
                        
%timeit check_number(500000)
                        

18 ms

500000 in MILLION_NUMBERS
                            

8.45 ms 2 times faster

#4 Membership testing

100 in MILLION_NUMBERS
                        

1.55 µs

999999 in MILLION_NUMBERS
                        

15.7 ms

#4 Membership testing

MILLION_SET = set(MILLION_NUMBERS)
%timeit 100 in MILLION_SET
                        

46.3 ns 33 times faster (vs list)

%timeit 999999 in MILLION_SET
                        

63.3 ns 248 000 times faster (vs list)

%timeit set(MILLION_NUMBERS)
                            

106 ms

#5 Remove duplicates

unique = []
for element in MILLION_ELEMENTS:
    if element not in unique:
        unique.append(element)
                    

8.29 s

set(MILLION_ELEMENTS)
                    

19.3 ms 400 times faster

Trick with OrderedDict (if order matters)

#6 List sorting

sorted(MILLION_RANDOM_NUMBERS)
                    

467 ms

MILLION_RANDOM_NUMBERS.sort()
                    

77.6 ms 6 times faster

#7 1000 operations and 1 function

def square(number):
    return number**2
squares = [square(i) for i in range(1000)]
                    

399 µs

def compute_squares():
    return [i**2 for i in range(1000)]
                    

314 µs 27% faster

#8 Checking for True

if variable == True:
                        

35.8 ns

if variable is True:
                        

28.7 ns 24% faster

if variable:
                        

20.6 ns 73% faster

#8 Checking for False

if variable == False:
                        

35.1 ns

if variable is False:
                        

26.9 ns 30% faster

if not variable:
                        

19.8 ns 77% faster

#8 Checking for empty list

if len(a_list) == 0:
                        

91.7 ns

if a_list == []:
                        

56.3 ns 60% faster

if not a_list:
                        

32.4 ns 280% faster

#9 Def vs lambda

def greet(name):
    return 'Hello {}!'.format(name)
                        

329 ns

greet = lambda name: 'Hello {}!'.format(name)
                        

332 ns

#9 Def vs lambda

>>> dis.dis(greet)
 0 LOAD_CONST    1 ('Hello {}!')
 3 LOAD_ATTR     0 (format)
 6 LOAD_FAST     0 (name)
 9 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
12 RETURN_VALUE
                        
Stack Overflow question on when lambda might be necessary

#10 list() or []

list()
                        

104 ns

[]
                        

22.5 ns

4.6 times faster

#10 dict() or {}

dict()
                        

161 ns

{}
                        

93 ns

1.7 times faster

Danger zone

#11 Variables assignment

q=1
w=2
e=3
r=4
t=5
y=6
u=7
i=8
o=9
p=0

                    

71.8 ns

q,w,e,r,t,y,u,i,o,p = 1,2,3,4,5,6,7,8,9,0
                    

56.4 ns 27% faster (but please don't)

#12 Variables lookup

def squares(MILLION_NUMBERS):
    output = []
    for element in MILLION_NUMBERS:
        output.append(element*element)
    return output
                    

149 ms

def squares_faster(MILLION_NUMBERS):
    output = []
    append = output.append # <= !!!!!!!!
    for element in MILLION_NUMBERS:
        append(element*element)
    return output
                    

110 ms 35% faster (and 27% more confusing)

Summary

  • There are different kinds of optimization
  • There are different levels of optimization
  • Source code optimizations adds up
  • Source code optimizations is cheap
    • Idiomatic Python
    • Don't reinvent the wheel
  • Profile your code and be curious!

Sebastian Witowski

Thank you!

Happy and fast coding!

Check the slides at GitHub:http://switowski.github.io/europython2016