Skip to content

Commit e95c8bf

Browse files
JostMigendaRobadob
authored andcommitted
expand introduction
1 parent 641ea91 commit e95c8bf

File tree

1 file changed

+68
-2
lines changed

1 file changed

+68
-2
lines changed

episodes/optimisation-introduction.md

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@ exercises: 0
1919
## Introduction
2020

2121
<!-- Enable you to look at hotspots identified by compiler, identify whether it's efficient -->
22-
Now that you're able to find the most expensive components of your code with profiling, it becomes time to learn how to identify whether that expense is reasonable.
22+
Now that you're able to find the most expensive components of your code with profiling, we can think about ways to improve it.
23+
However, the best way to do this will depend a lot on your specific code! For example, if your code is spending 60 seconds waiting to download data files and then 1 second to analyse that data, then optimizing your data analysis code won’t make much of a difference.
24+
We’ll talk briefly about some of these external bottlenecks at the end. For now, we’ll assume that you’re not waiting for anything else and we’ll look at the performance of your code.
2325

2426
<!-- Necessary to understand how code executes (to a degree) -->
2527
In order to optimise code for performance, it is necessary to have an understanding of what a computer is doing to execute it.
2628

2729
<!-- Goal is to give you a high level understanding of how your code executes. You don't need to be an expert, even a vague general understanding will leave you in a stronger position. -->
28-
Even a high-level understanding of how you code executes, such as how Python and the most common data-structures and algorithms are implemented, can help you to identify suboptimal approaches when programming. If you have learned to write code informally out of necessity, to get something to work, it's not uncommon to have collected some bad habits along the way.
30+
Even a high-level understanding of how you code executes, such as how Python and the most common data-structures and algorithms are implemented, can help you to identify suboptimal approaches when programming. If you have learned to write code informally out of necessity, to get something to work, it's not uncommon to have collected some “unpythonic” habits along the way.
2931

3032
<!-- This is largely high-level/abstract knowledge applicable to the vast majority of programming languages, applies even more strongly if using compiled Python features like numba -->
3133
The remaining content is often abstract knowledge, that is transferable to the vast majority of programming languages. This is because the hardware architecture, data-structures and algorithms used are common to many languages and they hold some of the greatest influence over performance bottlenecks.
@@ -44,6 +46,70 @@ Therefore, the balance between the impact to both performance and maintainabilit
4446

4547
This is not to say, don't consider performance when first writing code. The selection of appropriate algorithms and data-structures covered in this course form good practice, simply don't fret over a need to micro-optimise every small component of the code that you write.
4648

49+
### Performance of Python
50+
51+
If you’ve read about different programming languages, you may have heard that there’s a difference between “interpreted” languages (like Python) and “compiled” languages (like C). You may have heard that Python is slow *because* it is an interpreted language.
52+
To understand where this comes from (and how to get around it), let’s talk a little bit about how Python works.
53+
54+
<!--
55+
TODO: It might be nice to use the figure from https://jakevdp.github.io/blog/2014/05/09/why-python-is-slow/#1.-Python-is-Dynamically-Typed-rather-than-Statically-Typed. here for illustration?
56+
57+
![Illustration of integers in C and Python.](episodes/fig/cint_vs_pyint.png){alt="A diagram illustrating the difference between integers in C and Python. In C, the integer is a raw number in memory. In Python, it additionally contains a header with meta data."}
58+
-->
59+
60+
In C, integers (or other basic types) are raw objects in memory. It is up to the programmer to keep track of the data type.
61+
The compiler can then turn the source code directly into machine code. This allows the compiler to perform low-level optimisations that better exploit hardware nuance to achieve fast performance. This however comes at the cost of compiled software not being cross-platform.
62+
63+
```C
64+
/* C code */
65+
int a = 1;
66+
int b = 2;
67+
int c = a + b;
68+
```
69+
70+
In Python, everything is a complex object. The interpreter uses extra fields in the header to keep track of data types at runtime or take care of memory management.
71+
This adds a lot more flexibility and makes life easier for programmers. However, it comes at the cost of some overhead in both time and memory usage.
72+
73+
```python
74+
# Python code
75+
a = 1
76+
b = 2
77+
c = a + b
78+
```
79+
80+
::::::::::::::::::::::::::::::::::::: callout
81+
82+
Objects store both their raw data (like an integer or string) and some internal information used by the interpreter.
83+
We can see that additional storage space with `sys.getsizeof()`, which shows how many bytes an object takes up:
84+
85+
```Python
86+
import sys
87+
88+
sys.getsizeof("") # 41
89+
sys.getsizeof("a") # 42
90+
sys.getsizeof("ab") # 43
91+
92+
sys.getsizeof([]) # 56
93+
sys.getsizeof(["a"]) # 64
94+
95+
sys.getsizeof(1) # 28
96+
```
97+
98+
(Note: For container objects (like lists and dictionaries) or custom classes, values returned by `getsizeof()` are implementation-dependent and may not reflect the actual memory usage.)
99+
100+
:::::::::::::::::::::::::::::::::::::::::::::
101+
102+
We effectively gain programmer performance by sacrificing some code performance. Most of the time, computers are “fast enough” so this is the right trade-off, as Donald Knuth said.
103+
104+
However, there are the few other cases where code performance really matters. To handle these cases, Python has the capability to integrate with code written in lower-level programming language (like C, Fortran or Rust) under the hood.
105+
Some performance-sensitive libraries therefore perform a lot of the work in such low-level code, before returning a nice Python object back to you.
106+
(We’ll discuss NumPy in a later section; but many parts of the Python standard library also use this pattern.)
107+
108+
Therefore, **it is often best to tell the interpreter/library at a high level *what you want*, and let it figure out *how to do it*.**
109+
110+
That way, the interpreter/library is free to do all its work in the low-level code, and adds overhead only once, when it creates and returns a Python object in the end.
111+
This usually makes your code more readable, too: When I read your code, I can see exactly *what you want to do*, without getting overwhelmed by overly detailed step-by-step instructions.
112+
47113

48114
## Ensuring Reproducible Results
49115

0 commit comments

Comments
 (0)