|
|
Subscribe / Log in / New account

A brief experiment with PyPy

By Jonathan Corbet
May 11, 2011
While one might ordinarily think of the PyPy project as an experiment in implementing the Python runtime in Python itself, there is really more to it than that. PyPy is, in a sense, a toolbox for the creation of just-in-time compilers for dynamic languages; Python is just the start - but it's an interesting start. It has been almost exactly one year since LWN first looked at PyPy and a few weeks since the 1.5 release, so the time seemed right to actually play with this tool a bit. The results were somewhat eye-opening.

LWN uses a lot of tools written in Python; one of them is the gitdm data miner which is used to generate kernel development statistics. It is a simple program which reads the output of "git log" and generates a big in-memory data structure reflecting the relationships between developers, their employers, and the patches they are somehow associated with. There is very little that is done in the kernel, and there is no use of extension modules written in C. These features make gitdm a natural first test for PyPy; there is little to trip things up.

The test was to stash the git log output from the 2.6.36 kernel release through the present - some 31,000 changes - in a file on a local SSD. The file, while large, should still fit in memory with nothing else running; I/O effects should, thus, not figure into the results. Gitdm was run on the file using both the CPython 2.7.1 interpreter and PyPy 1.5.

When switching to an entirely different runtime for a non-trivial program, it is natural to expect at least one glitch. In this case, there were none; gitdm ran without complaint and produced identical output. There was one significant difference, though: while the CPython runs took an average of about 63 seconds, the PyPy runs completed in about 21 seconds. In other words, for the cost of changing the "#!" line at the top of the program, the run time was cut to one third of its previous value. One might conclude that the effort was justified; plans are to run gitdm under PyPy from here on out.

To dig just a little deeper, the perf tool was used to generate a few statistics of the differing runs:

CPythonPyPy
Cycles124B 42B
Cache misses14M 45M
Syscalls55,000 28,000

As would be expected from the previous result, running with CPython took about three times as many processor cycles as running with PyPy. On the other hand, CPython reliably incurred less than 1/3 as many cache misses; it would be hard to say why. Somehow, the code generated by the PyPy JIT generates more widely spread-out memory references; that may be related to garbage collection strategies. CPython uses reference counting, which can improve cache locality, while PyPy does not.

One other interesting thing to note is that PyPy only made half as many system calls. That called for some investigation. Since gitdm is just reading data and cranking on it, almost every system call it makes is read(). Sure enough, the CPython runtime was issuing twice as many read() calls. Understanding why would require digging into the code; it could be as simple as PyPy using larger buffers in its file I/O implementation.

Given results like this, one might well wonder why PyPy is not much more widely used. There may be numerous reasons, including a simple lack of awareness of PyPy among Python developers and users of their programs. But the biggest issue may be extension modules. Most non-trivial Python programs will use one or more modules which have been written in C for performance reasons, or because it's simply not possible to provide the required functionality in pure Python. These modules do not just move over to PyPy the way Python code does. There is a short list of modules supported by PyPy, but it's insufficient for many programs.

Fixing this problem would seem to be one of the most urgent tasks for the PyPy developers if they want to increase their user base. In other ways, PyPy is ready for prime time; it implements the (Python 2.x) language faithfully, and it is fast. With better support for extensions, PyPy could easily become the interpreter of choice for a lot of Python programs. It is a nice piece of work.


to post comments

A brief experiment with PyPy

