PYPY3(1)
๐ PYPY3(1) ๐ โ straight outta the PyPy manual
โจ NAME โจ
pypy3 โ it's the fast, compliant alternative implementation of the Python 3 language bestie, no cap ๐
๐ธ SYNOPSIS ๐ธ
pypy3 [options] [-c cmd|-m mod|file.py|-] [arg...]
๐ OPTIONS ๐
kay so here's the full flag menu, and yes you can stack them, we love that for you โจ
-iโ inspect interactively after running the script, so you can poke around in the wreckage ๐ซถ-Oโ skipassertstatements, they simply do not exist today-OOโ everything-Odoes PLUS removes docstrings when importing modules (double O energy, double the stripping)-c CMDโ program passed in asCMD; heads up bestie, this terminates the option list, periodt-Sโ do not import site on initialization-sโ don't add the user site directory tosys.path-uโ unbuffered binarystdoutandstderr-h,--helpโ shows a help message and exits, that's the whole vibe-m MODโ library module to be run as a script; also terminates the option list ๐-W ARGโ warning control, whereargisaction:message:category:module:lineno-Eโ ignore environment variables (such asPYTHONPATH), iconic fresh-start behaviour-Bโ disable writing bytecode (.pyc) files-X track-resourcesโ produces aResourceWarningwhenever a file or socket is closed by the garbage collector ๐ฅ--versionโ print the PyPy version--infoโ print translation information about this PyPy executable--jit ARGโ low level JIT parameters, mostly internal stuff; run--jit helpfor more info if you're curious ๐
๐ ENVIRONMENT ๐
the vibes you can set before launching, ok:
PYTHONPATHโ add directories topypy3's module search path. The format is the same as shell'sPATH.PYTHONSTARTUPโ a script referenced by this variable gets executed before the first prompt is displayed, in interactive mode. lil pre-show moment โจPYTHONDONTWRITEBYTECODEโ if set to a non-empty value, equivalent to the-Boption. Disables writing.pycfiles.PYTHONINSPECTโ if set to a non-empty value, equivalent to the-ioption. Inspects interactively after running the specified script.PYTHONIOENCODINGโ if this is set, it overrides the encoding used forstdin/stdout/stderr. The syntax isencodingname:errorhandler. Theerrorhandlerpart is optional and has the same meaning as instr.encode.PYTHONNOUSERSITEโ if set to a non-empty value, equivalent to the-soption. Don't add the user site directory tosys.path.PYTHONWARNINGSโ if set, equivalent to the-Woption (warning control). The value should be a comma-separated list of-Wparameters.PYPYLOGโ if set to a non-empty value, enables logging. The format is:fnameor+fnameโ logging for profiling: includes alldebug_start/debug_stopbut not any nesteddebug_print.fnamecan be-to log tostderr. The+fnameform can be used if there is a:infname.:fnameโ full logging, includingdebug_print.prefix:fnameโ conditional logging. Multiple prefixes can be specified, comma-separated. Only sections whose name matches the prefix will be logged.
so like, PYPYLOG=jit-log-opt,jit-backend:logfile will generate a log suitable for jitviewer, a tool for debugging performance issues under PyPy ๐ฆ
PYPY_IRC_TOPICโ if set to a non-empty value, prints a random#pypyIRC topic at startup of interactive mode. silly lil gremlin behaviour, love it ๐
okay so now we're getting into the garbage collector lore and honestly it's giving main character energy, stay with me ๐ง
PyPy's default garbage collector is called incminimark โ it's an incremental, generational moving collector. Here we hope to explain a bit how it works and how it can be tuned to suit the workload.
Incminimark first allocates objects in what's called the nursery โ a place for young objects, where allocation is very cheap, being just a pointer bump. The nursery size is a very crucial variable โ depending on your workload (one or many processes) and cache sizes you might want to experiment with it via the PYPY_GC_NURSERY environment variable. When the nursery is full, a minor collection is performed. Freed objects are no longer referenceable and just die, just by not being referenced any more; on the other hand, objects found to still be alive must survive and are copied from the nursery to the old generation. Either to arenas, which are collections of objects of the same size, or directly allocated with malloc if they're larger. (A third category, the very large objects, are initially allocated outside the nursery and never move.)
Since Incminimark is an incremental GC, the major collection is incremental: the goal is not to have any pause longer than 1ms, but in practice it depends on the size and characteristics of the heap: occasionally, there can be pauses between 10-100ms. ๐
๐ซถ Semi-manual GC management ๐ซถ
If there are parts of the program where it's important to have a low latency, you might want to control precisely when the GC runs, to avoid unexpected pauses. Note that this has effect only on major collections, while minor collections continue to work as usual. so like, minor collections are doing their own thing, we don't touch them.
As explained above, a full major collection consists of N steps, where N depends on the size of the heap; generally speaking, it is not possible to predict how many steps will be needed to complete a collection.
gc.enable() and gc.disable() control whether the GC runs collection steps automatically. When the GC is disabled the memory usage will grow indefinitely, unless you manually call gc.collect() and gc.collect_step(). ๐ฅ
gc.collect() runs a full major collection.
gc.collect_step() runs a single collection step. It returns an object of type GcCollectStepStats, the same which is passed to the corresponding GC Hooks. The following code is roughly equivalent to a gc.collect():
while True:
if gc.collect_step().major_is_done:
break
For a real-world example of usage of this API, you can look at the 3rd-party module pypytools.gc.custom <https://github.com/antocuni/pypytools/blob/master/pypytools/gc/custom.py>, which also provides a with customgc.nogc() context manager to mark sections where the GC is forbidden. iconic ๐
๐ช๏ธ Fragmentation ๐ช๏ธ
Before we discuss issues of "fragmentation", we need a bit of precision. There are two kinds of related but distinct issues:
- If the program allocates a lot of memory, and then frees it all by dropping all references to it, then we might expect to see the RSS to drop. (RSS = Resident Set Size on Linux, as seen by "top"; it is an approximation of the actual memory usage from the OS's point of view.) This might not occur: the RSS may remain at its highest value. This issue is more precisely caused by the process not returning "free" memory to the OS. We call this case "unreturned memory".
- After doing the above, if the RSS didn't go down, then at least future allocations should not cause the RSS to grow more. That is, the process should reuse unreturned memory as long as it has got some left. If this does not occur, the RSS grows even larger and we have real fragmentation issues. ๐ซ
๐ gc.get_stats ๐
There is a special function in the gc module called get_stats(memory_pressure=False).
memory_pressure controls whether or not to report memory pressure from objects allocated outside of the GC, which requires walking the entire heap, so it's disabled by default due to its cost. Enable it when debugging mysterious memory disappearance. ๐ป
Example call looks like that:
>>> gc.get_stats(True)
Total memory consumed:
GC used: 4.2MB (peak: 4.2MB)
in arenas: 763.7kB
rawmalloced: 383.1kB
nursery: 3.1MB
raw assembler used: 0.0kB
memory pressure: 0.0kB
-----------------------------
Total: 4.2MB
Total memory allocated:
GC allocated: 4.5MB (peak: 4.5MB)
in arenas: 763.7kB
rawmalloced: 383.1kB
nursery: 3.1MB
raw assembler allocated: 0.0kB
memory pressure: 0.0kB
-----------------------------
Total: 4.5MB
In this particular case, which is just at startup, GC consumes relatively little memory and there is even less unused, but allocated memory. In case there is a lot of unreturned memory or actual fragmentation, the "allocated" can be much higher than "used". Generally speaking, "peak" will more closely resemble the actual memory consumed as reported by RSS. Indeed, returning memory to the OS is a hard and not solved problem. In PyPy, it occurs only if an arena is entirely free โ a contiguous block of 64 pages of 4 or 8 KB each. It is also rare for the "rawmalloced" category, at least for common system implementations of malloc().
The details of various fields:
- GC in arenas โ small old objects held in arenas. If the amount "allocated" is much higher than the amount "used", we have unreturned memory. It is possible but unlikely that we have internal fragmentation here. However, this unreturned memory cannot be reused for any
malloc(), including the memory from the "rawmalloced" section. - GC rawmalloced โ large objects allocated with
malloc. This gives the current (first block of text) and peak (second block of text) memory allocated withmalloc(). The amount of unreturned memory or fragmentation caused bymalloc()cannot easily be reported. Usually you can guess there is some if the RSS is much larger than the total memory reported for "GC allocated", but do keep in mind that this total does not includemalloc'ed memory not known to PyPy's GC at all. If you guess there is some, consider usingjemalloc<http://jemalloc.net/> as opposed to systemmalloc. ๐ - nursery โ amount of memory allocated for nursery, fixed at startup, controlled via an environment variable
- raw assembler allocated โ amount of assembler memory that JIT feels responsible for
- memory pressure, if asked for โ amount of memory we think got allocated via external
malloc(eg loading cert store in SSL contexts) that is kept alive by GC objects, but not accounted in the GC
๐ฃ GC Hooks ๐ฃ
GC hooks are user-defined functions which are called whenever a specific GC event occur, and can be used to monitor GC activity and pauses. You can install the hooks by setting the following attributes:
gc.hooks.on_gc_minorโ called whenever a minor collection occurs. It corresponds togc-minorsections insidePYPYLOG.gc.hooks.on_gc_collect_stepโ called whenever an incremental step of a major collection occurs. It corresponds togc-collect-stepsections insidePYPYLOG.gc.hooks.on_gc_collectโ called after the last incremental step, when a major collection is fully done. It corresponds togc-collect-donesections insidePYPYLOG.
okay so plugging those hooks back OUT? literally just set that attribute to None and it's gone, no ceremony, bye bestie ๐
and if you want to install all of them at once, you call gc.hooks.set(obj), which goes looking for methods named on_gc_* on obj. Want them all gone at once? gc.hooks.reset(). boom. done. โจ
The functions your hooks call get handed one single argument called stats, and it's packed with all kinds of stats about the event that just went down. ๐
ngl there's a lil โจtiming situationโจ here: PyPy cannot call the hooks the instant a GC event happens, because it has to wait until it reaches a point where the interpreter is in a known state and running user-defined code is harmless. so it's givingโฆ delayed. Which means multiple events can pile up before your hook actually fires ๐ญ and when that happens, you gotta peek at stats.count to know how many times the event happened since the last time the hook was called. Same deal with stats.duration โ that's the total time the GC spent on this specific event since your last hook call. ๐ฐ๏ธ
but heads up โ every other field on the stats object is relative only to the last event of the series. only the last one!! not the pile. periodt.
๐ธ The attributes for GcMinorStats in the on_gc_minor hook ๐ธ
countโ the number of minor collections that happened since the last hook call. ๐ขdurationโ total time spent inside minor collections since the last hook call, in seconds. โฑ๏ธduration_minโ the duration of the fastest minor collection since the last hook call. ๐๏ธduration_maxโ the duration of the slowest minor collection since the last hook call. ๐ขtotal_memory_usedโ the amount of memory used at the end of the minor collection, in bytes. this includes the memory used in arenas (for GC-managed memory) and raw-malloced memory (e.g., the content of numpy arrays). ๐งpinned_objectsโ the number of pinned objects. ๐
๐ฆ The attributes for GcCollectStepStats in the on_gc_collect_step hook ๐ฆ
count,duration,duration_min,duration_maxโ see above, same energy, no notes. ๐oldstate,newstateโ integers that indicate the state of the GC before and after the step. ๐major_is_doneโ a boolean that indicates whether this was the last step of the major collection. โ
The value of oldstate and newstate is one of these constants, defined inside gc.GcCollectStepStats: STATE_SCANNING, STATE_MARKING, STATE_SWEEPING, STATE_FINALIZING, STATE_USERDEL. and yes you can get a string representation of it โ just index the GC_STATES tuple. ๐
๐ The attributes for GcCollectStats in the on_gc_collect hook ๐
countโ see above. ๐num_major_collectsโ the total number of major collections that have been done since the start. contrary tocount, this is an always-growing counter and it is not reset between invocations. ๐arenas_count_before,arenas_count_afterโ number of arenas used before and after the major collection. ๐๏ธarenas_bytesโ total number of bytes used by GC-managed objects. ๐พrawmalloc_bytes_before,rawmalloc_bytes_afterโ total number of bytes used by raw-malloced objects, before and after the major collection. ๐ฆpinned_objectsโ the number of pinned objects. ๐
not me warning you about the missing field ๐ญ โGcCollectStatsdoes not have adurationfield. this is because all the GC work is done insidegc-collect-step;gc-collect-doneis only used to give additional stats and doesn't do any actual work. it's the vibe reporter, not the worker. ๐
โจ here's an example of GC hooks in use, bestie โจ
import sys
import gc
class MyHooks(object):
done = False
def on_gc_minor(self, stats):
print 'gc-minor: count = %02d, duration = %d' % (stats.count,
stats.duration)
def on_gc_collect_step(self, stats):
old = gc.GcCollectStepStats.GC_STATES[stats.oldstate]
new = gc.GcCollectStepStats.GC_STATES[stats.newstate]
print 'gc-collect-step: %s --> %s' % (old, new)
print ' count = %02d, duration = %d' % (stats.count,
stats.duration)
def on_gc_collect(self, stats):
print 'gc-collect-done: count = %02d' % stats.count
self.done = True
hooks = MyHooks()
gc.hooks.set(hooks)
# simulate some GC activity
lst = []
while not hooks.done:
lst = [lst, 1, 2, 3]
๐ Environment variables ๐
PyPy's default incminimark garbage collector is configurable through a whole lil squad of environment variables, and honestly they're iconic. iykyk. ๐ง
PYPY_GC_NURSERYโ the nursery size. defaults to 1/2 of your last-level cache,4Mif that's unknown, or4Mif the last-level cache is too small. small values (like1or1KB) are useful for debugging. ๐PYPY_GC_NURSERY_DEBUGโ if set to non-zero, it will fill the nursery with garbage, to help with debugging. ๐๏ธPYPY_GC_INCREMENT_STEPโ the size of memory marked during the marking step. default is size of nursery times 2. if you mark it too high your GC is not incremental at all ๐ญ so watch it. the minimum is set to size that survives minor collection times 1.5, so we reclaim anything all the time. โป๏ธPYPY_GC_MAJOR_COLLECTโ major collection memory factor. default is1.82, which means: trigger a major collection when the memory consumed equals1.82times the memory really used at the end of the previous major collection. ๐จPYPY_GC_GROWTHโ the major collection threshold's max growth rate. default is1.4. useful to collect more often than normal on sudden memory growth, e.g. when there's a temporary peak in memory usage. ๐PYPY_GC_MAXโ the max heap size. if you come near this limit, it will first collect more often, then raise anRPythonMemoryError, and if that's still not enough, crash the program with a fatal error ๐ so likeโฆ respect it. try values like1.6GB.PYPY_GC_MAX_DELTAโ the major collection threshold will never be set to more thanPYPY_GC_MAX_DELTAthe amount really used after a collection. defaults to 1/8th of the total RAM size (which is constrained to be at most2/3/4GBon 32-bit systems). try values like200MB. ๐งฎPYPY_GC_MINโ don't collect while the memory size is below this limit. useful to avoid spending all your time in the GC in very small programs. defaults to 8 times the nursery. ๐PYPY_GC_DEBUGโ enable extra checks around collections that are too slow for normal use. values are0(off),1(on major collections) or2(also on minor collections). ๐PYPY_GC_MAX_PINNEDโ the maximal number of pinned objects at any point in time. defaults to a conservative value depending on nursery size and maximum object size inside the nursery. useful for debugging by setting it to0. ๐
๐ SEE ALSO ๐
๐ซถ Author ๐ซถ
The PyPy Project
๐ Copyright ๐
2024, The PyPy Project
and that's the whole page bestie, you're officially a PyPy GC girlie now ๐ ๐ท this was version 7.3, dated Sep 19, 2026 โ slay, go collect some garbage (respectfully) and stay iconic โจ
๐ฆ where this page comes from
- ubuntu stonking: pypy3 7.3.23+dfsg-1ubuntu1 ยท MIT
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms