Performance Benchmark
This page records the performance benchmark results for RenMori 1.1.0: the tests run inside a real Ren'Py 8.5.2 engine, build the same asset set as unprotected, baseline-protected and fully protected distribution builds, and time every asset read through the engine's own loading path, renpy.loader.load, so the figures reflect the shipped runtime rather than a standalone crypto library.
The benchmark script is tools/benchmark.py in the repository, and the raw results behind this page are in docs/benchmarks/2026-10-31-apple-m1.json.
Summary
- With full protection, in-engine decryption throughput is 567 to 1529 MB/s depending on file size
- Reading a 2 MB CG takes about 0.95 ms longer with full protection than without; for reference, one frame at 60 FPS lasts 16.7 ms
- A random seek plus a 64 KB read in a video takes 0.086 ms (median) with full protection
- A
lintrun, which loads and parses every script, goes from 0.52 s to 0.67 s - The
game/directory of a protected build grows by only 0.18% - Protecting 319 MB of assets at build time takes about 13 s with full protection
Test environment
Method
Workload
The Ren'Py SDK sample project the_question is extended with a synthetic asset set sized like a commercial visual novel, 812 files in all, bringing game/ to 319.3 MB including the sample's own files; the synthetic assets are random bytes, as incompressible as encoded images, audio and video.
Measurements
MB on this page means 220 bytes.
Results
In-engine read throughput
In MB/s; higher is better.
Per-file read time
In ms, the time to read one whole file.
Video is streamed by the engine rather than read as a whole file, so the latency figures in the next section apply to it.
Video streaming latency
In ms, median.
Script loading
the_question contains 52 compiled scripts, and the three builds differ by no more than 0.15 s.
Build time and size overhead
Build time is spent only on the developer's machine or in CI and never affects players; full protection is performed by the closed plugin and includes extra work such as per-build structural polymorphism, so it takes longer than baseline protection.
Chunk size
These figures come from the build tool's Python with the cryptography backend and serve to compare chunk_size trade-offs; they do not represent absolute in-engine performance.
Larger chunks raise sequential throughput and lower size overhead but make each random read decrypt more data; the default of 64 KB balances the two and rarely needs changing.
Interpreting the results
Full protection uses the native runtime provided by the closed plugin and is the default for distribution builds; baseline protection uses the reference reader written in Python, with roughly 1/14 to 1/6 of the throughput of full protection, and suits only situations where the plugin cannot be used (see Protection Strength and the Closed Plugin).
The unprotected figures above approach memory-copy speed because the assets were already in the operating system's page cache during the test; this setup removes disk performance from the comparison so the results reflect only the CPU cost of decryption, and on a player's machine the first read is usually bound by disk speed, which narrows the relative gap between protected and unprotected builds.
Limitations
- Frame time during real play is not measured; this page covers asset reading and decryption only, not image decoding, audio decoding or rendering
- The synthetic assets are random bytes that the engine reads but does not decode
- The tests run with a warm page cache and exclude cold-start disk I/O
- Only one Apple M1 device was tested; figures for Windows, Linux and Steam Deck have not been measured yet
Reproducing
This needs the Ren'Py SDK and the RenMori source repository; full protection needs the closed plugin, which is downloaded automatically on the first run.
Absolute figures vary between devices, so compare the relative results of the three builds on the same device.