For AI agents: the complete documentation index is available at https://renmori.mewbaka.cn/en/llms.txt, the full documentation bundle is available at https://renmori.mewbaka.cn/en/llms-full.txt, and this page is available as Markdown at https://renmori.mewbaka.cn/en/milestones/benchmark.md.
  • English
  • 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 lint run, 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

    ItemValue
    Date2026-10-31
    DeviceApple M1, 8 GB memory
    Operating systemmacOS 26.5.1 (arm64)
    Ren'Py8.5.2.26010301
    RenMori1.1.0, closed plugin plugin-v1.1.0
    Build tool Python3.12.12

    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.

    CategoryExtensionFile sizeFilesTotal
    CG.png2 MB4080.0 MB
    Sprites.png600 KB6035.2 MB
    UI assets.png24 KB4009.4 MB
    BGM.ogg4 MB1040.0 MB
    Voice.opus60 KB30017.6 MB
    Video.webm64 MB2128.0 MB

    Measurements

    MeasurementMethod
    In-engine read throughputA benchmark extension injected into each build reads every file through renpy.loader.load in 1 MB blocks during engine startup; each category gets one warm-up pass, then 5 timed passes, and the median is reported
    Video streaming latencyOn a 64 MB video, "open and read the first 64 KB" and "random seek then read 64 KB" are each sampled 200 times, and the median is reported
    Script loadingA full renpy.sh <game> lint runs 3 times per build and the median is reported; lint loads and parses every script from the container
    Build timerenmori protect runs 3 times on the same input and the median is reported
    Size overheadThe difference in game/ size between the protected and unprotected builds, covering the container, the loader extension and the script transform
    Chunk sizeIn the build tool's Python, the reference reader measures sequential throughput and random 4 KB read latency on 64 MB of data for 4 chunk sizes

    MB on this page means 220 bytes.

    Results

    In-engine read throughput

    In MB/s; higher is better.

    CategoryFile sizeUnprotectedFull protectionBaseline protection
    CG2 MB55901529108
    Sprites600 KB55851463120
    UI assets24 KB729567100
    BGM4 MB46081048115
    Voice60 KB119774997
    Video64 MB38221287112

    Per-file read time

    In ms, the time to read one whole file.

    CategoryFile sizeUnprotectedFull protectionAdded time
    CG2 MB0.361.31+0.95
    Sprites600 KB0.110.40+0.30
    UI assets24 KB0.0320.041+0.009
    BGM4 MB0.873.82+2.95
    Voice60 KB0.0490.078+0.029

    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.

    MeasurementUnprotectedFull protectionBaseline protection
    Open and read the first 64 KB0.0510.0820.596
    Random seek then read 64 KB0.0350.0861.113

    Script loading

    Buildlint time
    Unprotected0.52 s
    Full protection0.67 s
    Baseline protection0.58 s

    the_question contains 52 compiled scripts, and the three builds differ by no more than 0.15 s.

    Build time and size overhead

    StrengthTimeRategame/ size change
    Baseline protection2.52 s126.8 MB/s+0.147%
    Full protection13.41 s23.8 MB/s+0.177%

    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.

    Chunk sizeSequential throughputRandom 4 KB read (median)Random 4 KB read (p95)Size overhead
    16 KB1273 MB/s0.011 ms0.022 ms0.098%
    64 KB (default)2008 MB/s0.024 ms0.050 ms0.025%
    256 KB2339 MB/s0.077 ms0.124 ms0.006%
    1 MB1461 MB/s0.286 ms0.482 ms0.002%

    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.

    git clone https://github.com/MewBaka/RenMori.git
    cd RenMori && pip install -e .
    python tools/benchmark.py --sdk /path/to/renpy-8.5.2-sdk \
        --game /path/to/renpy-8.5.2-sdk/the_question --json result.json
    OptionDefaultNotes
    --scale1.0Workload size multiplier; 1.0 is about 310 MB of synthetic assets
    --passes5Timed passes per category in the engine
    --repeat3Repetitions of protect and lint
    --strengthbothWhich protected builds to produce: both, baseline or full
    --jsonnoneWrites the raw and summarized results to the given file
    --keepoffKeeps the work directory for inspecting the generated builds

    Absolute figures vary between devices, so compare the relative results of the three builds on the same device.

    © 2026 笨猫工作室 MewBaka Studio. All Rights Reserved.