Visit our site to listen to past episodes, support the show, join our community, and sign up for our mailing list.
Summary
Do you find yourself reaching for a different language when you need some extra speed? With Cython you can get the best of both worlds by writing your code in Python and executing it as compiled code. In this episode we were joined by Craig Citro and Robert Bradshaw from the Cython project to discuss how and when you might want to incorporate it into your applications.
Brief Introduction
- Hello and welcome to Podcast.__init__, the podcast about Python and the people who make it great.
- Subscribe on iTunes, Stitcher, TuneIn or RSS
- Follow us on Twitter or Google+
- Give us feedback! Leave a review on iTunes, Tweet to us, send us an email or leave us a message on Google+
- Join our community! Visit discourse.pythonpodcast.com for your opportunity to find out about upcoming guests, suggest questions, and propose show ideas.
- I would like to thank everyone who has donated to the show. Your contributions help us make the show sustainable. For details on how to support the show you can visit our site at pythonpodcast.com
- Linode is sponsoring us this week. Check them out at linode.com/podcastinit and get a $20 credit to try out their fast and reliable Linux virtual servers for your next project
- I would also like to thank Hired, a job marketplace for developers, for sponsoring this episode of Podcast.__init__. Use the link hired.com/podcastinit to double your signing bonus.
- Your hosts as usual are Tobias Macey and Chris Patti
- Today we are interviewing Craig Citro and Robert Bradshaw
Interview with Craig Citro and Robert Bradshaw
- Introductions
- How did you get introduced to Python? – Chris
- What is Cython and how did the project get started? – Tobias
- My understanding is that Cython can improve the performance of a Python program without even having to provide any type annotations. How does it manage to do that? – Tobias
- Can a Cython module be used as a way to sidestep the GIL? What are some of the pitfalls that can be caused by doing so? – Tobias
- Can you give some examples of how Cython can be used to improve the perfomance of Python programs? – Tobias
- How does Cython work under the covers? – Tobias
- What were some of the challenges during the creation of Cython and what design decisions were made to overcome them? – Tobias
- Does Python’s cross platform nature create any unique challenges when compiling down to the C level? – Chris
- What processor and system architectures does Cython support and are there plans to expand that support? – Tobias
- How do generators and list comprehensions map to C, and did those higher level language constructs pose any special challenges in Cython’s design? – Chris
- Would Rust ever be a potential compile target for performance and safety optimized modules? – Tobias
Keep In Touch
Picks
- Tobias
- Craig
- Curious Kids Science Book by Asia Citro
- dplyr
- magrittr
- Everything Is Obvious: How Common Sense Fails Us by Duncan Watts
- Robert
Links
The intro and outro music is from Requiem for a Fish The Freak Fandango Orchestra / CC BY-SA
Hello and welcome to podcast. In it, the podcast about Python and the people who make it great. You can subscribe to our show on iTunes, Stitcher, TuneIn Radio, or add our RSS feed to your podcatcher of choice. You can also follow us on Twitter or Google plus and please give us feedback. You can leave us a review on iTunes to help other people find the show, send us a tweet or an email, leave us a message on Google plus or our show notes, and you can also join our community. Visit our new discourse dot pythonpodcastdot com forum for your opportunity to find out about upcoming guests, suggest questions, propose show ideas, and just get to know the hosts.
I would like to thank everyone who has donated to the show. Your contributions help us make the show sustainable. For details on how to support the show, you can visit our site at python podcast.com. Linode is sponsoring us this week. Check them out at linode.com/podcastinit and get a $20 credit to try out their fast and reliable Linux virtual servers for your next project. I would also like to thank Hired, a job marketplace for developers and designers, for sponsoring this episode of Podcast.init. Use the link hired.com/podcastinit to double your signing bonus.
Your host tonight is Tobias Macy, and I'm interviewing Craig Citro and Robert Bradshaw about their work on the Cython project. So could you guys introduce yourselves? Why don't you start, Craig?
[00:01:30] Unknown:
Hi. I'm Craig Zidro. I'm a software engineer in the Seattle area.
[00:01:34] Unknown:
What about you, Robert?
[00:01:36] Unknown:
See, I'm also I'm a a software engineer in the Seattle area as well, working for Google.
[00:01:41] Unknown:
Yeah. I I should have said I worked at Google with Robert.
[00:01:45] Unknown:
And how did you guys get introduced to Python?
[00:01:49] Unknown:
So I, I originally got introduced to Python when I got involved with a project called, Sage, which is an open source math software system, which uses, Python for, you know, a lot of the moving parts and for sort of gluing all of the various libraries that are included together. And, in particular, Cython a large chunk of the motivation for Cython came out of work on Sage. There was, you know, 1 or 2 other projects that kind of had a big influence there as well, but Sage was definitely 1 of them.
[00:02:16] Unknown:
Doesn't feel like deja vu. I was, also I was William Science adviser. He started Sage, so kind of the both of us, you know, used to be grad students in math. And, so I was, you know, working on Sage as a side project when I should have been doing my thesis, and then I started hacking on Cython as a side project when I was intending to work on Sage. And
[00:02:34] Unknown:
through all of it, I learned Python as well. So that's where I come from. Yeah. I should say I, I met Python the same week I met Robert because there was a a workshop I went to, and Robert was randomly assigned as my roommate. So, that 1 worked out pretty well for both of us, I think.
[00:02:49] Unknown:
It's an interesting story and, pretty humorous how you've managed to escape a side project with a side project. So can you guys explain what Cython is and how the project got started?
[00:03:05] Unknown:
It started out as a, Greg Ewing actually had a project called Pyrex, back in the day. The idea was that writing c extensions by hand is very tedious, doing things like, you know, getting reference count right and working with Python CABI directly. And that's the kind of thing that computers are good at. So he curated this project called Pyrex, which was a Python like language for that then compiled down to c. And that's what Sage was originally using. And then we started hacking on it and extending it in ways that that he, he didn't kinda take it in further directions that he wasn't taking his project. So Python is actually a fork of the Pyrex project. And we knew we kind of had our own project when we had I think, kind of what kicked it off is there was like this group of firemen who are downloading all of Sage, which is like this gigabyte tarball, just to use what was called with say at the time, Sagex, which was our port of, Pyrex.
And kind of at that point, we said, well, maybe we better spin this off into some thing. And so we grouped up with, Stefan Bonnell, who's maintaining another fork of Pyrex and created Cython.
[00:04:13] Unknown:
And, you know, to offer some more background, you know, what we used it for there was basically, in Sage, there's a lot of stuff you do in Python where it just makes sense, you kind of have these high level objects, you wanna introspect on them and do sort of very naturally fitting things in Python, draw pictures, you know, things like that. But very often, some of the computations you need to do are these, you know, traditional sort of like tight sequences of 4 loops where, you know, there's a a lot of computation done. You know, something like matrix multiply is a good example here where, you know, you can think of it as I'm gonna do several loops over this and paying an extra little bit of overhead for every single 1 of those operations just made the whole thing sort of pointless.
So Cython, you know, originally Pyrex, and then Cython was the hook where we could, you know, take just the pieces of the code where performance was really an issue and just basically move those down to c without it being a lot of work to rewrite everything on top of the Python c API. And, you know, Pyrex and later, you know, Cython grew to sort of fill exactly that role. It was it was the way to take just what you needed and make it fast without having to rewrite everything as you went.
[00:05:21] Unknown:
And can you guys explain a little bit of the syntax of Cython and how that interoperates with the Python language itself?
[00:05:29] Unknown:
Yeah. So it's it's nearly a superset of the entire Python syntax. So basically, you take Python and then you annotate, certain variables or functions with type information. It's the it's kind of the key key extension of the Python language. And then if you have type information, like, for instance, if you just declare something to be a c double, or a pointer or something, then you can do all the c operations on it without the Python overhead. There's also another use case which hasn't been alluded to, which is when you're trying to wrap a c library, we have a syntax for declaring the the functions that are available in the c library so you can call them directly. So it's good for writing wrappers as well as making your Python go fast. And for both of these, we try to extend the Python syntax in kind of a Pythonic way.
So it's just kinda like minimal, minimal extensions. And then pretty much if you can read Python, you can read Python.
[00:06:29] Unknown:
And does the new, type hinting in Python 3 5, is there are there any plans to take advantage of that as a way of annotating the Python code or modifying the annotations you guys support? Or would that just be too much work to modify the syntax that's already been created?
[00:06:48] Unknown:
There is a lot of thoughts to, to maybe doing that. Mhmm. 1 of the things that's interesting is that the type annotations are not contracts. They're more like, comments, at least in the, Python 3. So we versus, you know, when you when you give a type in Python, it's really a contract. But if, you know, if this thing isn't a double, like, we're gonna make it a double. And the other thing that is difficult is it doesn't have any way of kind of declaring external types declarations. If I want, like, you know, array of pointers, that's not very expressible.
And then the third thing that's hard is a lot of the speed could be gained by making sure that your local variables are typed. Typed. There's there's no way to do that. It's true. The the Python function annotations are really about, your function interfaces, and you can lots of times from the function signatures and infer a lot of information about the internal types, but, not always completely. And really, you know, if the the integer that you're looping over is typed is kind of a key, a key thing where if I siphoning in a lot of speed.
[00:07:55] Unknown:
Right. And, couple things that you mentioned in there. So you're saying, for instance, if you annotated a particular variable as being a double, it sounds like you're saying that you will actually explicitly cast that to being a double. Is that correct?
[00:08:08] Unknown:
Correct. So if I assign a Python variable to that, you know, if I have a Python object and I assign it to something typed as a c double, I'll actually, you know, force it to be a floating point number and extract the c double out of it. And then in the generated code, it will be a c double and then on. If it turns out that it's actually an incompatible type, then when you actually run the compilation, it'll generate a a compiler warning or a compiler error? So if we know it is, it'll be a compiler error. But of course, since, Python's dynamically typed, we may just say, oh, x is an object. I'm gonna treat it as a double now. And, the best we can do is throw a type error at runtime because x happened to be a string rather than a float or a name or something. Okay. So if you do have incompatible types, then there's no guarantee that the compiler is going to check it because of the dynamic nature of Python, and you might still end up with, with runtime errors unexpectedly. Okay. Yeah. Kind of the default type is the object type. Uh-huh. Objects is courseable to anything except the run time.
[00:09:01] Unknown:
And, my understanding too is that Cython can improve the performance of a Python program without having to provide any type annotations. I'm curious how it manages to do that.
[00:09:12] Unknown:
Yeah. So 1 of the things is that it cuts out on the interpreter overhead. So instead of, you know, having a bytecode among a little virtual machine, it actually, basically produces the c code to call the c API calls directly. And that usually buys you anywhere from a factor of 1a half to 4 depending on your, your actual program. So then the other thing we can do is we can infer a lot of the types. So, you know, if you have, you know, x equals 1.5, we actually infer that to be a double and then, a c double and then manipulate to the c types. So if you have, you know, 4, you know, I and range 1 to 10, we say, I is gonna be a c integer rather than actually creating this, you know, range object by calling the Python to get a list of Python integers and so on. So there's there's a lot of a lot of type inference that we can do that can speed up your code as well. All the type inference, unless this has changed recently and I didn't know about it, all the type inference that happens, happen sort of within the body of a function. This is also, you know, sort of complimentary to the kind of thing that you would wanna do with, type hinting.
[00:10:13] Unknown:
You were mentioning, for instance, if the variable has a value of 1.5, you were saying that it would be cast as a c double. I'm wondering if you prefer doubles in general because of their greater precision or if you will just, or if you do use floats liberally as well.
[00:10:29] Unknown:
Well, we use doubles because that's what, Python uses for its, floating point. So so there's kind of a Python float is under the hood of c double. So yeah. You could explicitly declare it as a c float, but, if you really wanted to. Okay.
[00:10:46] Unknown:
Yeah. Because I know that Python also has the concept of a double that you can explicitly set something as, so I wasn't sure. I didn't realize the, that under the covers floats are run as doubles as well at the sea level. So that's interesting. Yeah. Yeah. Also, inks are longs under the hood. Yes. That part, I did know. And I know that, 1 of the things that they changed in the 3 x series was how those longs get upgraded, at certain boundaries. I don't remember the details of it. But
[00:11:17] Unknown:
Yeah. Yeah. No. There's I mean, integers are actually 1 of the really tricky things because you wanna be able to infer them because c ints can be, you know, a factor of 500 faster than Python ints. On the other hand, you don't want to overflow things. So so pretty much if you just take pure Python code and compile with Python, the criteria is that it should behave exactly like as if you ran it in Python, except for hopefully fast. And so for instance, if you, you know, assign something and we only infer things by default to be integers if we can verify that they won't overflow.
So for instance, if you have, you know, x equals y plus y and we thought y might be an integer, but we might not be able to tell the y is a small enough integer, the y plus y won't overflow if we make it a c int. And so in that case, we have to say, well, x has to be Python object because it might be too big. So integers are really really tricky because I would say 99% of the time, you're not gonna overflow. But it's that 1% of the time it's gonna bite you. So so we can't we can't infer that and then, you know, have, you know, a silent wrap around overflow where Python would give you just a bigger value.
[00:12:22] Unknown:
Handling numbers in computation is definitely 1 of the trickier things around because of those overflows and the byte ranges or, you know, the the the byte boundaries.
[00:12:31] Unknown:
I mean, even just basic things. Right? Like, if I do a for loop over something that's larger than a c int could ever go to, the whole non terminating thing will definitely be a bad behavior change for your for your program.
[00:12:43] Unknown:
So can a Cython module be used as a way to sidestep the GIL? And what are some of the pitfalls that can be caused by doing so?
[00:12:51] Unknown:
So what we have is we have a way of declaring functions or portions of functions as not requiring GIL, in which case the GIL will be released during the execution of those functions. And so, there's a limited thing number of things you can do. For instance, you can't actually, the GIL is very, very pervasive. So you can't even do reference counting without the GIL. So you can't even manipulate Python list of dictionaries. But if all your types are c code or for instance, you're you're reaching into a list but you're not modifying it, you don't actually need to do any, ref counting.
You can release the GIL and then do a whole bunch of stuff. And, you know, in the meantime, Python threads are actually real threads that, that just have this global lock. Other threads can be going along while you're turning away in the C code without the GIL, and then you reacquire the GIL when you exit this block or exit this function. This is also useful sometimes when you're calling external libraries and, they obviously don't need the GIL. So if it's gonna be a long call, you can declare them as you release the GIL, call the library, and then when the call returns, then you re reacquire the GIL.
[00:13:52] Unknown:
And it's worth noting sort of the you know, I I'd mentioned that a lot of people, the way they use this is, you know, you start with some pure Python code and, you know, you take bits and pieces where you've already discovered you have performance constraints and you move those, you know, you you sort of annotate those in Cython so that those get moved down to sort of pure c. Well, if you find that you have a block of code that the whole piece or the whole function, right, which is sort of a natural boundary for Python code, the whole function happens to be basically executing in c, then, you know, lo and behold, suddenly, you don't actually need the GIL before you know, until the end when you're looking to return. So you can, you know, you can annotate that. And, you know, it's just 1 more nice lever for ways that you can once you've sprinkled in a little bit of annotation and found out what's going on, it gives you a hook to, you know, get get yet more performance out of it depending on the model of, you know, what you're doing.
[00:14:39] Unknown:
And we don't let you, do unsafe things with the GIL. So, 1 of the pitfalls in, trying to go to CA extensions by hand is knowing when you have and when you don't have the GIL and what you can do. And so for instance, if you release the GIL, you're not we actually took a pilot to do anything that requires the GIL.
[00:14:55] Unknown:
We've talked about it a little bit already, but can you give some other examples of how Cython can be used to improve the performance of Python programs? And I'm also curious about what the natural boundaries or natural sizing is for Cython subcomponents of a program or if it's common to use Cython for compiling an entire module or or application even?
[00:15:17] Unknown:
So I can I can take the first part of that, and then Robert can give you sort of the more exotic answers because, you know, he has more familiarity with the bigger examples? But, you know, the first thing that people do is, you know, sort of what I was mentioning before. You have you have your application. You know, it it does exactly what you want. You've basically written it in Python. You've enjoyed all of the things that people enjoy about Python. Right? Like, it's quick to write. It's quick to, you know, get up and running. You can experiment easily. And now what you found is that, you know, you have some sort of performance constraint. So I Cython actually gives you a few interesting tools for annotate for, profiling and things like that. I mean, you can sort of run your code through Cython and, you know, get some c code out and try to profile that directly with existing c profiling tools, which is often quite helpful and sometimes, you know, maybe not what you want. 1 of the things that Cython has is this annotation mode where you can spit out when you ask Cython to compile a a file, it'll actually spit out alongside it an HTML file, which is your source code, and the various lines will be colored from from white to, is it still dark yellow? Last I looked it was dark yellow. Okay. To dark yellow. And, the darker the line of code, the more the more Python c API functions that are sort of encoded in what's going on there. So now this is not directly telling you how much time is being spent in that code, but it's a good proxy for it. You know, the the 2 are strongly correlated.
So what you can do is and and you can actually also you know, just to sort of learn how Cython works, you can also take 1 of these and just, you know, click on it, and it will expand to show you the code that this original line of Python or Cython became. So you can use this to sort of identify places where there are hot spots in your code in terms of how much time is being spent pushing data back and forth from Python into c, you know, or or paying the price of using a Python object. And then if you look at that and you say, well, gosh. There's a whole bunch of, you know, activity going on here, and I know that this is just, you know, a loop control variable that's never gonna go above a100. I'm not gonna do anything exotic with it. It's really just a counter for me. It's the perfect spot to say, oh, okay. Well, I'll just tell Cython, hey. You know what? Go ahead and make this a c int. I'm okay with that. Or make this a c string. I'm okay with that. And you sprinkle a few annotations in, then you repeat the process, and you can, you know, reprofile or, you know, spit back out another annotated file and look at what happens or, you know, just run your whole app again and see, you know, where your new hotspots are, and maybe they're all gone. You know, the the examples if you if you find any talk on Cython, the first 1 the first example that people always offer is, you know, you have some trivial, you know, triply nested for loop where it is exactly the kind of thing where everything can just be pushed into c, and you can sprinkle, you know, 2 or 3 lines in, and then magically the function, of course, you know, for obvious reasons here, runs just as fast as it would have if you'd written it directly in c, but it looks a lot like Python and plays nicely with the rest of your Python code.
[00:18:08] Unknown:
Yeah. I mean, I think that that cloud that, classifies how optimization works in general. The the 1 of the nice things is so with a lot of, before Cython Pyrex, what you would do is you would write your thing in in Python, and if some part was too slow, you would have to rewrite that in c, you know, possibly introducing new bugs and debugging and everything. And then you'd have to write some wrapper to go back and forth. And also this boundary, was very ossified. It's kinda like, you know, in Python, you're above the land and then you drop down and see and you're like completely under the water in a completely new language with, you know, different conventions and, you know, a lot of hoops to jump back and forth. And the nice thing about Cython is it really lets you just take this little little tiny bit of code and say, this is the part that's too slow. Today, I'm just gonna optimize this. And then you can run along and then, you know, maybe 2 days later, you're like, oh, and actually this part here, I'm gonna do that too. And kind of like every little type you add gets you 1 step closer, and you have this really gradual, slope where you can kinda sit anywhere on the, you know, how much do I care about performance versus actually trying to profile and annotate my code versus, you know, a lot of I mean, it's kind of the, you know, 90% of your time is spent in 10% of your code. And allows you to spend time on that 10%. You don't even have to identify that 10% ahead of time.
Because we're as humans, we're notoriously bad at, protecting where we're actually gonna be spending our time, especially as program evolves. And, just, you know, on an as needed basis, you profile and you say, oh, this is the little spot that needs to be a little bit faster. And then you, you know, add some hints and sometimes that drops to 0. And of course, you know, when that drops to 0, you see another hot spot somewhere else. You can go and optimize that. So in terms of compiling the whole program, kind of the second part of your question, usually don't compile your whole whole program. You profile to figure out what parts of your program actually matter to compile. Because there is this extra step involved where you have to say, you have to invoke the Cython compiler to produce c file and both c compiler who's a shared object library and then load that into running session. And that is, you know, higher overhead than than just Python itself.
And but just like Python, Python is organized into modules. So instead of py files, you just have pyx files, and, you can see import as well as import from, p y x files to p y x files, so that they share c types as well as, Python types. So kind of organize your code exactly the same way. And then you say, well, this module is gonna be a compiled module because I'm, you know, hinting some stuff in here to be fast. And then, you know, usually, I'd say the bulk of your code is just Python because the bulk of your code is not, performance critical.
[00:20:42] Unknown:
And so once you've compiled down that shared object file, do the regular Python imports that you already had in your code still work or do you need to change the import mechanism that's used for loading that object? Nope. They all just work. So it's the same as any other, Python c extension where, when you say import foo, it looks for, you know, foo.sofileinrightplaceorfood.dllorwhateveryoursystem
[00:21:06] Unknown:
happens to have and, uh, loads that. You know, I can turn a module into Python and, and people will import it and not even notice that it's been changed.
[00:21:15] Unknown:
And could Python be used to as a way to help developers understand how their code performs under the regular CPython interpreter?
[00:21:23] Unknown:
Yeah. Yes and no. I mean, you definitely can use it to get some information. I was sort of mentioning this before. If you if you look at the annotated the annotated output from running Cython on a on a p Wi Fi, it'll show you, you know, sort of how much how much that 1 line of code corresponds to activity that's happening under the hood in terms of the Python c API. But, of course, you know, that is not the only aspect of performance. Right? You still need traditional profiling tools, some of which play very nicely with Python and some of which, you know, you have to sometimes do a little leg work to, to get going.
[00:22:01] Unknown:
Yeah. You could definitely see how much interaction there is with the CPython API. But, you know, some c a CPython calls are a 100 times faster than others. So, from a raw performance perspective, it's usually what happens is you profile your code and you say, I wonder why that is slow. And then you go look at it and then you then you identify a culprit. But the problem is if you just try try to identify it ahead of times, lots of times what people will do is they'll look at them like, oh, God. This, you know, this is touching objects all over the place in rough counting. And they don't realize that, you know, despite the fact that it's doing a bunch of rough counting, it runs in 5 nanoseconds.
You know, versus other piece of code that only, you know, makes 1 call to see API is actually doing something really expensive. So
[00:22:44] Unknown:
And, we've already talked about it a bit, how Cython works under the covers. I don't know if there's anything else worth digging into there.
[00:22:53] Unknown:
I mean, yeah. I mean, kind of the, you know, high level thing is it turns your, Python pile into a c file and then compiles it as a c extension. It might be worth noting that, you know,
[00:23:02] Unknown:
by and large, Cython also isn't, you know, sort of inventing its own practices for how your compiled code is going to play with Python. Right? It's just using the Python c API. A not unreasonable way to think about Python, which Robert mentioned earlier, is, you know, it's a way to make it so that you can avoid ever having to learn the ins and outs of the Python c API yourself, which if you've ever used the Python c API for anything fairly serious, you will appreciate greatly.
[00:23:30] Unknown:
Fortunately, I have not had to dig down to that level. But, if I ever do, I'm sure I'll be reaching for a site on pretty rapidly. So what were some of the challenges that you guys encountered during the creation and, maintenance and, evolution of Cython, and what are some of the design decisions that you made to overcome those challenges?
[00:23:52] Unknown:
1 design decision that's still with us unless, you know, been quite a bit of activity that I haven't noticed lately, is that the the parser is largely a large chunk of code, and the language that parses is, you know, whatever the parser happens to accept. Robert should correct me if I'm if this is no longer the case. But that for any anybody who's ever written a compiler. Right? Like, you know, you have this principle view of the world where, you know, you're gonna just write down your grammar and everything, you know, everything will be generated from there and, you know, you can just sort of model things on top of that. But then you end up with a few corner cases and trying to, you know, trying to make those changes later can be tough and troublesome.
[00:24:28] Unknown:
Yeah. Definitely, I would say that's so we do have prototype grammar for Cython, but it's not used yet. And certainly mixing the parsing of c types with the parsing of Python is is an interesting experiment. And another challenge I'd say is anytime you you you wanna add extensions to language with care, Because anytime you add anything, it's gonna be there forever, pretty much. And, so and, it's it's really easy. Like, a lot of it is, you know, look good on surface, but, you end up with this mess if you accept everything. And so you really wanna, you know, try to stay close to Python and say, is there a way we could express this, you know, very cleanly using the syntax we already have, rather than trying, you know, invent your own thing and diverge from from Python itself. Because the further you go from Python, the hardest can be for anyone who is reading this code to to understand what's going on. And so I think that's 1 of the things we try to do is stay close to Python so that anyone who who knows Python can just, you know, read a Python file. And you almost forget that you're reading Cython sometimes. And tell you like, oh, look, there's type annotation. That's not legal in Python. Oh, yeah. I'm not in Python.
[00:25:41] Unknown:
Yeah. Robert also, you know, hit on this 1 before. Right? But, like, the the compatibility with Python, not just in terms of syntax, but also in terms of semantics is a really interesting design constraint. Right? So, you know, for, when I was finishing grad school and Robert was finishing grad school around the same time, you know, Robert and I, I used to talk to him driving home, you know, 2 or 3 days a week, because I would, like, drop him off on my way home just to get a chance to chat longer. And I could characterize most of those conversations as basically we would start a conversation about a cool new idea that 1 of us had for, you know, an optimization that we could make. And over the course of the, you know, 15 or 20 minutes we were driving and the 5 or 10 minutes we'd be chatting after that, it would turn out that, basically, it sounded really good on paper and there you know, we could easily make it work, except that there are these, like, 2 corner cases where we'd actually be breaking the semantics of Python itself if we tried to do this.
And then we would, you know, try to figure out if there was some sort of static analysis that we could do that would make it so that we could still pull off this optimization in some cases. And almost universally, the answer would be no. Unfortunately, we can't do that without breaking, you know, compatibility with Python itself. So this is this has been a a a very serious design constraint, and the way we've overcome it is just, you know, if there there are certain things that if you want them, you simply have to provide an annotation or, you know, give us some other information yourself in order to let us be able to take advantage of, you know, some potential optimization.
[00:27:06] Unknown:
And have you guys ever submitted any peps for introducing any new syntax or utilities into the Python language and Python interpreter itself to either simplify or improve some of the work you guys are doing in Cython?
[00:27:22] Unknown:
I haven't. The the 1 pet that I've played with, which I I think the feature is almost there already, is, a lot of the code is plagued with dictionary lookups. Like, if you look at Python, really, it's just like there's this big pile of dictionary lookups. That's what you're doing. You're looking up you're looking up things in dictionaries. Yes. And when you do a method call, for instance, in a loop, it would be really nice if there's some cheap way to say, you know what? I did this dictionary look up before. I wanna be able to know if the result has changed. And so, you know, dictionaries had like some kind of a dirty bit or ink, you know, number that can be intermittent. I think there's, there's something like that with method caching on on types. And I don't know if dictionaries have this per se, but that would be really handy. If I was gonna submit a PEP, it would be that.
So you don't actually have to, like, you know, see, you know, is is is c dot food the same food that it was, you know, 20 milliseconds ago. And, so
[00:28:13] Unknown:
Didn't Dag do some pep related to or was it just something that got into NumPy? I thought he did a pep for something related to something with the way he was doing layout and strides and, you know, stuff like that. So there's so,
[00:28:25] Unknown:
Cython I guess, the Cython implementation of buffers influence the, the the new buffer path API.
[00:28:33] Unknown:
And when you say DAG, are you talking about DAG Bradley, the Rx Python maintainer?
[00:28:38] Unknown:
No. DAG. I don't actually know how to say his name, which is why I only said DAG, but it connects there, I guess. Listening. He can he can laugh at me at, you know, how poorly I pronounce his name. Alright.
[00:28:50] Unknown:
So does the cross platform nature of Python create any unique challenges when you're working on generating and compiling the c code?
[00:29:00] Unknown:
So yes and no. We have the advantage of auto generating code. So if you look at, like, you know, Python, like the first, like, 1, 000 lines of your Python code are, like, a whole bunch of macro system, you know, specific defines and everything like that. And so we're able to like, when you when you compile your c file, it actually works on a variety of platforms from and the same c file. So if you compile it on Windows side, then the same c file will compile on Linux or OSX. For Python, I believe the earliest support now is 25 up through 35.
So we're really abstract away all these details and it's, getting them right the first time is, kinda tricky. But once we're able to make the, you know, make macros for all the things we have, it doesn't matter how ugly the generated code is. But when you actually compile it on the system, these macros all expand to exactly the right thing. So it's actually better than writing CFDA by hand because, you don't have to be like, well, every time I'm making this call, I have to remember to do this thing if I'm on Windows, nothing if I'm not. You can just extract that away. I mean, there is 1 small difficulty, which is just that if you,
[00:30:00] Unknown:
I mean, at install time for a package, if you write a package that just uses Python, depending on how you've done things. Right? I mean, I think most people basically just can either include compiled .SOs, you know, or you know, in a wheel or whatnot for some of the common operating system. But if you if you do need somebody for various reasons to be able to basically start from your original PYS file, then run Cython, and then run the c compiler, I mean, you know, it is just a higher bar just as it is with any Python code that happens to include c extensions. But, you know, it's 1 that is easier to incur than, you know, taking the time yourself to write your own c extension from scratch. So, you know, it's 1 that we hit maybe more often than the average package. In In some sense, we make it so easy to write c extensions that then you're by yourself when,
[00:30:41] Unknown:
you find people, you know, trying to run your code on, systems that don't come with c compilers. So
[00:30:46] Unknown:
As an extension to that question, what processor and system architectures does Cython support? And are there any plans to expand that support to new systems or new CPU architectures?
[00:30:57] Unknown:
We pretty much support everything that Python supports. So c Python, I should say. I don't think we do any platform or anything. We're very we're pretty strict to trying to say a c 89 and staying within the standard of well defined behavior. So for instance, you know, we don't we don't rely on, you know, generated assembly or, you know, special hacks about how integers work in some cases. I mean, we even, you know, have cases where, for instance, overflow of signed integers is undefined. So what we'll do is we'll cast everything to unsigned integers, do the arithmetic there where it's defined by the c standard, and then cast back into signed in to avoid any architecture dependent behavior. So, of course, if you write code that depends on architecture specific behavior, for instance, care being signed or unsigned that, you know, if you're using a care, it'll be signed on the systems that assigned and unsigned on the system that signed. And, you know, that kinda leaks through. If you're using c types that have undefined behavior, then then we can't really do anything there.
[00:31:58] Unknown:
So how do generators and list comprehensions
[00:32:00] Unknown:
look in c? And do those higher level language constructs pose any special challenges in the design and implementation of Cython? I mean, if if anything, they were they were sort of a, a situation where there was a chance to do something a bit better, but Robert should really say this because he wrote all that code. I would say,
[00:32:17] Unknown:
Cython has a lot of go to, when you look at the generated code, and that's because we're trying to emulate the, the control flow of 1 language using another language. And, so as well as c types, you can also declare objects that are kind of cdef objects that have a underlying struct and have members that are c objects. And, we use the same thing for, like, generators and this and, where we actually create a struct representing the state of the interpreter. Then I mean, basically, a lot of a lot of the higher level features like generators are really just a cheap way of creating an object as an iter method that, that remembers what its state is at every edge at every point of execution.
And so we emulate that in c by creating an object. So when you call, like, the next method, all the stack variables are yeah. All the stack variables are actually contained in the struct of the object itself. And I know there's much more to say. I mean, it's basically, we we we look at where the spec is. We emulate it in c, and we try to pull stuff off the c stack to emulate the fact that Python's stack is much more of a flexible object than c stack.
[00:33:26] Unknown:
Part of the reason that we wanted to have you guys on is I've actually been following the Cython project for a little while, but it's also come up a number of times in other episodes that we've done. For instance, when we were speaking with the folks at Kivi, they make pretty heavy use of Cython for being able to target the Android NDK. I know that Pandas uses Cython to optimize some of their operations, and it's pretty popular in a number of data science libraries. I don't know if you guys have any sense of which sorts of industries or problem domains tend to make the heaviest use of Cython or any insights into how it gained such great popularity and widespread use?
[00:34:05] Unknown:
I mean, definitely, for sort of scientific applications, right, where, you know, if you think of the kind of the place where Cython can give you the biggest advantage is where you kinda wanna be writing C code, or rather the code that you're writing could be written just as well in C as it is in Python. And what you wanna do is sort of quickly pass from, I happen to be in Python with some objects and libraries that I care about down to, I'm just gonna write a bunch of for loops that are nested inside each other. This is this is very common in scientific applications. Right? Like the you know, we we all joke about these sort of, you know, nested for loops that you find everywhere. Right? If if that's what your code looks like, Cython is an incredibly powerful tool because with just the tiniest bit of work, you get all the advantages of having written it in c with none of the downsides of, you know, manual memory management or anything like this, and you, you know, it plays very nicely with existing Python code. So partly just in terms of timing and people that we're interacting with. So Sage, you you know, Sage has been you know, the Sage developers and the IPython developers have had close communication for years. So, you know, I mean, we were talking to the IPython and SciPy and NumPy folks since, you know, 2, 006, 2007. So, you know, I mean, it sort of worked its way in there very early. So for a lot of the scientific applications, it's a natural fit, and it's just been around long enough that it's just been picked up very well. As far as other areas where, you know, there are obvious use cases for it, I mean, like what Robert was mentioning, if you if you have an existing c library that you wanna wrap and expose into Python, Cython gives you sort of 1 good way to do this with a lot of sanity.
The other common way of doing this is to use something like swig, which as, you know, anybody who's used it will attest, often involves quite a bit of insanity. The contrast there is that swig swig will automatically generate you a wrapper for a library, and, you know, Cython cannot necessarily do that depending on how things are laid out. So, you know, it's it's, you know, you sort of have a choice, but places where you're wrapping existing c code are just a natural fit. So, you know, any sort of shop where they have some existing stuff, especially, like, networking libraries or things like that. I mean, for instance, 1 of the Google projects, gRPC, which has a networking library written in c, they expose that via Cython.
So, you know, it's it's a really natural fit in situations like that. Robert probably has some other good examples.
[00:36:19] Unknown:
Yeah. I mean, I just wanna comment on the wrapping stuff. Cython does force you to then to actually manually write a wrapper, but this is actually an advantage lots of times because the way that you use a library in c or c plus plus is often very different than you use it. Because a lot of times the c or c plus plus library is worried about things like memory management, lifetime of objects. And and if you just expose those APIs to Python, you really usually don't get a very Pythonic API. And sometimes you'll even get an API that can, like, segfaults if you're like, oh, I have to like manually allocate this thing before I can, you know, call these methods on it. And so Cython gives you an opportunity to not only expose the c library, but you create you can create a more Pythonic interface to the library, which is without incurring a whole bunch of overhead.
So then I think, so this is 1 of the areas where where Cython is used a lot. I think also the numeric community, like, scientific community uses Cython a lot because people play the role of, you know, on the 1 hand, they're like, you know, doing experimentation and prototyping, and they're trying to play with a bunch of ideas. And then, like, you know, 4 hours later, they're like, you know, I wanna run this on my supercomputer. And, you know, it you know, I only have 4 hours. If I can make my code run 10 times as fast, I can, you know, do it on 10 times bigger the the data set. And, so it really fits this niche nicely of, you know, you wanna you wanna have very high level language to play with, and then you want to be able to optimize things. You can say, at the end of the day, I just wanna add a 1, 000, 000, 000 doubles together, in the right order. And, this is this is very different than, like, say, the, a lot of professional programmers who, you know, when they sit down to write a project, they say, well, I'm just gonna choose language that's just insanely fast because this is what I do for a living and I already know I wanna do fast.
And kind of skip the, the prototyping experimentation stage a lot that scientists have. It's also used a lot outside. For instance, the other primary developer is developed is the leader of the LXML, Python project. And, and that's obviously not scientific computing. It's a lot of string processing. And, you know, there there's a lot of benefits for having a low overhead for parsing XML documents that, you know, there's a lot of, like, some kind of low level manipulation that kills you death by a 1000 cuts if you do it in pure Python with object allocation and string manipulation. So I think pretty much anywhere there's a there's need to, you know, take some Python code and interface with existing c library or just make it faster.
There's interest, and the scientific community has its fair share of those problems. So, so that's why it's been accepted a lot there.
[00:38:59] Unknown:
Extending that a little bit further, I'm just wondering what some of the most interesting and crazy uses of Cython are that you guys have seen out in the wild?
[00:39:06] Unknown:
What was the I forget the the story, Robert, but, like, the you pointed this you you mentioned this 1 earlier. What was the story with the firemen?
[00:39:13] Unknown:
We don't know what they're doing, but they're downloading Google by the Sage to get our, you know, 5 megabyte package and pull it out and use it. So, so I actually never hunted down what they're what they're using it for. But, I can't think of anything, like, way out there that comes to mind. I mean, I I remember being surprised, but I can't remember any of the specific instances. I know there's, like, you know, a lot of, fairly, serious number theory stuff that's done in Python with, with my experience in Sage where where you have these very it's it's an interesting blend of you have these very high level Python objects like elliptic curves.
And, you're able to boil this down to a problem that involves, you know, manipulating millions and millions of integers in the right way, looking for a certain combination of things that add up. And, it really, you know, is this enormous span of levels of abstraction. Because at the 1 end, you know, people are very concerned with, like, you know, getting things to fit within a 64 bit in it, you know, bit integer. And on the other hand, they're dealing with these, these things that, like, you just still don't understand after you have a PhD. So
[00:40:14] Unknown:
This question, I'm sure, is a little bit out there and probably unrealistic, but would would is there would there ever be potential for Rust to be a compile target for performance and safety optimized modules?
[00:40:26] Unknown:
It depends on how similar Rust is to c. I I remember looking into Rust, at a high level, but I haven't ever coded in it.
[00:40:34] Unknown:
Yeah. From a syntax perspective, it's pretty divergent in my understanding. And, I know it's possible to write Rust modules that can, be imported in Python because it does have the ability to be compatible with certain c modules, and I think you can call into the Python c headers. But from a syntax perspective, it's pretty wildly divergent and it has a totally different memory model. But just curious because of it being the sort of the new contender at the systems level for a performance optimized, systems language as well as its memory safety guarantees.
[00:41:10] Unknown:
Something you could do you know, this is not compiling that to Rust, but something you could do in the same ballpark that might be, you know, the kind of thing that somebody would need to use this for, you could imagine basically saying, look. I have a chunk of Python code or I have, you know, maybe a large swath of Python code, and I have some Rust code that I really want to interface with it. Maybe it's Rust and Python. Maybe it's Python and some other language. Right? But while not every language has an easy to interoperate with Python, just about every language under the sun does have an easy way to interoperate with c. So you can use Cython as sort of your shim to say, well, Cython is kind of a clean way for me to, you know, declare and move a chunk of this down or basically, you know, manually expose a little interface that can take my chunk of Python code and go down to c. And you can imagine, you know, in that other language, whatever it's going to be, doing the same thing on that side and then sort of using that as a shim to push the bytes back and forth at the bottom. So using this as a as a hook for calling between, you know, Python and some other language that you have to interface with. I mean, you would still have to do a little bit of legwork. This is the kind of thing where the first person who does it will have a really hard time. The second person who does it will have a pretty hard time, and then after that, it'd be pretty easy. But it's, you know, it's the kind of thing that we have tossed around as an idea once or twice. Like, oh, maybe I could do this by, you know, wrapping this thing and using and then going over here and it
[00:42:26] Unknown:
didn't ever, you know, materialize. But, you know, it's something that's definitely doable. Yeah. Certainly. I mean, if you're Rust if you have a Rust library that exposes a c API, there have been a lot of times when what people are wrapping is not c code. They're wrapping, you know, c plus plus code. They're wrapping the list code. They're wrapping decode. You know, that happens to expose the C API, and then we can tell that. So c is kind of a lingua franca of foreign function interfaces. I would say that actually it might be difficult because a lot of the stuff we do in Cython when we generate the output of the code looks unsafe. So we have all sorts of pointers that we're casting to all sorts of random pipes all over the place.
And, we could do this in Cython because we know, for instance, that this thing that's a py object star is actually some special struct because, you know, it was constructed in such a way. And, you know, we checked for it earlier by checking some bit on a field as, you know, types match. And I think it would be really hard for a language like Rust to understand these checks. For instance, to understand that we never take this branch that does this unsafe looking cast, unless we know this this cast is actually safe. So that might, that might, make it difficult to compile down something like Rust because it would say, oh, you know, you're not you're not allowed to do that. And in fact, we've we've kind of already put the guards, and the guards are scattered all over the place and not in a very declarative form. So so that might be a interesting challenge. We wanted to have other back ends that weren't as liberal as c.
But on the other hand, the since we start with Python, the opportunity to get things wrong unless you're manual unless you're manually calling malloc and such is much lower. So the benefit to compiling REST kind of I guess, I'd say it's Cython is I won't I won't say it's as good as rest, but it is, you know, leveraging, CPython and CPython's garbage collection stuff. Kind of the, the sanity check say, what you're doing doesn't make sense. Then the code that it generates is gonna be this crazy code that does all sorts of unsafe casts and, you know, point that you're referencing that it knows due to the higher level of the code that is now lost are safe.
[00:44:30] Unknown:
So are there any questions that I didn't ask that you think I should have? Or anything else that you'd like to bring up before we move on? I can think of now. Okay. Nothing's in mind. Great. So for anybody who wants to keep in touch with you guys and follow what you're up to, what would be the best way for them to do that? Why don't we start with you, Craig?
[00:44:49] Unknown:
I mean, you can find me in sort of the expected places. I'm on Twitter. I have a GitHub account. I have a web page. If you just Google my name, you're almost sure to get me. I'm, you know, I'm I'm 1 of few Craig Citros out there, and, you know, I'm I'm easy to track down. How about you, Robert? Yeah. My name is,
[00:45:09] Unknown:
definitely much more common, but, but if you Google me and, you know, post something in there like Scython, then you'll find my email. I don't really have Twitter or anything. Email is definitely the best way to get ahold of me. I'm not, that good at keeping up with it, but I'm definitely better keeping up with that than any other form of communication. So that's probably
[00:45:25] Unknown:
your best bet. I I found that if I wanna get a hold of Robert, the easiest thing to do is to find him in person. So if you wanna just go ahead and get a job at Google, that's the easy way to find
[00:45:35] Unknown:
it. So I guess with that, we'll move on to the picks. I'll get us started tonight. So I'm gonna keep it short. My pick tonight is actually a pretty interesting blog post from some of the folks at MIT. It's a post called Certificates, Reputation, and the Blockchain, and it's about how they were doing some research and experimentation into leveraging some of the blockchain technologies as a way to more easily and permanently track people's reputation and certificates and achievements in a way that is provable and more difficult to forge, and they do a really good job of likening it to the, to the notebooks that journeyman carpenters in Germany used to carry around in order to get the stamps from the different masters that they worked with so that they could then show that to their future customers to show what their qualifications and certifications were. So interesting read. I definitely recommend checking it out. And with that, I will pass it on to you, Craig.
[00:46:33] Unknown:
Alright. So, I have I guess I I thought of 3 as I was coming into this. So the first 1 will be entirely shameless. My wife has a a blog and now several books. So, I'll pitch 1 of her books, which is called the curious kids science book. So it's a first science you know, it's a a book of ideas to explore with your kids ages. You know, it's sort of targeted at 4 to 8. Some of the you know, our our 3 year old is not quite ready for it, but, you know, I won't lie. I had fun doing some of the experiments while she was writing it, so that's an easy 1 to to pitch.
I'm obviously a little biased given that, you know, she wrote it and my kids are in, you know, all sorts of pictures throughout it, but, that one's a lot of fun. Another 1 I, I you know, is in my day job at Google, I do I work on tools and sort of data science y stuff, you know, related to Python, but also with some other languages. And, you know, we use Jupyter a lot. So, the thing I would pitch is there's an R library called dplyr, which if you haven't run into and you do spend a lot of time doing data analysis, sort of dplyr and, you know, some of the related things going on around it with, like, McGruder. There's probably a classier way to say that, but I kind of like the lowbrow, pronunciation.
It's, it it's kind of an interesting way of you know what I mean? Just rephrasing the code you've always written, but it tends to be a lot more readable. And, you know, having used it a bit for for various things so far, I I tend to find that the data analysis code that I write is much less likely to be write once, read never, and much more likely to be stuff that I can reuse later. And that, you know, for for people who spend a lot of time writing data analysis code, that's a, you know, a really valuable thing. So, you know, even if you're not gonna switch to using r for anything, it'd be worth checking out just to, you know, just to see what it might look like and maybe, you know, pull a few ideas back with you. And, I guess, 3rd, I'll put you a book, that nobody I'm related to wrote, which is called Everything is Obvious Once You Already Know the Answer by Duncan Watts.
It's know, a behavioral economics book in the same vein as, you know, Thinking Fast and Slow and some of the other ones that have been popular, like Dan Ariely's books. But, he has a slightly different take, and Duncan Watts is a complexity researcher at Microsoft Research. And he's you know, having read his blog and, you know, that book and part of 1 of his others, he just seems like a pretty interesting guy. And, you know, I I got a lot out of reading that book and it's, you know, I think a lot of people have heard of. In fact, I think maybe somebody even listed it as 1 of their picks on another episode. And this is another 1 in the same vein, but, you know, maybe for whatever reason, people don't seem to have heard of as much. So I enjoyed it a lot. Great. Robert, how about you? Okay. So,
[00:49:06] Unknown:
I'm gonna start out with kid's book author. Unfortunately, it's not your wife. But I I have read 1 of her books, and it was really fun. We did some stuff there. So, Moe Williams, if you never heard of him. Yeah. Like elephant and piggy and a whole bunch of, like, you know, somewhat surrealist, but very funny for parents to read and kids to read. Yeah. So that's a very fun thing if you have kids between, like, the age of, like, 28 or, you know, probably or adults. So,
[00:49:34] Unknown:
yeah. We've we've tried to get him voted in as the, you know, children's poet laureate for the US, but haven't had any success yet. Maybe maybe we can work together on that 1. Yeah. Yeah. So, that that'd be my first pick. Second 1 is,
[00:49:46] Unknown:
just a just crazy fun toy that, that we got the other day. Philips Hue Lights. So they're, basically, they have, you know, red, green, blue LEDs in there, so you can, like, make your room whatever color you want. And 1 of the cool things is their little hub actually has a really nice rest API. So you could totally program it to do whatever you want, and it's just listening on the rest API to do to to listen to commands. So for instance, for my kids, I wired it to a keyboard. So they they type a color. You know, it's educational. They're they're learning how to spell their colors. And then, you know, the lights go that color. So, so that's just been a fun toy that we've been playing with lately. And then, for my 3rd pick, I'm gonna have to go with, so Sage, and most recently, the SageMathCloud is a really fast way to get mine. I I used to well, I did a lot of hacking on Sage, and I have to say my the Sage on my computer is way out of date. But, but these days, I'll I'll, you know, go online when I need to do a little computation or graph something or something like that. And, so you can just hop online and have a, you know, instant save running session. It's actually a full VM that has Jupyter Notebooks and everything in there, just, you know, kind of at the click of a button. So that's, that's really handy if you like, if you like math or did you just want a really easy place to run a notebook. Great. Well,
[00:51:00] Unknown:
I have 2 kids of my own, in those age ranges, so I'm definitely gonna have to check out Mo Willems. And I'll also, take a pick at that curious kids science book because they both like science as well. Awesome. Well, I definitely appreciate you guys taking the time out of your day to join me to talk about your work on Cython. I'm sure a number of other people are gonna be interested to hear about it as well. And, it's definitely a popular language. I've seen and heard it pop up in the community a number of times over the years. So congratulations on a job well done. And again, thank you. Thank you. It's been a pleasure. Have a good night, guys. You too. See you.
Introduction and Podcast Information
Meet the Guests: Craig Citro and Robert Bradshaw
Getting Started with Python
Introduction to Cython
Cython Syntax and Interoperability
Performance Improvements with Cython
Releasing the GIL in Cython
Optimizing Python Code with Cython
Compiling Python Modules with Cython
Challenges and Design Decisions in Cython
PEPs and Python Language Extensions
Cross-Platform Support and System Architectures
Handling Generators and List Comprehensions
Cython in the Wild: Use Cases and Popularity
Potential for Rust as a Compile Target
Final Thoughts and Contact Information
Picks of the Week