Posted May 12, 2011 1:00 UTC (Thu) by paravoid (subscriber, #32869) [Link]

Well, the fact that PyPy is not packaged and included e.g. in Debian or Ubuntu is an important factor as well...

A brief experiment with PyPy

Posted May 12, 2011 1:15 UTC (Thu) by andresfreund (subscriber, #69562) [Link] (1 responses)

One reason for the higher amount of cache misses might simply be that a tigher execution schedule makes it harder for the prefetching units to load all the data in advance.

A brief experiment with PyPy

Posted May 12, 2011 1:42 UTC (Thu) by jzbiciak (guest, #5246) [Link]

Or freeing dead objects less aggressively. I'm willing to bet that there's a lot of temporary objects that get recycled in CPython, but don't get reaped as quickly in the PyPy version.

45M cache misses with a 64 byte line size is ~2.8GB of RAM... that's a lot of RAM to cycle through!

A brief experiment with PyPy

Posted May 12, 2011 2:58 UTC (Thu) by elanthis (guest, #6227) [Link] (1 responses)

On reason less people are using it in my field (games) is because PyPy's embedding API is not super usable yet. It needs more work in that area. Preferably it should be even better than CPython's, which is so-so.

A brief experiment with PyPy

Posted May 12, 2011 9:46 UTC (Thu) by dgm (subscriber, #49227) [Link]

I was under the impression (from my first Python tests, a few years ago) that embedding was one of Phyton's strengths. How came that it's now so-so? Can you elaborate a bit more?

A brief experiment with PyPy

Posted May 12, 2011 6:17 UTC (Thu) by grahame (guest, #5823) [Link] (1 responses)

I've moved to Python 3 for all my projects, unfortunately pypy support for Python 3 doesn't exist (at least in the stable releases). Modules built with cython (such as the excellent LXML bindings to libxml2) don't work with pypy. I guess it's just hard to make sure all your deps will work in the pypy world.

A brief experiment with PyPy

Posted May 12, 2011 8:55 UTC (Thu) by bboissin (subscriber, #29506) [Link]

There are still many more programs and library working with python2.x than with python3.x. So in my opinion pypy is better positioned there than cpython (it even removes some incentives to switch, since unladen swallow was stopped).

A brief experiment with PyPy

Posted May 12, 2011 7:47 UTC (Thu) by Da_Blitz (guest, #50583) [Link]

Most of the standard library is importable and should work without issue (if you find one that doesn't work file a bug :))

There is a compatibility page on the bitbucket wiki: https://bitbucket.org/pypy/compatibility/wiki/Home

the programs listed as not working there may be out of date so it is recommended to try them out and report if they work so the list can be updated

A brief experiment with PyPy

Posted May 12, 2011 8:05 UTC (Thu) by ernstp (guest, #13694) [Link]

I've written an a-star pathfinding module in Python and tried it with PyPy.
PyPy is 3-4 times faster! Also, I saw that 32-bit PyPy is about 30% faster than 64-bit PyPy. PyPy uses a bit more memory but not that much!

Python 2.7:

0.00002927894592285156

23.77user 0.00system 0:23.79elapsed 99%CPU (0avgtext+0avgdata 63472maxresident)k
0inputs+0outputs (0major+4375minor)pagefaults 0swaps

32-bit PyPy 15:

0.00000724706649780273

8.01user 0.05system 0:08.08elapsed 99%CPU (0avgtext+0avgdata 80064maxresident)k
0inputs+0outputs (0major+5578minor)pagefaults 0swaps

C extensions

Posted May 16, 2011 1:35 UTC (Mon) by kingdon (guest, #4526) [Link] (3 responses)

As for the C extensions, sounds like Python needs an equivalent to the ruby FFI, which enables ruby code to talk to a C library without any close ties to the internals of the ruby implementation. See https://github.com/ffi/ffi/wiki/

C extensions

Posted May 16, 2011 2:54 UTC (Mon) by njs (subscriber, #40338) [Link] (2 responses)

Python has that -- 'ctypes', in the standard library since 2006 -- but that doesn't mean that all existing code uses it. (Nor is it necessarily a great match for extensions that are extending the interpreter in low-level ways, as opposed to just wrapping an existing C library.)

C extensions

Posted May 16, 2011 18:33 UTC (Mon) by foom (subscriber, #14868) [Link] (1 responses)

ctypes is not a great way of wrapping a C library either, since it doesn't give you access to the header files, which you need to use if you want portability.

Redefining all the structs/#defines/etc manually in ctypes is a great way to make a completely unportable library wrapper.

C extensions

Posted May 16, 2011 18:54 UTC (Mon) by njs (subscriber, #40338) [Link]

Yeah, it works well for some simpler cases, but that's why I use cython :-).

(Actually, I think most of the times I've used ctypes were to commit horrors by poking at the innards of the interpreter -- casting id(myobj) to a pointer and then screwing with C-level fields. It's not easy to guarantee portability between different implementations of a language!)

A brief experiment with PyPy

Posted May 17, 2011 8:52 UTC (Tue) by ssam (guest, #46587) [Link]

the gtk folk are moving to using introspection to generate bindings for c libraries for python. could this be used to beef up library support for pypy?


Copyright © 2011, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds