PyPy is almost a drop-in replacement for Python. Only extensions modules written in C can be a problem — for instance the
Thus I wrote a pure Python fallback for the msgpack module, which has been merged upstream. As a rough benchmark, I measured the pack and unpack time of a 30MB msgpack-object from the wild.
- The execution times for the fallback with normal Python are all off the chart (15.4s, 15.1s, 10.1s, 10.0s).
- PyPy is faster on the second run: it had time to trace and optimize at runtime.
- For packing, PyPy (nightly) is almost as fast as the original C extension.
- For unpacking, PyPy (nightly) is 340% slower. Yet it more than 10 times faster than normal Python.
This code should be in the next official release of
msgpack-python. If you want to use it already, check out the git repository.
The first version did not run this fast on PyPy. With PyPy’s jitviewer the compiled code and assumptions of PyPy can be examined. These are the tweaks I used in descending order of impact.
- Instead of
StringIO, the fallback uses PyPy’s own
StringIOallows writing, reading, seeking and what more.
StringBuilderonly allows appending and thus it is easier for PyPy to optimize. This increased performance of packing by an order of magnitude.
- Using constant format strings for
struct.unpackallows PyPy to optimize it. Thus
struct.unpack("I", f.read(4)); f.read(n)instead of
stream.write(a); stream.write(b)instead of
- Adding an explicit fastpath
- PyPy usually specializes a function well for its most common path, however in some cases it needs help. In this case a function returns a concatenation of several strings. However: in the most common case it only does one:
ret=''; ret+=something; return ret. PyPy does not recognize that
retis not needed at all in this case, so I added an if-statement before initializing it for the case where there is only one concatenation.
Clearly the unpacking code could be faster, if anyone with expertise on PyPy’s JIT would look at it, that would be great.