The information below is relevant as of 2020, with PyPy 7.3.0. More information can be found on the performance page
The just in time compiler in PyPy is a complicated piece of software. It can run some programs very fast, while others it can't yet or it'll never be able to. This is a non-exhaustive list of good and bad practices when writing code targeting PyPy, so it'll run fast.
Table of Contents
Simple is better than complex. JIT is not very smart, you have to let it find out what your code does. The simpler your code is the better it'll run. Also is applies to using well-known, but unwritten coding practices. There are many optimizations that optimize common usage pattern against an uncommon usage pattern.
Attribute access for new style classes is very fast if attribute names are constant. For example:
will be much faster than a dynamic version:
New style classes are faster than old style classes. Both are much faster than classes that inherit from both new and oldstyle classes. Avoid the latter at all cost.
The simpler your calls are the better. Passing normal arguments should not pose big performance hit, while always passing a very general and might. The actual depth of the call stack does not matter that much, since a lot of it might end up being inlined and generating no extra code at all.
, work, but they give performance penalty that can be huge, by disabling the JIT. Use only for specialized use cases (like a debugger) where performance does not matter.
One unobvious usecase where this is used is the logging module. Don't use logging module if you want things to be fast.
As of now (Apr 2020) generators are usually slower than corresponding code not using generators. Same goes for generator expressions. For most cases using list comprehension is faster.
If you are creating a list on-the-fly (for-loop that appends to a list, then join everything at the end), then creating a object and appending to it is faster than in PyPy.
disables the JIT.