Performant Python
CC Attribution - NonCommercial - ShareAlike 3.0 Unported:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this
Kloss, Burkhard
Burkhard Kloss - Performant Python Python is a great language. Easy to learn, friendly to use, widely used. It is not, however, renowned for being fast. In a lot of situations that does not matter. Sometimes it really does. This talk will introduce you to some tools and techniques for making sure your Python code becomes fast enough – without turning into a maintenance nightmare. Warning: may contain small bits of other languages.
 Good morning. Oh, wow. Thank you. That's a pleasant welcome. Thank you very much for coming to my talk. I'm going to talk about Perform Python. Now, I'm slightly disorganized and I forgot to bring batteries for my clicky things, so I'll be wandering back and forth. Bear with me. What do I mean by Perform Python? I like a standard and I like to think about writing good, fast, Python code. There are three words in there. Good, fast, and Python. To my mind, they have equal significance. Let's talk about good code first. Now, good code is complicated as a concept. We all know good code when we see it, right? There's all sorts of measurements you can apply, and there's academic literature about what is good code and what's not good code. As a working definition, I'll just say, it's code that we can easily read and understand. The next thing is fast code. Now, I love Python. I've been programming in Python for, oh, I don't know, since about version 1.6. There are lots of things I love about Python. But I don't write Python because it's fast, right? But it is fast enough for pretty much everything I need to do. And with a few tricks, it is actually fast enough for everything I do at the moment. Now, I have to kind of limit that slide my domain is mainly financial simulations. So, you know, I do floating pointer arithmetic all day long, and for that, it's fast enough. If your domain is web services, I.O., huge databases, you might not get that much out of this talk. But bear with me. Hopefully you'll enjoy it. And the last word we want to talk about is, oops, Python. We can, you know, there's the old joke about a real programmer can write Fortran in any language. And as a card carrying C++ programmer, I can certainly write C++ or Java in Python. Particularly when people write Java and Python with lots of abstract interfaces and abstract methods, it kind of irritates me. But we can, so I want us to focus also on writing Python code for a number of reasons. One is because it's actually much nicer to read and write than C++ in Python or Java in Python, but also it's faster. So, let's move on with the slide that's gone out of sync. Before we talk about speed, let's reconsider the famous quote from Nooth. Basically, 97% of the time we shouldn't worry about smaller inefficiencies. The 3% of the time that we should worry about them, it really matters. So bear that in mind. One of the things I'm trying to get across to people is that my slides are out of order. Never mind. That computers are cheap. Programmers are expensive, right? Every now and then you hear about skills shortages, shorted of programmers. Have you ever heard about a shorter of computers? Recently? No. So, let's move on. What do I mean by Pythonic? Has anybody ever done this, read this? The Zen of Python? Quick show hands? Excellent. Most of you have, those of you haven't, just type import this. At the Python prompt you get a whole lot of this. And I like the top three kind of make the most sense to me, right? Beautiful is better than ugly. Who could argue with that? Explicit is better than implicit. I think that's also very important. And simple is always better than complex. I have to always tell myself I'm a bear of little brain and complex code bothers me. Who was it that said the debugging your code is 10 times more difficult than writing it? So if the code you write is as complex as you can write it, you have no chance of being able to debug it. So that's always something I have to bear in mind. Fast Python. Okay. So a couple of themes I'll come through here. Pythonic. I'm happy to have a philosophical discussion about this over coffee later. It's just clean and readable. I will talk a little about profiling. I don't believe the quote about you can't manage what you can't measure. From a people management perspective, that's bullshit. From a programming perspective, it is absolutely correct. There's no point making any assumptions or building a hypothesis about your code unless you can measure what your code is doing. Once you have measured it, of course, the key things to speed things up is to do less. The fastest, most reliable code, as somebody much more intelligent pointed out, is code that you don't write. If code isn't there, it executes instantaneously. It has no bugs. So whenever you can do less or jump ahead and cheat and get somebody else to do it for you, that is a win in programming. The other two topics that come up a little bit later is vectorizing and parallelizing. Slightly interchangeable. Vectorizing from my perspective of somebody who writes in a relatively slow language and likes code to work quickly basically means that when we pass data off to a faster language or a faster subsystem, we do it in one go and operate on a whole batch of data to basically pay the overhead of the call only once and obviously parallelizing means using as many calls or computers as you can. So let's go back to Python and see what it performs. Now, on the interwebs and all that, you constantly hear about this Python as slow compared to X, Y, Z. What does it mean? You know, I don't think that's, Python being slow isn't really an issue. The question is, is Python too slow? I don't think it is for anything that I do and I do a fair bit of performance sensitive stuff in that domain. And, you know, we have a couple of hundred thousand lines of library code and most of that is not performance sensitive at all. There are a few hotspots where we do worry about performance but once you worry a little bit about them and deal with them properly, it actually turns out that Python is fast enough. So let's look at some code. Hopefully that's big enough for the people in the back to see. So this is kind of, you know, Python 101 code. This is the kind of code I would have written in 1999 when it started Python programming. And, you know, it's simple, it's clear, it's readable. It works. You know, all three of those are pretty, pretty good attributes for a little piece of code to have, you know, as a starting point. Is it fast? Well, let's write a little gadget to measure it. This is probably not as similar to Python, PyMet. How many Python users in the room? Cool. Why don't you... Like, those of you who don't use Python, why not? I mean, I don't use the notebooks myself because, you know, when you're running a Python code, you can't beat it. So let's look at this code, right? So if I run this, and a little bit of test code there, I'm going to run add up 10 million random numbers, and it comes back in just over half a second. That seems pretty damn quick to me. You know, I hate to play the old man card here, but, you know, the computers I started are much less add them up in half a second. So that's not too bad. So why are people still worried about Python performance? Well, they're worried about Python performance compared to C++ or C, right? So let's look at the same program in C. Boo, his. Nobody hates C? Okay, good. I'll take my C++ jokes out later on. Okay, so this is very similar. But if we time it, we'll find out that it is about 40 times faster than our Python program, which is a bit irritating. You know, just for bragging rights over coffee if you talk to a C++ programmer, or if you really have something very time critical. So let's look at this code again. This code is actually pretty much exactly the same as the C code. Now, I think we can make it faster but make me better. So if we just go to slightly more modern, more Pythonic style, and instead of, you know, using random access, stop again, instead of using random access and using a C style for loop, if we just use a proper Python for loop and iterate over a collection, it's actually I think a little bit faster. Let's have a look. Oh, yeah. To me, this is kind of the canonical perfect Python function to do a sum. There, you know, the current fashion is forgoing all functional programming. So I did write a functional version of it. Not desperately keen on that myself, but, you know, as a follower of fashion I had to put it in. So does changing it, changing a code to make it more Pythonic actually make it faster? I don't know for time. Yes, it does. So if you look at the second line down, you know, we've got a 100% 50% which is a way around you measure it and performance improvement by making a code a little bit simpler, a little bit cleaner, a little bit tidier, which is great. But it's still about 20 times slower than C++. Okay. So, hmm. So let's think about what else we have in our arsenal. And obviously you all know Python, so you know that we have a sum function built into Python. And we should just use that instead of reinventing the wheel badly. And that takes us to pretty much the same ballpark as C++. It's still a little bit slower. But, you know, at least we're in the same town now, right? So I think, you know, this kind of shows that actually if you write Python properly and know the language and use the facilities in the language, you're going to get something that's, you know, three or four times slower than C++. But that isn't, you know, an order of magnitude or two orders of magnitude. So it's, you know, for most things, it's probably not something you have to worry about. But we have a few more tricks like that. We have a bit of如何, flattening decía on the disc first. So it's kind of it. It's cat Perché wacky, and with ASAPit and drawing P comm física, looking the pictures do So this is really good. Now let's look at the performance of that. We're cheating slightly because we're converting from a Python list to an umpire array because it's a more efficient data structure. But glossing over that, we're actually faster than C++. That surprised me a little bit last night when I put this slide together. But you know, this is kind of what we do all day and it's not very surprising because we're comparing C code written by somebody who hasn't really written C for a living for 20 years to some of the best C++ programmers in the world. And they're faster than I am. That's great. So there's other things we can do though because sometimes numpy is a little bit awkward to use if you're not using, you know, if you're not doing basically matrix arithmetic. So we can cheat again in a different way. Oh, we've vectorized. We've gone pythonic. Let's cheat. And this is the slide from before, where you see Python. If we use PyPy, we get, with any of the version of the code, we're just as fast as a C++ code. So if you have a performance problem, look at some of the new jits out there. PyPy is great. We didn't even have to worry about rewriting anything. I mean, I still think you should write the more pythonic version of the code. But the performance gain you can get from cheating with PyPy is fantastic. So let's look at that. Now there's something different about this slide from the previous slide, and that's the fact that we're missing the numpy version. Now PyPy and numpy haven't had problems integrating in the past. They're slowly getting better. I mean, you can actually now install numpy and PyPy together, and it works. I just didn't have a chance to try it on my laptop before coming to do these slides. The kind of other thing you might want to look at, if you want to look at a jit, is number, which gives you a number of deployed opportunities to use GPUs. If you have the kind of problem that fits a GPU, now in my experience, that's one of those situations where if your problems fits the hardware architecture, it's fantastic, but it has to be a pretty close fit of your problem to the hardware architecture. So your mileage may vary. Let's move on. That was a cheat. And remember this. Now it's interesting, this quote has probably been stolen more than any other quote I've run across from a quote-checker. So I've seen it attributed to Picasso, Tennyson, and Jobs. I don't really care if it's good enough for any of them. It's good enough for me. So I think one of the real key things to getting your Python code far is to actually see what other work is out there already, check what you can take from other people, and use it to improve it. Moving on. Profiling and doing less. So let's start with some really naughty code. So these are anagrams. Yeah, there used to be something that people did before the internet was invented to keep themselves amused at night. But they're really tedious to work out by hand. Quite fun sometimes with political developments. But anyway, so let's see if we can get a computer program to work them out. So what do we do here? First of all, read the list of dictionaries. So we have a list of all the words we want to look at. And then we have a very simple kind of program to find all anagrams. Now let's look at this. A couple of things about this code. It worked. And it was really, really easy to write. Now you might not believe me that this works, but I do have you in tests to prove me, see me afterwards if you don't want to. So if we just look at how fast this runs, then again, we're looking at a little timing harness. If you're just interested in finding a few anagrams, this code runs in sub-second, blah, blah, blah, it doesn't matter. If you want to do this a lot, then you start having to profile. I think that's useful to remember. It's only worth spending time on speeding up code that's actually used a hell of a lot. If you run the program once a week and it takes five hours, who cares? If you run it all the time and it has to run in three hours, then you have a problem. So let's measure this. So this is using Python 3 on an old laptop. Takes one minute, 14 seconds. That's not slow, but interestingly enough, when I ran this in Python 2.7, rather than 3.4, it turned out to be faster. Not so good. We all want to switch over to three. Another poll, who's still using Python 2? Is anybody still using Python 1? No? Okay. About half of you are using Python 3. I'll just point out this is not a general rule. Python 3 isn't generally slower than Python 2. So that's going up great. Get home, get back to the office, switch over to Python 3. The world will be a better place. So let's run this in PyPy and yeah, okay. We've gone from one minute, something in Python 3, 40 seconds, and Python 2, to 22 seconds, which is pretty damn good, you know, for not doing anything apart from changing the command line. But let's assume that we have a really mean boss and he says 22 seconds are still too long to find all the anagrams in the English language. So let's look at profiling. Now Python is great, Python has a profile included, just so I can gauge how quickly to go through the profiling slides. Who knows the profiler? Okay. All right. The rest of you should learn it. It is worth well knowing. So the Python profiler is really, really simple to use. I don't know if you can see this. The all we do is call Python minus M to invoke the module C profile, which invokes the profiler, and that spits out a huge text file here. Your IDE probably has it included anyway, so just have a quick look and run it. And that spits out a file that looks a bit like this. Now, I don't have my laser pointer, but basically what it shows us, the number of calls, the time spent, the time spent per call, the time spent in the function and in any function it calls, and then the file name and the line number. And you know, unsurprisingly, if you look at the cumulative time column, we spend pretty much all of our time in the find all anagrams functions. Now in this case, that's not terribly useful. If you're profiling a big program, obviously finding out what functions take all your time, is the first step. There's no point optimizing all the functions you have, you need to apply your time sensibly. So let's see if we can do better than this. If you just look at this function, we can't really tell what's going on here. Let's see what other tools we have. Now, there's another profiler which sadly doesn't come installed with Python by default, is easy to install, pip install line profiler, takes no time at all. All you have to do is do an app profile decorator on the function that you've previously identified as a hotspot, and then we run Kern prof. So it's a slightly different command line, not too difficult. And this gives us some really interesting output. So first of all, it tells us how often each line of the function is executed. And that's in this case, isn't terribly surprising. What is, I mean sometimes I find the, actually the hit count more interesting than the timings, because the hit count tells you where the hotspots are. But in this case, you know, all our time, 99.4% of our time is spent in this if candidate and dictionary lookup, which is slightly surprising. While it was surprising until I thought about it, and you guys probably have figured this out already, the problem is we're doing a linear search in a dictionary inside a for loop, so n times n gives us an n squared algorithm, not a great idea if you have large numbers of items. So we can make a one line change to this function which speeds it up dramatically. And this is again slightly cheating, so we just change it to a set of words. It's slightly cheating because when I say it's a one line change, actually we're fundamentally changing the data structures that the program operates on. But Python makes it nice and convenient to do that in this case, so we're looking pretty good. And that now runs in 1.4 seconds, which is, yeah, I think fast enough compared to the one minute 14 seconds that we were looking at earlier. There's a slight surprise I had after this when I ran it in PyPy. And the PyPy version was actually slower. Now I mean, you know, that isn't terribly surprising if you think about it a little bit because it takes warm up time and has to do some more work than the interpreter. And if your program is running and has such a short execution time, then it doesn't have time to amortize that afterwards. But, yeah, anyway, so there we go. We have, with a very simple change, come to the end. Okay. Oh, sorry, did I miss the point? Okay. Well, thank you very much for bearing with me. No time for questions. Okay. Well, I'll be around for the day. So if you want to grab me, grab me. Thank you. Thank you.
 Good morning.  Oh, wow.  Thank you.  That's a pleasant welcome.  Thank you very much for coming to my talk.  I'm going to talk about  Perform Python.  Now, I'm slightly disorganized  and I forgot to bring batteries for my clicky things,  so I'll be wandering back and forth.  Bear with me.  What do I mean by Perform Python?  I like a  standard  and I like to think about  writing good, fast,  Python code.  There are three words in there.  Good, fast,  and Python.  To my mind, they have  equal significance.  Let's talk about good code first.  Now, good code  is complicated  as a concept.  We all know good code when we see it, right?  There's all sorts of measurements you can apply,  and there's academic literature  about what is good code  and what's not good code.  As a working definition, I'll just say,  it's code that we can easily read  and understand.  The next thing is  fast code.  Now,  I love Python.  I've been programming in Python  for, oh, I don't know,  since about version 1.6.  There are lots of things I love about Python.  But I don't write Python  because it's fast, right?  But it is fast enough  for pretty much everything I need to do.  And with a few tricks,  it is actually fast enough  for everything I do at the moment.  Now,  I have to kind of limit  that slide  my domain is  mainly financial simulations.  So,  you know, I do floating  pointer arithmetic all day long,  and for that, it's fast enough.  If your domain is web services,  I.O., huge databases,  you might not get that much out of this talk.  But bear with me.  Hopefully you'll enjoy it.  And the last word  we want to talk about  is, oops,  Python.  We can,  you know,  there's the old joke about  a real programmer can write  Fortran in any language.  And  as a card carrying  C++ programmer,  I can certainly write  C++ or Java in Python.  Particularly  when people write Java and Python  with lots of abstract interfaces  and abstract methods,  it kind of irritates me.  But  we can, so I want us to focus also  on writing Python code for a number of reasons.  One is  because it's  actually much nicer to read and write  than  C++ in Python or Java in Python,  but also it's faster.  So, let's move on  with the slide  that's gone out of sync.  Before we talk about speed,  let's reconsider the famous quote  from Nooth.  Basically, 97%  of the time we shouldn't worry about  smaller inefficiencies.  The 3% of the time  that we should worry about them, it really matters.  So bear that in mind.  One of the things I'm trying to get across  to people is that  my slides are out of order.  Never mind.  That computers are cheap.  Programmers are expensive, right?  Every now and then you hear about  skills shortages, shorted of  programmers. Have you ever heard about a  shorter of computers?  Recently? No.  So, let's move on.  What do I mean by Pythonic?  Has anybody ever done this, read this?  The Zen of Python? Quick show hands?  Excellent.  Most of you have, those of you haven't, just type  import this. At the Python prompt you get a whole lot of this.  And I like  the  top three kind of  make the most sense to me, right?  Beautiful is better than ugly.  Who could argue with that?  Explicit is better than implicit.  I think that's also very important.  And simple is always better than  complex. I have to  always tell myself  I'm a bear of little brain  and complex code bothers me.  Who was it that said  the  debugging your code is 10 times  more difficult than writing it?  So if the code you write is as complex  as you can write it, you have no chance  of being able to debug it.  So that's always something I have to bear in mind.  Fast Python.  Okay.  So a couple of themes I'll come  through here.  Pythonic.  I'm happy to have a philosophical  discussion about this  over coffee later.  It's just clean and readable.  I will talk a little  about profiling.  I don't believe  the quote about  you can't manage what you can't measure.  From a people management perspective, that's  bullshit.  From a programming perspective, it is  absolutely correct.  There's no point  making  any assumptions  or building a hypothesis about your code  unless you can measure what your code is doing.  Once you have measured it, of course,  the key things  to speed things up is to  do less.  The fastest, most reliable  code, as somebody much more intelligent  pointed out, is code  that you don't write.  If code isn't there, it executes  instantaneously.  It has no bugs.  So whenever you can do less  or jump ahead and cheat  and get somebody else to do it for you,  that is a win  in programming.  The other two topics that come up  a little bit later is vectorizing  and parallelizing.  Slightly interchangeable.  Vectorizing  from my perspective of  somebody who writes in a relatively slow language  and likes code to work quickly  basically means that  when we pass data  off to  a faster language  or a faster subsystem, we do it in one go  and operate on a whole batch of data  to basically pay the overhead  of the call only once  and obviously parallelizing means  using as many calls or computers as you can.  So let's go back to Python  and see what it performs.  Now,  on the interwebs and all that,  you constantly hear about this Python as slow  compared to X, Y, Z.  What does it mean?  You know,  I don't think that's,  Python being slow isn't really an issue.  The question is, is Python too slow?  I don't think  it is for anything that I do  and I do a fair bit  of performance sensitive stuff  in that domain.  And, you know, we have  a couple of hundred thousand lines of library code  and most of that is  not performance sensitive at all.  There are a few hotspots  where we do worry about performance  but once you worry a little bit about them  and deal with them properly,  it actually  turns out that Python is fast enough.  So let's look at some code.  Hopefully that's big enough  for the people in the back to see.  So this is kind of,  you know, Python 101 code.  This is the kind of code I would have written  in 1999 when it started Python programming.  And, you know,  it's simple, it's clear, it's readable.  It works.  You know, all three of those are  pretty, pretty good attributes  for a little piece of code to have,  you know, as a starting point.  Is it fast?  Well, let's write a little  gadget to measure it.  This is probably  not as similar to  Python, PyMet.  How many Python users in the room?  Cool. Why don't you...  Like, those of you who don't use  Python, why not?  I mean, I don't use the notebooks myself  because, you know,  when you're running a Python code,  you can't beat it.  So let's look at this code, right?  So if I run this,  and a little bit of test code there,  I'm going to run  add up 10 million random numbers,  and it comes back in just over half a second.  That seems pretty damn quick to me.  You know, I hate to play the old man card here,  but, you know, the computers I started  are much less add them up in half a second.  So that's not too bad.  So why are people still worried about Python performance?  Well,  they're worried about Python  performance compared to C++ or C, right?  So let's look at  the same program in C.  Boo, his.  Nobody hates C? Okay, good.  I'll take  my C++  jokes out later on.  Okay, so this is very similar.  But if we time it, we'll find out that it is  about  40 times faster than  our Python program,  which is a bit irritating.  You know, just for  bragging rights over coffee  if you talk to a C++ programmer,  or if you really have something very time critical.  So let's  look at this code again.  This code is actually  pretty much exactly the same  as the C code.  Now,  I think we can make it faster  but make me better.  So if we just go  to slightly more modern, more  Pythonic style, and instead of, you know,  using random access,  stop again,  instead of using random access  and using  a C style for loop,  if we just use a  proper Python for loop  and iterate over a collection, it's actually  I think a little bit faster. Let's have a look.  Oh, yeah.  To me, this is kind of the canonical  perfect  Python function  to do a sum.  There,  you know, the current fashion is  forgoing all functional programming.  So I did write a functional  version of it.  Not desperately keen on that myself,  but, you know, as a follower of fashion  I had to put it in.  So does  changing it, changing a code  to make it more Pythonic actually make it faster?  I don't know for time.  Yes, it does. So if you  look at the second line down,  you know, we've got a  100%  50% which is a way around you measure it  and performance improvement  by making a code a little bit simpler,  a little bit cleaner, a little bit tidier,  which is great.  But it's still about  20 times slower than C++.  Okay.  So, hmm.  So let's  think about what else we have  in our arsenal.  And obviously you all know Python, so you know  that we have a sum  function built into Python.  And we should just use that instead  of reinventing the wheel badly.  And  that takes us to pretty much  the same ballpark  as C++. It's still a little bit slower.  But, you know, at least we're in the same  town now, right?  So  I think, you know,  this kind of shows that actually if you  write Python properly  and know the language  and use the facilities in the language,  you're going to get something that's,  you know,  three or four times slower than C++.  But that isn't, you know,  an order of magnitude or two orders of magnitude.  So it's, you know, for most things,  it's probably not something you have to worry about.  But we have a few more tricks  like that.  We have a bit of如何,  flattening decía on the  disc first.  So it's kind of it.  It's cat  Perché  wacky,  and  with ASAPit  and  drawing P  comm física,  looking the pictures do  So this is really good.  Now let's look at the performance of that.  We're cheating slightly because we're converting from a Python list to an umpire array because  it's a more efficient data structure.  But glossing over that, we're actually faster than C++.  That surprised me a little bit last night when I put this slide together.  But you know, this is kind of what we do all day and it's not very surprising because we're  comparing C code written by somebody who hasn't really written C for a living for 20 years  to some of the best C++ programmers in the world.  And they're faster than I am.  That's great.  So there's other things we can do though because sometimes numpy is a little bit awkward to  use if you're not using, you know, if you're not doing basically matrix arithmetic.  So we can cheat again in a different way.  Oh, we've vectorized.  We've gone pythonic.  Let's cheat.  And this is the slide from before, where you see Python.  If we use PyPy, we get, with any of the version of the code, we're just as fast as a C++ code.  So if you have a performance problem, look at some of the new jits out there.  PyPy is great.  We didn't even have to worry about rewriting anything.  I mean, I still think you should write the more pythonic version of the code.  But the performance gain you can get from cheating with PyPy is fantastic.  So let's look at that.  Now there's something different about this slide from the previous slide, and that's  the fact that we're missing the numpy version.  Now PyPy and numpy haven't had problems integrating in the past.  They're slowly getting better.  I mean, you can actually now install numpy and PyPy together, and it works.  I just didn't have a chance to try it on my laptop before coming to do these slides.  The kind of other thing you might want to look at, if you want to look at a jit, is  number, which gives you a number of deployed opportunities to use GPUs.  If you have the kind of problem that fits a GPU, now in my experience, that's one of  those situations where if your problems fits the hardware architecture, it's fantastic,  but it has to be a pretty close fit of your problem to the hardware architecture.  So your mileage may vary.  Let's move on.  That was a cheat.  And remember this.  Now it's interesting, this quote has probably been stolen more than any other quote I've  run across from a quote-checker.  So I've seen it attributed to Picasso, Tennyson, and Jobs.  I don't really care if it's good enough for any of them.  It's good enough for me.  So I think one of the real key things to getting your Python code far is to actually see what  other work is out there already, check what you can take from other people, and use it  to improve it.  Moving on.  Profiling and doing less.  So let's start with some really naughty code.  So these are anagrams.  Yeah, there used to be something that people did before the internet was invented to keep  themselves amused at night.  But they're really tedious to work out by hand.  Quite fun sometimes with political developments.  But anyway, so let's see if we can get a computer program to work them out.  So what do we do here?  First of all, read the list of dictionaries.  So we have a list of all the words we want to look at.  And then we have a very simple kind of program to find all anagrams.  Now let's look at this.  A couple of things about this code.  It worked.  And it was really, really easy to write.  Now you might not believe me that this works, but I do have you in tests to prove me, see  me afterwards if you don't want to.  So if we just look at how fast this runs, then again, we're looking at a little timing  harness.  If you're just interested in finding a few anagrams, this code runs in sub-second, blah,  blah, blah, it doesn't matter.  If you want to do this a lot, then you start having to profile.  I think that's useful to remember.  It's only worth spending time on speeding up code that's actually used a hell of a lot.  If you run the program once a week and it takes five hours, who cares?  If you run it all the time and it has to run in three hours, then you have a problem.  So let's measure this.  So this is using Python 3 on an old laptop.  Takes one minute, 14 seconds.  That's not slow, but interestingly enough, when I ran this in Python 2.7, rather than  3.4, it turned out to be faster.  Not so good.  We all want to switch over to three.  Another poll, who's still using Python 2?  Is anybody still using Python 1?  No?  Okay.  About half of you are using Python 3.  I'll just point out this is not a general rule.  Python 3 isn't generally slower than Python 2.  So that's going up great.  Get home, get back to the office, switch over to Python 3.  The world will be a better place.  So let's run this in PyPy and yeah, okay.  We've gone from one minute, something in Python 3, 40 seconds, and Python 2, to 22 seconds,  which is pretty damn good, you know, for not doing anything apart from changing the command  line.  But let's assume that we have a really mean boss and he says 22 seconds are still too  long to find all the anagrams in the English language.  So let's look at profiling.  Now Python is great, Python has a profile included, just so I can gauge how quickly  to go through the profiling slides.  Who knows the profiler?  Okay.  All right.  The rest of you should learn it.  It is worth well knowing.  So the Python profiler is really, really simple to use.  I don't know if you can see this.  The all we do is call Python minus M to invoke the module C profile, which invokes the profiler,  and that spits out a huge text file here.  Your IDE probably has it included anyway, so just have a quick look and run it.  And that spits out a file that looks a bit like this.  Now, I don't have my laser pointer, but basically what it shows us, the number of calls, the  time spent, the time spent per call, the time spent in the function and in any function  it calls, and then the file name and the line number.  And you know, unsurprisingly, if you look at the cumulative time column, we spend pretty  much all of our time in the find all anagrams functions.  Now in this case, that's not terribly useful.  If you're profiling a big program, obviously finding out what functions take all your time,  is the first step.  There's no point optimizing all the functions you have, you need to apply your time sensibly.  So let's see if we can do better than this.  If you just look at this function, we can't really tell what's going on here.  Let's see what other tools we have.  Now, there's another profiler which sadly doesn't come installed with Python by default,  is easy to install, pip install line profiler, takes no time at all.  All you have to do is do an app profile decorator on the function that you've previously identified  as a hotspot, and then we run Kern prof.  So it's a slightly different command line, not too difficult.  And this gives us some really interesting output.  So first of all, it tells us how often each line of the function is executed.  And that's in this case, isn't terribly surprising.  What is, I mean sometimes I find the, actually the hit count more interesting than the timings,  because the hit count tells you where the hotspots are.  But in this case, you know, all our time, 99.4% of our time is spent in this if candidate  and dictionary lookup, which is slightly surprising.  While it was surprising until I thought about it, and you guys probably have figured this  out already, the problem is we're doing a linear search in a dictionary inside a for  loop, so n times n gives us an n squared algorithm, not a great idea if you have large numbers  of items.  So we can make a one line change to this function which speeds it up dramatically.  And this is again slightly cheating, so we just change it to a set of words.  It's slightly cheating because when I say it's a one line change, actually we're fundamentally  changing the data structures that the program operates on.  But Python makes it nice and convenient to do that in this case, so we're looking pretty  good.  And that now runs in 1.4 seconds, which is, yeah, I think fast enough compared to the  one minute 14 seconds that we were looking at earlier.  There's a slight surprise I had after this when I ran it in PyPy.  And the PyPy version was actually slower.  Now I mean, you know, that isn't terribly surprising if you think about it a little  bit because it takes warm up time and has to do some more work than the interpreter.  And if your program is running and has such a short execution time, then it doesn't have  time to amortize that afterwards.  But, yeah, anyway, so there we go.  We have, with a very simple change, come to the end.  Okay.  Oh, sorry, did I miss the point?  Okay.  Well, thank you very much for bearing with me.  No time for questions.  Okay.  Well, I'll be around for the day.  So if you want to grab me, grab me.  Thank you.  Thank you.
