A native runtime bootstrap for PEXes.
Warning
This set of tools is very alpha and definitely not intended for production use yet!
PEXes must meet a few basic criteria to meet the historic Pex design goals:
- Support both CPython 2.7 and PyPy 2.7 as well as 3.5 and up for both implementations.
- Support multi-platform PEXes that include platform-specific wheels for each targeted platform.
- Provide a hermetic runtime environment by default. You can only import the project packages your PEX ships with no matter the vagaries of the machines the PEX lands on.
Pexrc provides a pexrc binary that can take an existing zipapp PEX and replace its runtime
.bootstrap/ with a native code bootstrap that meets all the design goals above while also
producing PEXes that are faster to execute in both cold and warm cache scenarios across the full
range of PEX sizes.
For example:
# Given both a traditional zipapp PEX and a venv PEX:
:; pex cowsay -c cowsay -o cowsay.zipapp.pex
:; pex cowsay -c cowsay --venv -o cowsay.venv.pex
# Inject them with the new runtime:
:; time target/release/pexrc inject cowsay.zipapp.pex
Writing x86_64-unknown-linux-gnu.libpexrc.so 1068848 bytes to __pex__/.clib/x86_64-unknown-linux-gnu.libpexrc.so...done.
real 0m0.056s
user 0m0.050s
sys 0m0.005s
:; time target/release/pexrc inject cowsay.venv.pex
Writing x86_64-unknown-linux-gnu.libpexrc.so 1068848 bytes to __pex__/.clib/x86_64-unknown-linux-gnu.libpexrc.so...done.
# Compare PEX sizes:
:; ls -1sh cowsay.zipapp.* cowsay.venv.*
880K cowsay.venv.pex
1.4M cowsay.venv.pexrc
860K cowsay.zipapp.pex
1.4M cowsay.zipapp.pexrc
# Compare cold cache speed:
:; hyperfine -w2 \
-p 'rm -rf ~/.cache/pex' 'python cowsay.zipapp.pex -t Moo!' \
-p 'rm -rf ~/.cache/pex' 'python cowsay.venv.pex -t Moo!' \
-p 'rm -rf ~/.cache/pexrc/' 'python cowsay.zipapp.pexrc -t Moo!' \
-p 'rm -rf ~/.cache/pexrc/' 'python cowsay.venv.pexrc -t Moo!'
Benchmark 1: python cowsay.zipapp.pex -t Moo!
Time (mean ± σ): 859.0 ms ± 10.6 ms [User: 777.3 ms, System: 81.6 ms]
Range (min … max): 846.4 ms … 885.2 ms 10 runs
Benchmark 2: python cowsay.venv.pex -t Moo!
Time (mean ± σ): 1.030 s ± 0.016 s [User: 0.920 s, System: 0.111 s]
Range (min … max): 1.005 s … 1.055 s 10 runs
Benchmark 3: python cowsay.zipapp.pexrc -t Moo!
Time (mean ± σ): 132.9 ms ± 1.7 ms [User: 114.6 ms, System: 26.4 ms]
Range (min … max): 130.3 ms … 137.1 ms 21 runs
Benchmark 4: python cowsay.venv.pexrc -t Moo!
Time (mean ± σ): 134.2 ms ± 3.0 ms [User: 116.0 ms, System: 26.4 ms]
Range (min … max): 129.5 ms … 141.5 ms 22 runs
Summary
python cowsay.zipapp.pexrc -t Moo! ran
1.01 ± 0.03 times faster than python cowsay.venv.pexrc -t Moo!
6.46 ± 0.12 times faster than python cowsay.zipapp.pex -t Moo!
7.75 ± 0.15 times faster than python cowsay.venv.pex -t Moo!
# Compare warm cache speed:
:; hyperfine -w2 \
'python cowsay.zipapp.pex -t Moo!' \
'python cowsay.venv.pex -t Moo!' \
'python cowsay.zipapp.pexrc -t Moo!' \
'python cowsay.venv.pexrc -t Moo!'
Benchmark 1: python cowsay.zipapp.pex -t Moo!
Time (mean ± σ): 362.1 ms ± 17.6 ms [User: 323.7 ms, System: 38.6 ms]
Range (min … max): 336.5 ms … 391.8 ms 10 runs
Benchmark 2: python cowsay.venv.pex -t Moo!
Time (mean ± σ): 111.9 ms ± 6.0 ms [User: 97.4 ms, System: 14.4 ms]
Range (min … max): 102.5 ms … 131.4 ms 27 runs
Benchmark 3: python cowsay.zipapp.pexrc -t Moo!
Time (mean ± σ): 71.8 ms ± 5.0 ms [User: 58.9 ms, System: 12.9 ms]
Range (min … max): 64.4 ms … 93.0 ms 39 runs
Benchmark 4: python cowsay.venv.pexrc -t Moo!
Time (mean ± σ): 69.2 ms ± 4.8 ms [User: 57.1 ms, System: 12.0 ms]
Range (min … max): 63.7 ms … 84.1 ms 45 runs
Summary
python cowsay.venv.pexrc -t Moo! ran
1.04 ± 0.10 times faster than python cowsay.zipapp.pexrc -t Moo!
1.62 ± 0.14 times faster than python cowsay.venv.pex -t Moo!
5.23 ± 0.45 times faster than python cowsay.zipapp.pex -t Moo!On the huge PEX side of the spectrum, some extra tricks come to the fore. Namely, injected PEXes use
zstd compression for all files except __main__.py and PEX-INFO and zip extraction is further
parallelized across all available cores.
Using the torch case:
# Given a traditional zipapp torch PEX:
:; pex torch -o torch.pex
# Inject the PEX with the new runtime:
:; time target/release/pexrc inject torch.pex
Writing x86_64-unknown-linux-gnu.libpexrc.so 1068848 bytes to __pex__/.clib/x86_64-unknown-linux-gnu.libpexrc.so...done.
real 0m39.855s
user 0m38.108s
sys 0m1.579s
# That took a little bit! But a pretty big space savings is a result:
:; ls -1sh torch.pex torch.pexrc
3.9G torch.pex
3.2G torch.pexrc
# Cold cache perf is improved:
:; hyperfine -w1 -r3 \
-p 'rm -rf ~/.cache/pexrc' 'python torch.pexrc -c "import torch; print(torch.__file__)"' \
-p 'rm -rf ~/.cache/pex' 'python torch.pex -c "import torch; print(torch.__file__)"'
Benchmark 1: python torch.pexrc -c "import torch; print(torch.__file__)"
Time (mean ± σ): 5.563 s ± 0.111 s [User: 14.526 s, System: 3.968 s]
Range (min … max): 5.455 s … 5.678 s 3 runs
Benchmark 2: python torch.pex -c "import torch; print(torch.__file__)"
Time (mean ± σ): 29.046 s ± 0.131 s [User: 27.196 s, System: 1.733 s]
Range (min … max): 28.897 s … 29.145 s 3 runs
Summary
python torch.pexrc -c "import torch; print(torch.__file__)" ran
5.22 ± 0.11 times faster than python torch.pex -c "import torch; print(torch.__file__)"
# As is warm cache perf:
:; hyperfine -w1 -r3 \
'python torch.pexrc -c "import torch; print(torch.__file__)"' \
'python torch.pex -c "import torch; print(torch.__file__)"'
Benchmark 1: python torch.pexrc -c "import torch; print(torch.__file__)"
Time (mean ± σ): 1.207 s ± 0.010 s [User: 1.045 s, System: 0.160 s]
Range (min … max): 1.195 s … 1.215 s 3 runs
Benchmark 2: python torch.pex -c "import torch; print(torch.__file__)"
Time (mean ± σ): 1.975 s ± 0.013 s [User: 1.807 s, System: 0.168 s]
Range (min … max): 1.963 s … 1.988 s 3 runs
Summary
python torch.pexrc -c "import torch; print(torch.__file__)" ran
1.64 ± 0.02 times faster than python torch.pex -c "import torch; print(torch.__file__)"N.B,.: The ideas developed in this repo, once proved out, will likely move into the main Pex repo or at least used by the Pex CLI tool to replace the current pure-Python PEX bootstrap runtime.