Python Performance Unleashed: Essential Optimization Techniques Beyond Libraries

Performance optimization remains a critical challenge in Python development. While Python's simplicity and extensive ecosystem make it the language of choice for many applications, its interpreted nature can lead to significant performance bottlenecks. This is particularly evident in data-intensive applications, machine learning pipelines, and large-scale production systems where every millisecond counts.

Many developers immediately reach for external libraries or complex solutions when facing performance issues. However, Python's standard library and built-in features offer powerful optimization opportunities that are often overlooked. Understanding these fundamental optimization techniques not only improves code performance but also helps developers write more efficient code from the start.

This talk addresses the core performance challenges faced by Python developers daily. From memory management to algorithmic efficiency, we'll explore how seemingly simple code changes can lead to substantial performance improvements. Through practical examples drawn from real-world applications, we'll demonstrate how to identify, measure, and optimize performance bottlenecks effectively.

This session took place in track Python Language & Ecosystem and was classified suitable for intermediate domain / intermediate python by the speaker.

Transcript (auto)

Auto-generated from the recording utilizing Open-Source AI. Speaker labels (Speaker 1, Speaker 2) reflect diarization, not identity. Timestamps refer to the recording.

Speaker 1 [00:07]

Hi, PyCon. I'm happy to be here. I'm Thomas. I'm a machine learning engineer since three years. Before that, I was studying bachelor and master's. All of these I studied with the focus on machine learning and AI. This is not a topic about AI. It's about the bare-bone performance of Python. So, first of all, you might think, okay, why is Python performance important, or how do you measure it? Before I go more in depth about what the talk is about, I will briefly give you an example of what the talk is not about. So the talk is not about hacking code in Cytan, the talk is not about comparing different interpreters like Cytan, PyPy, or Rust Python, or using libraries to optimize your Python code like Numb or Desk. So this talk is really simple, pure simple Python, mostly based on the standard library, what are the many optimizations you can do to improve your quality and the speed of your code. Most people don't think like Python is like a fast language if you compare it to like a static type language like Rust or JavaScript. But Python is coming really far, good along, with each version increase, as you see on part chart of Python, the speed of the Python execution goes a little bit better and better and better. So the best performance increase in the last time was the jump from version 3.10 to 3.11. So if you can really keep my talk short, it might be sufficient enough to upgrade your Python version on your legacy project to gain like a significant performance. So this is something you can also read down, read about, I will later post the slides and And I've linked a blog post where this craft chat is from. And this goes in why Python has such steady increase in performance over each version. So now we go into like the optimization part. Before I want to go into the optimisation part, I want to use the speech of Donald Kute. Donald Kute is a famous informatica. He said, premature optimisation is the root of all evil, and down there under the headline Is there like the full quote? And like the really important stuff with optimization, optimization of Python code, which I then later will show you how it's done, is that you really should be aware that you don't go too far into the detail and spend like your whole time only to optimizing your code you have already written and not focusing on writing like a new or new features, a new product or like do feature optimizer program new features. So now before we can go into the optimisation, the optimisation, we first of all need to look at how our code is performing so that we don't end up like here in the quote. And therefore, you need to have some mechanism of profiling. So a profiler is simply put like a way to analyze how performant your code is running. So the tools which I use now for the talk is the timet module and the cprofile module. Both of these are quite in the standard library and are easy to use. If you want to analyze a larger code base, like production-ready systems, there are also other profilers you can use, all the inline profilers, but I can stand this talk down to these two profilers. And it's always important for you to measure your code, or you measure your optimisation. Because you're trying to optimise, if you optimise your code, you're trying to optimise on bottlenecks of your code. So you can optimise like your whole code, but it's like a never-ending task. There are small screws which you can tighten and which makes it faster, but mostly on the readability or like it takes a lot of time. So I will show you now what Cprofiler is. As I already said, Cprofiler is the built-in Python model for profiling code. based on C under this function of Python and it provides you a detailed information how the execution time of your program or like of your function is called and how much time it did take to compute. So the code is quite simple. You only need to import C profile, then have a function. Here is my Here I have simply an example function, and then you can simply call the C-profile model with the run function and give over the function you tried to optimise or you tried to analyse. This can also be your main function. How does it look like then in general? If you run the code, you get this output, which says how many function calls were in the function you provided, and which function structure did take how much time, the second column total time, and the third column is the cumulative time of the execution of all all of that, and this builds the call structure of your function you're calling and gives out how much time you did spend for each function call inside the function. So this is sufficient enough to get an overview of your normal Python optimisation or how your function is running comparatively. But if you want to have something in production, it might be worth to consider like a line profile that you can use. So the next step, if you have your profiling done, is that you need to plan the optimization. The key adjectives the first measure target, optimize, validate. So, first of all, you measure what you want to optimise, this can, therefore you can use like timet or cprofile to look if this really is a bottleneck in your code, this function or this code snippet you're providing or like in general. So, then you need to identify what is really the bottleneck, is the bottleneck really on a performance side, so CPU, or is this on the memory side or on the input output operations like disk speed or like network interfaces. And then you can apply the optimisation in the third step, only in the third step. So before that, you need to make sure it's really an issue, and you also need to make sure that you can fix the target and don't optimize the CPU performance if memory speed is the issue. So it won't help you further. And then, if you've done your optimization, you can validate. And to hammer forth this point, this is XKCD comic about how much time you can spend optimising your function code, and this is why I really want to hammer it in, that before you're doing your optimisation, please reconsider if it's really a bottleneck in your performance. It's really the bottleneck you want to address, or it's simply like a micro optimisation. you can use a lot of time to optimize your code. So now we have the profiling done. I want to get into the detail how you can optimize now in Python. In Python, it's really important to choose the right data structure, so for your choose the right data structure. So for example, like look apps, comparison can be faster with a different data structure. So like here in the example, an immutable tuple is faster than a list in Python to execute and search. The data performance increase is really not that large. This is more like a micro-optimization, which you can consider while writing the code. But I wouldn't do change this on, like, a production-ready code basis. The next point is something which also is about, like, more about types. It's how you string concatenate. An example you can see, like, we are trying to concatenate strings from our list. And like normal, you might learn this in the downed line, and the line 10 to plus upper right line 11 to plus concatenate with a plus inside of loop. And this is something which is really bad in Python, because like the string data type in Python is immutable. And every time you call the assign function to a new string, you need a new memory allocation, and this takes really time, and therefore, it's really better to use built-in methods like the in the line five with join, where you can simply join the list up in parts. So this can be quite helpful if you're trying to have, oftentimes this is an issue in production ready code because this adds up in a long loop. So the other things I want to also to show you about is really using like the standard built-in function in Python. So Python has really lots of cool functions like sum, map, filter, or sort, which are mostly implemented in C and are really fast. And so if you compare it with like a normal algorithm or if you don't use it, it adds, the performance adds. So here the performance isn't that, the performance increase isn't that large because we try to map like the map generator back to a list, and this is something which takes a lot of time in Python, but otherwise this would be like outside of this example case a really good use or use to use this utilization of like the normal functions. Also, I showed you a list comprehension as a second example. Please be aware that list comprehension without external function calls can be extremely fast. The performance tanks only if you really call like external functions, if you have like a huge call stack, otherwise list comprehensions are really a good way to optimize your code. I wouldn't dare to say that you should only write list comprehension because sometimes or sometimes they can be a bit too heavy and not easy to maintain if they are too large or have too much stuff in it. The other thing I wanted to show is like a feature most of you might not know. It's the Python function, it's the Python cache from functools. So the lau cache is simply least recently used cache, and if the function is called with like a parameter, then you can, the cache is saved, and if you call the function again with the same parameter, it simply used the cache version of it. This is a really easy-to-implement change because you can use the LiU cache decorator. This can be a considerable speed increase, as shown below, if the function calls has always the same parameters, the caching mechanism is good. This is something you can also easily add to your code. I think this were lots of little micro optimizations pattern I showed you. Now I want to be more generalized with that. So I want to speak about the pitfalls and any patterns of optimization. So as I already discussed in the beginning, over-optimization can hurt the readability and the maintenance, and it also takes a lot of time. This is why you should focus only really on your bottlenecks and try to optimize your bottlenecks. Most likely it's one or two lines of code which are really the issues, which are either like an unnecessary nested loop, which you can simply remove, or something like that that you can optimize. Also be aware that the concurrency of your CPU and the input and output concurrency can different so therefore try really to optimize on if you if you know it like CPU is a problem try to optimize on that and not on there so and the other thing that the third point is really if also if you profile they can misinterpretation can happen. So it's always good to correlate the results of your profiling with what your real world use cases would be. Normally you don't write at the beginning of your project like a system that can scale up to two million users. So normally you start quite small and then do the profiling and the optimising if problems arise in the performance. You should always balance the optimisation and the performance gain with code clarity and maintainability, because code clarity and maintainability is the stuff which helps you in the long run and you can because otherwise you can really destroy your code readability if you trying to to micro optimize your code so so I had the pitfalls and now what are the best practices for optimization so as I already said profiling before optimization use built-in or well tested library so that you can certain that the functions are already good maintained. This is also good use cases why we can use like the built-in standard library functions because normally they are already quite optimized at C-level. And if you change something in your code, try to condense it really at small local changes to really comprehend if the change you did really improved the performance or if it has some side effects you didn't consider the first time. If you do like lots of changes at once, this can be lost, and then you are trying to look which of the improvements destroyed your code. If you optimize something, please documentate the changes for the maintainability. This is, so I want to talk that you have like a nice project experience and not be as like the second line of the XKCD comic that you're trying to rethinking, overthinking your program or your code with the time scope of optimization and don't get further along with implementing features. Therefore, these are the key takeaways you should take away from the talk. And do micro-optimization, really focus on the bottlenecks. This is a little bit hard to discuss here in the talk because I cannot show, like, use some of code because it will be simply an overload. So also profile before your optimizations and focus on the bottlenecks. And this was, like, my talks. I'm finished, and I'm happy for questions.

