Implementation plan for speeding up CPython
github.com
github.com
https://mail.python.org/archives/list/python-dev@python.org/...
(Mark Shannon has been a core dev since 2018-05-15: https://devguide.python.org/developers/ though his involvement dates back to 2011 according to git log.)
> > 1. I already have working code for the first stage.
> I don't mean to be negative, or hostile, but this sounds like you are saying "I have a patch for Python that will make it 1.5 times faster, but you will never see it unless you pay me!"
and
> Where is your working code for the first stage?
> I believe that's how business works ;)
> I have this thing, e.g an iPhone, if you want it you must pay me.
> I think that speeding CPython 50% is worth a few hundred iPhones.
I wonder if he considered publishing it under a restrictive license that doesn't allow real world use. Then people could scrutinize his claims but there would still be an incentive to pay up for him to relicense it.
50k is about 3 months worth of a single FAANG developers base salary. It’s well worth it for someone to pay for the code.
But in the new corporate Python world this might be the logical conclusion. The losers are the people who made Python what it is today, for free.
Why are they "losers"? Did they sign any contract that something is owned to them? They did it for fun/personal reasons/ideology, and what they created is used by millions.
If they wanted to get paid for doing it, they could very well have had as well - several core python contributors have worked in large companies doing Python (including core Python) work, most famous of all Guido.
I don't see the pattern that the core contributors who work for large companies do important work. A lot of it is churn and minor patches.
Most important work appears to have been done by people who contribute a large chunk of code and then often leave.
Or by people who don't work for large companies but did a lot of useful bug fixes.
The PSF couldn't merge the code until it has the right license.
Large tech companies like Google wouldn't be willing to use the code until it has the right license.
If releasing the code under "look, don't touch" terms helps convince them that paying is worth it then that seems like a good idea.
Why not release this super-dooper Python interpreter under a proprietary license? Just have people pay $1000 for it. If it works, then it’s a steal for people who use Python for business. “Well, you have to take me at my word and pay me first” is highly suspect.
I’ll use PyPy, thanks.
This is something that I feel that Python need to do to keep the relevance. .Net Core, Rust and GoLang are examples of modern languages that deliver on the performance front.
I love Python and it is what use everyday. But very often I feel the need to a better code generation. Numba and Cython feels like a glue. PyPy it is slow with extensions, and this is a huge part of Python ecosystem. Although HPy (https://github.com/hpyproject/hpy) is trying to change this.
I feel that this is a must to Python, and would love to contribute money to this task.
And yeah, I would be happy to pitch in too if CPython can be 50% faster reliably. It only takes 500 people each pitching in $100 to reach $50k.
Edit: somehow got $50k etched in mind when it says $500k. That’s more difficult from individual funding.
Hpy appears to be the right approach to fixing this, but there needs to be a concerted effort to migrate the ecosystem toward it and then deprecate the old expansive interface. And then at some point in the distant future we can expect things to be as nice as other ecosystems are today.
Python and Ruby are trapped in the C extensions spiral. Languages are slow so everything uses C bindings, which makes it hard to speed up the language
I do believe this will be hard to change, though: Coming from academia my first Python experience was as a front-end to actively developed Fortran code. In that segment, the easy link to external code is one of the main selling points of Python.
You can write fast native you. You can write fast code by calling into native. The two rarely cross.
If we were clever, we could even have one front-end and multiple backends to go into fast Python, CPython, Cython, embedded micropython, etc. If we were double-plus-clever, we'd add GPGPU, multicore, and compute cluster to the mix.
We'd need someone with deep pockets and high risk tolerance to pull it off. It's a big change. Big changes often fail. Still, when I look at the amount of Python at Google... Well, it'd sure be a pity if all of that became legacy code when we all jumped ship to Julia.
Every dynamically-typed programming language attempts to expand until it has static types and compiles to machine code.
* Common Lisp has long had type annotations that can be use to generate specialized code.
* JS started out as a simple bytecode VM, then got a JIT from V8 and others, then TypeScript came and gave it static types.
* Python has had several JITs over the years—Unladen Swallow, PyPy, etc. It got static types with mypy (and others) and is now adding type annotations directly to the core language.
* Ruby's 3x3 plan involves adding a type-specializing JIT. Sorbet adds static types and I believe Matz wants the core language to go in that direction.
* Facebook created Hack to statically type PHP and PHP core added "scalar type dependencies". Facebook's HipHop VM brings a JIT to PHP.
* LuaJIT brought a high performance JIT to Lua and there's been a number of projects that layer static type annotations onto the language.
* Dart started with an optional type system and moved to a fully sound static type system.
So what I see is a language that starts out simple and dynamically typed with a set of core libraries and idioms designed around dynamism. Then later people add static types on the front end to help people maintain larger programs. And they add type specializing JITs on the back end to generate faster code.
But right in the middle you're still stuck with a mountain of existing code designed around the assumption that code and data don't need to be statically shaped. So even though you end up doing all the work (and adding all the complexity) to design a static type system and native code generator, you don't get the full benefits.
The static type systems are almost always unsound in order to play nice with existing dynamic idioms, so the back end can't use the static types for optimization purposes. You end up with these fantastically complex type systems like TypeScript's and these incredibly complex JITs, but you still don't get the performance you get from a simple fully-statically typed language like Go or, hell, Pascal.
I think the reasonable take-away is that if you ever intend your language to be used for large programs, just take the hit and start off with static types. Your future self will thank you.
I think this is missing something: the creators of those languages wanted languages that were fun/productive for writing small programs. That is, extremely flexible languages (and yes maybe they were unaware back in the 90's how this would limit their optimization potential)
And all big programs were once small programs. Facebook is probably one of the easiest to see, since it was "just" a bunch of PHP scripts (although people tend to underestimate/dismiss it for that reason).
Just like nobody ever says: "Well I estimate that in 10 years Facebook will consist of 10M lines of PHP with 5,000 programers working on it -- I should probably write it in another language".
Nobody ever says: "Well I think my language is going to be used by millions of people and will have billions of lines of code".
Well, there probably were people who thought that, but those were exactly the people who didn't make languages as useful as Python and JS :)
----
That said I think the phrase "irrational exuberance languages" referring to Python/JS is kinda funny, and in a way accurate ...
https://blog.sigplan.org/2020/10/12/from-heavy-metal-to-irra...
Although again I would say the unexpected part was not that they thought single core scaling would continue forever and make their languages fast, it's that those "slow" languages turned out to be the "best" ones for writing some of the most important systems of the last couple decades (not just commercial ones, but also Wikipedia, BitTorrent, etc.)
----
I'd make another analogy, to ISAs. If you talk to anyone who knows about CPU design, they'll say that x86 is shitty with a big pile of hacks.
"It would be better" if someone designed it from the beginning with current applications in mind. But if anyone actually did that back in 1980, they wouldn't have been successful.
And from my perspective, I mostly don't care, because the C compiler makes it all work for me (although I know the people who make it work care very much).
So I guess the point is that technology adoption proceeds by evolution, and trying to plan 10 or 20 years ahead of time never works.
----
Also, there is a pretty hard tradeoff between static types and metaprogramming. Recent languages have come closer to reconciling these features (Zig, Nim, D), but dynamic languages chose reflection/metaprogramming, and that's a primary reason why they became successful.
Ruby on Rails is a great example of that. It uses Ruby metaprogramming/reflection to a hilt, and lots of people who have no idea what that is love it, and they built tons of things with it (which now makes it an interesting optimization target)
That's the main argument in favor of optional type systems like TypeScript. Start small in a dynamically typed language and then add the types later when you need them.
But my personal experience is that I've never found types to cause much friction when programming in the small. What I have found painful is not having GC and not having type inference. If I was doing a startup and needed to be able to prototype and change quickly, then C++ or Java would be pretty painful. But C# or any modern typed, managed language with a decent modern IDE? I would be surprised if you were any less productive than someone using Python or Ruby.
A few points I would like to add on:
- I don't view the existence of TypeScript, MyPy, and Sorbet as evidence in favor of static typing. It's evidence in favor of gradual typing!
- The Oil project gave me a lot of experience with the relation between metaprogramming/reflection, dynamic types vs. static types, performance, and code length.
In particular the code moved from dynamic to static typing, and got a lot faster. But dynamic typing wasn't a mistake.
Short recap: Oil's code is 5-7x shorter than bash [1], and a lot of that is due to starting out as dynamically typed, with a lot of metaprogramming. (I still believe "size is code's worst enemy" -- big code is understood more poorly, which makes it harder to modify, regardless of static types.)
It is also something like 30x-50x slower than bash in Python! So, unusably slow. However the surprise is that I statically typed this code, and semi-automatically translated it to C++, and the result is now faster than bash. [2]
-----
So the high level, short code has enough semantic information to be fast (after you add explicit types).
The static typing process mainly evolved expanding metaprogramming to textual code generation! I estimate that this was at least 9 months of rewriting.
That's what your arguments are missing IMO. If you're writing Java in Python, then sure Python is going to seem like it offers no advantages, and it might as well be statically typed.
But that's not how people write programs in dynamic languages (and honestly I thought you would appreciate that more, having written so much about dynamic languages!) The porting process taught me exactly how much dynamism I was using, and it was a lot! It was pulling a lot of weight.
I should show all the code generators and generated code in an essay... it's a very concrete demonstration.
So there is the fallacy of "type inference" solves the problem -- it's not that we're too lazy to write down types; it's that we're using techniques that static type systems can't handle. Good thread about that: https://twitter.com/sliminality/status/1317331149354463232
I'm not saying the Oil experience generalizes, since it's an unusual project, but it's definitely not as simple with "go with static types so you don't get trapped". That said, the conundrum you're talking about is very real.
-----
But despite writing all that, I'm actually leaning in your direction, and I started a statically typed language :)
https://old.reddit.com/r/ProgrammingLanguages/comments/jb5i5...
I would start with an interpreter so it can have metaprogramming (e.g. like a constexpr interpreter, or what Zig does). I would like to add a gradual type system, but I don't really know how to write one, so the first cut will be a traditional static type system. (In this world, getting the 30-50x speedup relies on the program being 100% statically typed, yet gradual typing is still important IMO. It's very simple, no infinite treadmill of JIT work as you see in the "professional" projects.)
-----
This is probably something for an essay, but I would say dynamic types are demonstrably better than static types for at least 3 domains: UI, data science, and security/reverse engineering (and I have a bunch of experience to back this up). Basically anything that involves "learning about the world", or "schema discovery".
[1] http://www.oilshell.org/blog/2019/06/17.html#why-is-it-writt...
[2] https://www.oilshell.org/blog/2020/01/parser-benchmarks.html
Good thread about how successful Ruby and Python have been in YC companies: https://news.ycombinator.com/item?id=24279611
Or is that part of why you said you needed to write an essay.
https://old.reddit.com/r/ProgrammingLanguages/comments/jb5i5...
The mycpp tool I wrote can be thought of as a cross between this old "Shed Skin" project, and mypyc:
https://www.oilshell.org/cross-ref.html#mycpp
I kept extensive notes on the process on Zulip, list of threads ehre:
http://www.oilshell.org/blog/2020/03/recap.html#mycpp-the-go...
Really short summary: I used the MyPy front end, and basically printed its AST as C++. This involves a bunch of hacks, but it works.
The following conditions helped a lot:
- I control all the code in Oil, so I can statically type all of it, which involves both annotations and occasional patches. A shell doesn't have many Python library dependencies; i.e. it doesn't depend on BeautifulSoup or something like that. It's basically all string and data structure manipulation, with a few sys calls.
- Oil has extensive tests. I run the same tests against the translated and compiled C++, which flushes out bugs in the translator. (Something like 915 out of 1700 tests pass now, so the translation process isn't done.)
- I also used unit tests to generate some of the type annotations with pyannotate.
There is still some translation left to do if anyone is interested in helping. You will probably learn something about both Python and C++!
Strongly disagree. Although x86 was dominant on desktop PCs with MS DOS, Nintendo Entertainment System used MOS 6502 which is a lighter version of m68000, while Sega Mega Drive used a full blown m68000. Apple II desktop also used MOS 6502, HP used PA-RISC for their HP-UX servers, and Sun replaced their 68000s with SPARC in late 80-s. Alpha achitecture was so successfull there was a Windows NT port for this achitecture. So it wasn't really game over as of 80s, and even in 90s the market was still diverse. It's only by 00s x86 together with Windows NT series achieved total dominance, even in small server segment.
>So I guess the point is that technology adoption proceeds by evolution, and trying to plan 10 or 20 years ahead of time never works
Right conclusion - wrong reasoning. Quality of product is never a main driver of sales. That's why to succeed you need to market it first and then elaborate some way to make it usable. That's where, for example, Motorolla, Alpha, MIPS failed, and that's where ARM won as an umbrella brand for actually 4 incompatible architectures (original ARM, AArch32, AArch64, and Thumb a.k.a. SuperH).
So I think we are more seeing an ongoing convergence on combined static/dynamic typing approaches.
For C++ et al this is things like the 'auto' keyword that allow skipping the type specification where the compiler can work it out.
The end-game is specifying just enough type information that it's still understandable to both humans and compilers.
I firmly believe type inference is the future. Most objects in dynamic languages are statically typed anyways, the type just isn't exposed at compile time.
For instance, how often do you declare a variable as `int`, when what you really want to say is “an integer in the range 0...100”? A few languages (e.g. Eiffel) provide a formal mechanism for declaring these sorts of constraints, but most don’t, and you end up putting what should be declarative type-level information into the body of your code instead.
And then there’s “cutting-edge” stuff like dependent types, where you really want to express one argument’s type in terms of another argument’s, a classic example being an array indexing method, where you really want to declare the index at compile time as an integer in the range `0..<array.length`, and let the type system propagate that rule and its implications throughout the code that uses it. Whereas most “modern” languages chuck a run-time error if you’re lucky; or just ralph and dump stack like some antiquated 1970s throwback (yeah, looking at you, Apple’s Swift).
3/10 Could do much better.
This idea to "start with static types" presumes that there are no benefits to dynamically typed languages!
But the simplest argument is also the one I think that's the most true: dynamically typed languages let you explore the problem domain with faster feedback, leading to more successful software.
-----
Quote from John Ousterhout: The greatest performance improvement of all is when a system goes from not-working to working [1]
And we should never be cavalier about how hard that is. Most software projects fail; the successful ones are miracles!
I also take an expansive definition of "working" -- i.e. "useful to its users".
Also, TBH, types are kind of the wrong answer to the question. The question should be: How do we make the language efficiently expressive? For my money, that means defining constraints primarily on the code’s main interfaces, which a given constraint may be a combination of traditional generics, dependent types, and/or declarative run-time checks on input/output values. (e.g. Eiffel comes to mind.)
For instance, in my kiwi language I can define an argument as:
list (whole number (0, 100), 4, 4)
Which is to say a 4-item list, where each item is an integer between 0 and 100. And since I don’t want to type all that every time I can give that constraint a descriptive name: define type (CMYK color, list (whole number (0, 100), 4, 4))
I can even include user documentation in that definition so the whole lot’s self-describing.Kiwi’s very late-bound and interpreted, so its constraints are implemented solely as run-time coercions with optional bounds checks; nothing fancy. But the semantics are quite well formalized so a linter could be implemented as an assistant authoring tool for users, and if you can implement a linter then you can implement a type checker; and so on.
What matters is that the language has a formal mechanism by which it can guarantee that a given value will always satisfy one or more user-defined requirements; and whether those requirements are checked at compilation, execution, or some combination is secondary to that. How rigorous the user makes these declarations, and if/where she makes them, is entirely up to her.
Remember, the goal of any language is to please its users. Not the machines it runs on, nor the designers who created it. And users’ needs are not constant, not even across the development cycle of a single program, so a language that cannot adapt to those users’ changing requirements as it goes has already failed its first hurdle.
..
Perhaps if authors of existing languages like Python and C put less effort into chasing the constantly-diminishing returns of post-hoc micro-optimizations, and more into thinking how to design the next generation of languages so as to carry forward the good characteristics of their predecessors minus their original already-painted-themselves-into-a-corner limitations, we might actually have languages that tick all the boxes by now.
Even more applicable, in this case, would be Greenspun's tenth rule:
“Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp.”
More like, people whose hobby it is to create static front ends for dynamic languages will invariably invade the ecosystem of any sufficiently popular dynamic language.
(So not holding my breath.)
"With Unladen Swallow going the way of the Norwegian Blue..."
I love all this kind of thing that you don't get in commercial software.
- There are companies which would be willing to pay large amounts of money (more than he's asking for) for the speedups he's promising
- There are other similar efforts underway (such as ours) which have implemented some of his ideas and the gains are much smaller than he anticipates (for real code, microbenchmarks are another story)
Clearly it is the leader right now by a long way, but Julia is growing really fast.
Compile CPython in release mode, as most everyone uses the debug version.
Doing this gives a rough 10% performance increase.
https://www.python.org/dev/peps/pep-0620/#the-c-api-blocks-c...
This seems like the first step towards such improvements, but I guess recompiling libraries will be mandatory, and already existing wheels won't be valid for Python 3.10
Isn't this more scary than the Python 3 migration?
It's very hard to estimate how much speedup a JIT will get you on a dynamic language like python and x5 speedup seems unrealistic.
There are other lower hanging fruits, like optimizing core data structures (e.g: the implementation of python dicts) .
However, this speedup comes at the cost of being less dynamic. I'm not sure how much more optimized core python objects could be without sacrificing some of the dynamism some programs rely on. Python dicts are already pretty optimized as is.
YouTube also encountered the same problem. Their solution sounds kinda like "never use pickle, because it's slow. Use custom serialization".
https://github.com/markshannon/faster-cpython/blob/master/ti...
Still I'm a bit skeptical ... his reasons for why others failed and he will succeed is not that convincing.
You can have a 2x or 10x for many use cases speedup without a JIT, as PHP7 proved. You just need to start with a slow, not very optimized, implementation, which CPython pretty much is.
As for 5x, Javascript has had much more than speed bump than that with its JITs (compared to the interpreted Javascript pre-JSCore, Tracemonkey and V8 circa 1997-2005) and it's just as dynamic as Python...
>There are other lower hanging fruits, like optimizing core data structures (e.g: the implementation of python dicts).
Funny that you should mention it, because the author of the proposal has already done significant work (available since Python 3.3. or so) optimizing the dicts...
The fundamental issue is that python is a pointer machine: everything requires a dynamic lookup in memory.
Eg.,
x = [1, 2, 3]
len(x)
Here `x` is an actual string in memory which is a key in a locals() dictionary which holds values. (cf. with C where it is just a memory address).Likewise the list is a list of pointers (not a sequential array). And its heterogenous, ie., the contents can be of any type.
Likewise `len` is a string into a dictionary of functions which has to be looked up.
etc.
The whole thing is many levels of indirection. Applying an operation to a value (eg., even x + y) requires jumping around the memory of the machine many times.
This is necessary, in general, to deliver on the dynamic lang. features python provides.
Julia solves some of these issues by using static type information to ditch this dynamic behaviour. My suspicion is that python can follow a similar path (eg., above, x should be compiled to a static homogenous array of ints).
In general the point stands, the reason for slowness is indirection & reification. (Not sure why i'm downvoted).
len() only causes one dictionary lookup and then it's cached.
> This is necessary, in general, to deliver on the dynamic lang. features python provides.
It's the most obvious way to implement these features of dynamic languages but not at all necessary.
https://www.infoworld.com/article/2074780/avoiding-hash-look...
This is why PyPy reimplements standard library in RPython — so you can JIT-optimize it. But it feels like Mark Shannon knows nothing about these efforts — which is kinda strange considering his position of core CPython developer.
>There are other lower hanging fruits, like optimizing core data structures (e.g: the implementation of python dicts)
Unfortunately, you cannot easily implement efficient data containers without rewriting existent python code. The latter one relies heavily on dictionary-based access to pretty much everything, and you cannot easily convert "string hash" access into "record offset" access, because you cannot know a priori what object has what structure and converting hash into offset is basically the same dictionary lookup. For example:
a = A() a.field = varname + 1
What can you optimize here? What "varname" is? What A's structure is? Is "A" a class or a function? Not only you are unable tell the semantic of the code just by looking at the code — you can't even tell the semantic after you've examined the "A" and "varname" on some previous iteration, because somebody might've declared/modified those on outer scope or directly modified "A" or "varname".
Last year in my spare time I've been working on an unpublished library for python multitasking with shared memory structures (probably will make some blog post in few weeks and link it here), and I also encountered the problem of inherently inefficient implementation of python basic types. However, I'm yet to find the solution without breaking compatibility with existing code. For example, if you look at ctypes, they have some very efficient containers, but using them in a regular python code is a pain, and the c-python interface eats most performance benefits of efficient containers.
So what's really needed for optimization of python is some kind of python subset, like RPython but probably more human-friendly, so efficient containers can really become efficient while automatic optimizer can select or create automatically those efficient containers. Just like V8 JS engine does, which stores objects in records with static structure. It happens to works in JS for most cases. Countrary, in Python it does not work for most cases, that's why we have so much struggle optimizing the Python.
One thing I love about C and golang is how fast they make hardware feel. They can do much more with less hardware. I love writing Python, but it does feel a bit heavy. If every machine using Python required half as much hardware/power that would be amazing.
Which leads to an now deleted repo https://bitbucket.org/markshannon/hotpy_2
Searching for HotPy on HN yields another now 404 URL: http://www.dcs.gla.ac.uk/~marks/
https://wiki.python.org/moin/NeedForSpeed
Was just talking about that trip with my wife last night, in relation to COVID statistics. "Measuring things sucks! I'm still traumatized from spending a week trying to reliably measure speedups in Python."
1) the Python language was never made for speed of execution, it was made for speed of programming. It is an awesome language for prototyping, teaching and scripting
2) If you want speed, it likely means you are doing math operations, for example ML. In that case, you'd better learn to use state-of-the-art math libraries, made of decades of hardware and mathematical expert knowledge you will never beat.
It would serve you better to :
- learn about existing state-of-the-art compiled librairies and tools in your problem space
- learn to profile your Python code
Python is a great orchestrator to glue libraries and external systems together. If you are reinventing everything in Python, you are not solving actual problems. It's matter of using the right tool for the right task.
Python is a mean to achieve something greater, it's not an end by itself.
Only if you want to change the language itself, AFAIK. Optimisations that don’t change behaviour shouldn’t require a PEP.
Are any of those details published?
Why isn't this more known? Is it moving forward? Has it been announced officially...
Edit: This post wasn't supposed to be an hostile attack against anybody. Just found it odd to ask why an 2 hour old repo wasn't more known. I did not know that Mark Shannon is a core dev. Certainly did not pick that up from the repos contents.
In fact the post says: "The PSF seems to be the obvious organization to coordinate funding".
And no, I'm not Mark, somebody send me the link. Conspiracy theory much?
I like though, to see the JIT wave across interpreted languages (see Ruby). I'm also curious to see what's going to be the implementation.
It doesn't have to be this way though?
But, you don't get the JIT performance if you use the C extensions a lot, it's better for PyPy performance if you find a pure-python alternative for extensions used in "hot paths".
Yes!
But we aren't working with one of those.
I don't know the best route for python to get more performance but it probably involves allowing people to opt out of dynamism and basically doing what cython does but for all possible code.
Hasn't Strongtalk, Java, and JS put those concerns to sleep?
But just the most basic optimizations as done in PHP, a good lisp, scheme or lua would reach the 5x goal.
If there a precise difference between how Ignition and Turbofan interact that is not a tracing jit? I really thought both Java and JS implementations were tracing jits. Because if I'm going to whinge about technology I don't like I want to whinge about the precise problem. :)
Has that stopped HN before? We have devoted several top posts to the V language, LightTable, and several clearly doomed-to-fail efforts. This, in comparison is rather tame, from a legitimate source, and even if it just gives 2x performance, that will still be something.
PHP, for one, pulled it off, with PHP7.
where the code starts executing without JIT, while another thread is instantiated that is doing JITting in the background, and as soon as the JITting is done, JITted version takes over seamlessly for the rest of the execution.
Some people say 'dynamic JIT' (for example HotSpot) as opposed to a 'static JIT' (for example .NET.)
My guess is that it will stay that way. That said, it is probably feasible to do some parts of it, similar to the 3x3 project Ruby has. I wouldn't be surprised if numpy glue is a lot more tractable for optimization than Rails, but the interaction with C libs would pose some serious issues.
IE if you want to store and mess about with some data, Python is fine. If you want to store and mess about with a lot of data, use pandas, numpy etc. etc.