Burkhard_Kloss_-_Performant_Python_0_731.png Burkhard_Kloss_-_Performant_Python_732_851.png Burkhard_Kloss_-_Performant_Python_852_1787.png Burkhard_Kloss_-_Performant_Python_1788_2099.png Burkhard_Kloss_-_Performant_Python_2100_3827.png Burkhard_Kloss_-_Performant_Python_3828_4523.png Burkhard_Kloss_-_Performant_Python_4524_5447.png Burkhard_Kloss_-_Performant_Python_5448_7151.png Burkhard_Kloss_-_Performant_Python_7152_8615.png Burkhard_Kloss_-_Performant_Python_8616_9155.png Burkhard_Kloss_-_Performant_Python_9156_12083.png Burkhard_Kloss_-_Performant_Python_12084_13847.png Burkhard_Kloss_-_Performant_Python_13848_14207.png Burkhard_Kloss_-_Performant_Python_14208_15623.png Burkhard_Kloss_-_Performant_Python_15624_16043.png Burkhard_Kloss_-_Performant_Python_16044_16307.png Burkhard_Kloss_-_Performant_Python_16308_16859.png Burkhard_Kloss_-_Performant_Python_16860_18011.png Burkhard_Kloss_-_Performant_Python_18012_24731.png Burkhard_Kloss_-_Performant_Python_24732_27611.png Burkhard_Kloss_-_Performant_Python_27612_28751.png Burkhard_Kloss_-_Performant_Python_28752_36767.png Burkhard_Kloss_-_Performant_Python_36768_37951.png