Speaker 2 [21:41]

So the first question was how to determine IO, so disk speed, network speed or database bottlenecks in Python. Do you have any recommendation for that?

Speaker 1 [21:54]

You have external task managers that can watch the utilization with alarms if you're not cleared. You can use alarms on the instances if the disk or the RAM is an issue. And then you can also look on the network traffic, Fireshark, and then you can see if this really is an issue and if you're really bottlenecked with that.

Speaker 2 [22:24]

The next question was about the benchmark slide you showed in the beginning. It was how was the speed measured in the bar chart? What was the benchmark for that?

Speaker 1 [22:35]

The benchmark was a snake game, which uses a lot of list comprehension, dick comprehension, and it's built on different versions of Pythons, and this is a snake game. And the part chart performance, you see on the right, the 50,000, 100,000, is how many times like the snake simulation could run in each Python versions at a given time.

Speaker 2 [23:10]

Thank you. The next question was, how can you identify early on in your Python code you might have performance issues, even if it seems to be running smoothly, before you scale up?

Speaker 1 [23:21]

And this is really difficult because sometimes you are building your little script and you're trying on your local PC and it works all right, and then you scale up and the performance completely breaks down. And to mitigate this issue is really test with real data, try to be as close to integration as possible. So then, like the performance issue, arise quickly.

Speaker 2 [23:53]

And then for last question, before we wrap up, how would you decide if you have more I.O. bound or more CPU bound problems? Do you have to use different profilers for that or is it one profiler that combines everything?

Speaker 1 [24:09]

As my knowledge, you need to use different profilers. So one for the network and also one for the CPU board.

Speaker 2 [24:19]

Thank you and also thank you for your talk.

Thomas Berger

About — in the speaker's own words

Hi, I’m Thomas Berger! I work as a Machine Learning Engineer at a FinTech company and also teach part-time as a lecturer. I’ve been working with Python for over six years, starting during my studies, where I focused on machine learning. For the last three years, I’ve been applying these skills professionally in my full-time role as a Machine Learning Engineer. I’ve been diving deep into Python for things like machine learning, reinforcement learning, and high-performance computing. I love finding ways to make Python run faster and more efficiently, especially when tackling big data or complex models.

At PyCon, I’ll be talking about high-performance Python and sharing tips and tricks to help you optimize your code for demanding tasks. I’m excited to share what I’ve learned and connect with others in the Python community!

Social card for talk: Python Performance Unleashed: Essential Optimization Techniques Beyond Libraries