Pyjion – A Python JIT Compiler
trypyjion.com
trypyjion.com
But paradoxically, this veneer of simplicity hides an incredible amount of complexity. You have to read hundreds of lines of CPython to understand the full semantics of a statement like "a + b". The semantics are complicated/compromised by many optimizations and implementation details of CPython. This is a fantastic talk that goes into these details: https://youtu.be/qCGofLIzX6g
It is not good for anybody that the semantics are this quirky. But it's especially not good for people trying to optimize the language, who basically have to implement quirk-for-quirk identical semantics to what CPython has ended up with after 30 years of evolution and optimization.
I wish Python 3.0 had included a more formalized and cleaned up set of semantics. These things can't be simplified without a breaking change to the language (even if only a small number of programs truly depend on these quirks). I would say Python should fix this in 4.0, but the 2->3 transition was so painful and long that I'm not sure the ecosystem can take it.
For example people joke about JavaScript comparison semantics posting things like this https://i.stack.imgur.com/35MpY.png and laughing about it. But I wish Ruby and Python were this explicit and easy to understand!
https://docs.python.org/3/reference/executionmodel.html
https://docs.python.org/3/reference/datamodel.html
Reading these documents is not hard, and it’s been an enormous help over the years for writing effective efficient Python.
Something formal, so I can actually reason about it and test it.
These documents are just informal prose. Are they sound? I don't know. Do you? Does anyone? Does my implementation match what they say? Who knows. Does CPython even match it? Does anyone know?
I don't think that's really true. The Java language specification is entirely prose. The book you linked to was written by "outsider" authors and published in 1999 (!).
https://fsl.cs.illinois.edu/publications/bogdanas-rosu-2015-...
> These documents are just informal prose.
Not true. They're the language spec. Every guaranteed behavior of python is described clearly and concretely in these documents.
> Are they sound?
Yes.
> Does my implementation match what they say?
Yes.
> Does CPython even match it?
Yes.
> Does anyone know?
Yes! There's a very rigorous and thorough set of unit tests that specifically test an implementation's ability to match precisely the behavior described in these documents. All implementations (that I'm aware of, eg. cpython, pypy, jython, etc) state which versions of the spec they are compatible with, in other words they pass the unit test suite for that version.
Further, the maintainers of python (and by that I mean, regular contributors to the python-dev mailing list, not a cabal of robed individuals in a cave somewhere) are deeply aware of the language of the spec, the way the test suite implements it, and the importance of maintaining this relationship.
> Yes.
That's great! Can you point me at the formal proof? I haven't seen it myself.
> There's a very rigorous and thorough set of unit tests that specifically test an implementation's ability to match precisely the behavior described in these documents.
How can you test against English prose? You can't. So someone's manually translated the prose into tests elsewhere I guess. Have they done that correctly? How can we verify that? Was there any ambiguity when they were interpreting the English?
It's easy to see where these simple English descriptions aren't covering everything. To give you a practical example - look at https://docs.python.org/3/reference/datamodel.html#object.__... - 'should return False or True' - what if it doesn't? Where's that specified? Is it somewhere else in this document? That's the kind of practical issue we work with when implementing languages.
Can you point me at the proof for the soundness of the documented behavior java or javascript or C++ docs? A cute little table isn't a substitute for soundness, and none of the languages you mentioned are mores soundly implemented (at least in their popular implementations).
> It's easy to see where these simple English descriptions aren't covering everything. To give you a practical example - look at https://docs.python.org/3/reference/datamodel.html#object.__... - 'should return False or True' - what if it doesn't? Where's that specified? Is it somewhere else in this document? That's the kind of practical issue we work with when implementing languages.
Cpython raises an exception, and in general, cpython is the spec unless otherwise specified is how things turn out.
> How can you test against English prose? You can't. So someone's manually translated the prose into tests elsewhere I guess. Have they done that correctly? How can we verify that? Was there any ambiguity when they were interpreting the English?
This is sort of a silly complaint. every spec is implemented in english prose[1]. That's why we end up with arguments about SHALL vs. MUST in the specs. Except in the rare cases where the spec is a test suite, which usually reduces to the case of cpython: the popular implementation is the spec (or maybe the popular implementation forks its test suite out into a different repo to make it more "independent")
[1]: Please don't make a irrelevant point about an obscure implementation of C that's implemented in agda and the "spec" is the proof of soundness or whatever, that's fundamentally the same as the implementation is the spec, especially given that said C implementation probably isn't ANSI compliant or whatnot.
https://link.springer.com/book/10.1007/3-540-48737-9
Java does have a formal semantics, with a whole chapter on its soundness.
> in general, cpython is the spec
Well there we go - turns out the written document doesn't cover everything after all. A second ago we were at 'Every guaranteed behavior of python is described clearly and concretely in these documents.' Turns out not.
I'm not criticising Python as being exceptionally bad, but we can certainly do it much better.
No, there's a chapter on the soundness of its type system. The spec being sound and the type system being sound are very different things. If we consider typescript to be JS's type system, then JS's type system is unsound. If we consider cpython in isolation, under the definition you're using, cpython cannot be unsound, as it is untyped, QED.
If you're talking about whether the language's type system is sound, asking "These documents are just informal prose. Are they sound?" isn't even a well defined question.
> I'm not criticising Python as being exceptionally bad, but we can certainly do it much better.
You absolutely were when you said "But I wish Ruby and Python were this explicit and easy to understand!"
> For instance, to evaluate the expression x + y, where x is an instance of a class that has an __add__() method, x.__add__(y) is called.
The talk I linked to (https://youtu.be/qCGofLIzX6g) is a deep dive on how there is much more to the story than the simplified statement above.
What I wish for is not better docs, but rather simpler language in which the statement above would actually be an accurate specification of the behavior implemented by the interpreter.
https://www.python.org/dev/peps/pep-0263/
Names aren't unique.
> Do you know that there are some behavioral limitations of dict that arise from a specific optimization in the implementation in C of dictionary iterators?
I'm rather curious what you're referring to here, do you mean dict-ordering, or something else?
I can tell you it has to do with iterators, and maybe someone will comment on what that is.
Besides the nuances of how everything is executed, there are occasionally breaking changes between minor language revisions.
You cannot just pick up a random Python script and be sure it still behaves the same way across all minor revisions.
> I can tell you it has to do with iterators, and maybe someone will comment on what that is.
This sounds like you don't know what it is.
I think the GP means character encodings.
So the intent here is to ask whether (for example) a foo defined in a ISO 8859-1 encoded file is overidden by a foo imported from a Windows-1256 encoded file.
Then the link I posted answers their question. Names aren't unique in how encodings are handled. Files are decoded and canonicalized as a unicode string. If the identifiers are the same after canonicalization, then yes they are equivalent. If they aren't, then they aren't.
I really recommend the talk I linked (https://youtu.be/qCGofLIzX6g), it gets deep into this stuff. I don't think anyone benefits from "a + b" having so many special cases.
> PyPy is an implementation of Python with its own JIT. The biggest difference compared to Pyjion is that PyPy doesn't support all C extension modules without modification unless they use CFFI or work with the select subset of CPython's C API that PyPy does support. Pyjion also aims to support many JIT compilers while PyPy only supports their custom JIT compiler.
Apparently it's originally a Microsoft project, and it requires .NET. The project already exists for a few years, but it looks like they've gained net performance improvements over CPython only the past few months [2].
Besides not every PE instance is a Futurama Projection.
1. RPython + the PyPy _compiler_ which is a compiler for JIT compilers (like GraalVM as I understand it)
2. An implementation of the Python language _using_ RPython to produce a JIT for Python scripts.
There are other languages _using_ RPython + PyPy compiler to produce JIT compilers for languages other than Python too.
The first Futaruma Projection would be if PyPy would specialize the interpreter based on the python program yielding an executable.
> The point of the first futurama projection
...was to delight viewers with what would turn out to be a wonderful pilot episode?
Used across Java implementations, and modern Android.
The extremly cool and awesome thing about this is that this is effectively a general purpsoe JIT, one JIT to rule all interpreted languages that could ever be written. There is nothing specific about Python in the toolchain. For _Any_ interpreted language:
- You write only your naive-but-readable interpreter in Rpython, a restricted subset of python that tries to preserve the readability but ditch the dynamic madness. (This is not python, this is an entirely different language. It just happens that every valid Rpython program is also a valid Python program. There is nothing special about Rpython here either, they could have theoretically picked any readable language to write your naive interpreter in, but they chose Rpython)
- The compilation pipeline produces two things: an exectuable image of your naive interpreter*, and a bytecode image for the general-purpose JITer.
- Normally, it's the executable image of your interpreter that runs your language's programs, but once it detects a user-program-level loop (e.g. because it has encountered a backward jump.), it invokes the supporting runtime (the general-purpose JITer) and delegates to the bytecode version of itself.
- The GP JITer starts tracing the bytecode image of your interpreter (which, remember, is itself executing the user-level program the whole time), once it detects that the user-level loop is done, it says so. Now the general-purpsoe JITer has a record of all the operations that your interpreter executed while it was running the user-level path, which is the same as {all the operations that the user-level path executed} (minus all the interpreter-specific operations, which the GP JITer also knows about because this info is contained in the bytecode)
- The GP JITer treats the execution record as any other JIT, it produces an optimised native version from it, and bingo!, you got yourself a native image of that user-level loop.
- The original interpreter, the executable, now goes back into the picture. It puts that native version of the loop in its pocket, ready for the next time it encounteres the loop.
It's so f*ing cool, that's why their logo is a snake eating itself: there's so much meta shenanigans going on. Their implementation of Python is merely the application, it's the amazing toolchain they built to build it that is the real treasure.
*: One of the steps in creating the exectuable is, I kid you not, is running the standard Cpython interpreter on your Rpython source (as it's valid python), waiting for interpreter to do it's expensive startup, then freezing the whole enviroment it produced to package it with the executable. This couldn't be done to speed up normal Python because its extremly dynamic nature messes with this.*
This gist talks specifically about PyPy using the Futamara projection.
For a better overview on what the projections are, please see http://blog.sigfpe.com/2009/05/three-projections-of-doctor-f... , or the original paper by Futamura.
EDIT: Yes, indeed pretty much the same factor, see e.g. https://web.archive.org/web/20090324020143/http://www.codepl...
Ruby' drop in YJIT barely managed ~20% speed up.
I'd give these guys some more time, it's a long project. They're working hard.
I don't know what they calculated, but if I use the numbers given in the table of https://speed.yjit.org/benchmarks/bench-2021-11-04-071006 I get a factor 1.9 which is much better than the reported 27%.
> Goal #1 is explicitly to add a C API to CPython to support JIT compilers.
Given the plural compilers, hopefully this means it's going to support multiple JITs, which would be interesting. Choose the right JIT for your particular workload.
https://www.python.org/dev/peps/pep-0523/
As the PEP notes, this is useful for a lot more than just JIT. In particular, modern Python debuggers use it to dynamically patch bytecode to make breakpoints more efficient.
.NET 6 Release: 19 hours ago (https://github.com/dotnet/core/blob/main/release-notes/6.0/6...)
... ok.
https://docs.microsoft.com/en-us/dotnet/core/install/linux-f...
> The latest version of .NET that's available in the default package repositories for Fedora is .NET 5. Installing .NET 6 through the default package repositories is coming soon. For now, you'll need to install .NET 6 in one of the following ways:
> Install the .NET SDK or the .NET Runtime with Snap.
> Install the .NET SDK or the .NET Runtime with a script.
> Install the .NET SDK or the .NET Runtime manually.
Nope, thank you
some people are helpless without their package manager and the maintainers that tell them what software that can run
these days, just spin up some disposable container/vm and tinker...
Seems like a problem with however your system distributes software, not with Pyjion.
What does it take 18 months to do?
I wasn't criticizing Pyjion, I was responding to coldtea's comment saying that people who can't run this in production would likely not be interested in trying it. I have zero issue with Pyjion, and I'm also happy with my production setup as is.
And why wouldn't you want to try this in production in the first place?
HN discussion on the repo from which this is forked: "Pyjion – A JIT for Python based upon CoreCLR" https://news.ycombinator.com/item?id=10984514 (Jan 28, 2016 | 23 comments)
> Pyjion does not currently support async..await (YIELD_FROM) statements.
Those are some major limitations for modern Python code.
How about Numpy performance?
So why do not they warm the JIT up first? Moreover the graph does not contain any notion of mean/max values at all. Any idea where I can see a more comprehensive benchmark?
This is on the roadmap (and related to try..except).
=====
I thin with block is used all of the places, so this limitation make it not usable to a lot of projects
Pyjion – A JIT for Python based upon CoreCLR - https://news.ycombinator.com/item?id=10984514 - Jan 2016 (23 comments)
It would be better to compare this project to, in addition to CPython: PyPy, GraalPython, Pyston, Cinder, Jython, and IronPython. As well as the now-inactive Psyco project, and possibly also Stackless Python.
> Psyco was a module that monkeypatched CPython to add a custom JIT compiler. Pyjion wants to introduce a proper C API for adding a JIT compiler to CPython instead of monkeypatching it. It should be noted the creator of Psyco went on to be one of the co-founders of PyPy.
> IronPython is an implementation of Python that is implemented using .NET. While IronPython tries to be usable from within .NET, Pyjion does not have a compatibility story with .NET. This also means IronPython cannot use C extension modules while Pyjion can.
It's very fast, it compiles to C code, then compiles the C code with a normal compiler.
https://github.com/jameskmurphy/nes/tree/main/nes/cycore
It's more like a DSL for an FFI.
I think it's far more accurate to say that Cython is a programming language of its own that is a hybrid of Python and C++, that happens to produce CPython extension modules when compiled.
The performance benefits are really achieved by incrementally changing your Python code to something that looks a lot more like C(++).
This is also reflected in the Cython documentation which literally mentions the "Cython language".
Cython is great as an alternative to writing C extension modules for performance reasons or to creating bindings to libraries written in C or C++. It's not so great just to make Python applications faster as it's not fully compatible[1].
[1]: https://cython.readthedocs.io/en/latest/src/userguide/limita...
I completely agree. Cython can become quite attractive if your alternative is "write a python extension library by hand in C / C++". I first started using Cython after doing exactly that, writing my extension library in C, then realising that Cython might save a lot of work in generating the bindings and packaging/distribution -- it did, and it ran at exactly the same speed as my pure C library with hand crafted python bindings. After that I've been pretty excited about Cython.
If you've got a python program that needs to do a core of compute-heavy work, if you were to optimise this by writing a C / C++ library for python to use, the work would be: (i) think hard about how the library design will enable performance, (ii) implement that high performance library in C / C++ , (iii) figure out the interface so that python can call into the library, and (iv) figure out how to package and distribute the library so it can be used by python programs.
Cython doesn't really help with parts (i) designing for performance or (ii) writing that high performance code. But it helps a lot with parts (iii) and (iv), generating Python bindings and producing wheel archives that can be managed by existing python package management tooling.
I've seen 2x improvement on string processing code (cleaning text for input to a ML model) by doing nothing other than sticking `%%cython` at the top of a notebook cell.
I don't know if this technique scales to other applications; probably not. But Cython syntactically is a superset of Python.
I loved it, being able to float between Python and C in a program, writing in Python whatever was more convenient in Python, in C whatever was more convenient in C, or anywhere in between.
"While pure Python scripts can be compiled with Cython, it usually results only in a speed gain of about 20%-50%."
https://cython.readthedocs.io/en/latest/src/tutorial/pure.ht...
So if you change some of your Cython code, for it to be used at runtime you need to invoke the Cython build tools to rebuild the new version of your python native module.
I usually use Cython for a small core of compute heavy operations, and leave the rest of the project as pure python. That way I only need to rebuild Cython code if I change something inside that small core.
> Is it 1-to-1 with standard Python?
Not for the best speedups, no.
E.g. you might be able to get a modest speedup, say 50%, taking a pure python file, renaming it to *.pyx, and getting Cython to compile it. But that's not why I use Cython. I use it when I have compute-heavy code that I want to run at native speed (think matrix-vector product type stuff), by carefully rewriting in Cython, thinking carefully about memory allocation, data structures (prefer C arrays!) and performance, it is fairly achievable to get a 500x speedup.
Cython relies on you writing specialised Cython code that is quite close to C code -- strongly typed Cython variables work like statically typed C variables, not dynamically typed Python names. You end up with Cython code that cannot be executed as if it were normal Python code by a python interpreter.
But, Python code can usually not be executed very efficiently, whereas Cython can translate small loops of strongly-typed numeric code into small loops of strongly-typed C code, which can often compile to very small loops of native CPU instructions, which then run blazing fast.
Under the hood, Cython works by translating the not-quite-python code into C code that uses the python interpreter's C extension API. Then it compiles the C code into a python native module using a C compiler.
But it's also not the same thing - AOT doesn't always mesh well with how dynamic Python is, but JIT can take care of all the more advanced scenarios with runtime-loaded or runtime-generated types and code.