Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support --sh-boot for --layout {loose,packed}. #2645

Merged
merged 4 commits into from
Jan 19, 2025

Conversation

jsirois
Copy link
Member

@jsirois jsirois commented Jan 18, 2025

No description provided.

@jsirois jsirois requested review from benjyw and huonw January 19, 2025 01:10
@jsirois
Copy link
Member Author

jsirois commented Jan 19, 2025

Reviewers, you're on here because this should allow eliminating the hackery I added to Pants long ago to reduce PEX venv latency. You should find the same numbers you have today, but with less code and less brittleness.

The benchmark:

#!/usr/bin/env bash

set -euo pipefail

EXECUTION_MODE_ARGS=(
  "ZIPAPP|"
  "ZIPAPP-sh-boot|--sh-boot"
  "VENV|--venv prepend"
  "VENV-sh-boot|--venv prepend --sh-boot"
)

LAYOUTS=(
  loose
  packed
  zipapp
)

PEXES=()
for value in "${EXECUTION_MODE_ARGS[@]}"; do
  em_label="${value/\|*/}"
  em_args="${value/*\|/}"
  for layout in "${LAYOUTS[@]}"; do
    mkdir -p /tmp/test
    PEX="/tmp/test/${em_label}-${layout}.pex"
    python3.13 -mpex cowsay -ccowsay ${em_args} --layout ${layout} -o "${PEX}"
    if [[ -d ${PEX} ]]; then
      PEXES+=("${PEX}/pex")
    else
      PEXES+=("${PEX}")
    fi
  done
done

params="$(echo "${PEXES[@]}" | xargs | tr ' ' ,)"
hyperfine -w2 -L pex ${params} "{pex} -t Moo!"

And the results:

:; ./test.sh 
Benchmark 1: /tmp/test/ZIPAPP-loose.pex/pex -t Moo!
  Time (mean ± σ):     177.8 ms ±   1.4 ms    [User: 147.6 ms, System: 31.2 ms]
  Range (min … max):   175.9 ms … 180.1 ms    16 runs
 
Benchmark 2: /tmp/test/ZIPAPP-packed.pex/pex -t Moo!
  Time (mean ± σ):     331.9 ms ±   1.6 ms    [User: 292.3 ms, System: 40.5 ms]
  Range (min … max):   328.7 ms … 334.6 ms    10 runs
 
Benchmark 3: /tmp/test/ZIPAPP-zipapp.pex -t Moo!
  Time (mean ± σ):     336.8 ms ±   3.4 ms    [User: 294.7 ms, System: 43.1 ms]
  Range (min … max):   331.8 ms … 342.2 ms    10 runs
 
Benchmark 4: /tmp/test/ZIPAPP-sh-boot-loose.pex/pex -t Moo!
  Time (mean ± σ):     182.3 ms ±   1.4 ms    [User: 151.7 ms, System: 31.6 ms]
  Range (min … max):   180.3 ms … 184.8 ms    16 runs
 
Benchmark 5: /tmp/test/ZIPAPP-sh-boot-packed.pex/pex -t Moo!
  Time (mean ± σ):     182.1 ms ±   2.0 ms    [User: 151.9 ms, System: 31.2 ms]
  Range (min … max):   179.8 ms … 188.1 ms    16 runs
 
Benchmark 6: /tmp/test/ZIPAPP-sh-boot-zipapp.pex -t Moo!
  Time (mean ± σ):     182.3 ms ±   2.0 ms    [User: 151.8 ms, System: 31.6 ms]
  Range (min … max):   179.6 ms … 187.4 ms    16 runs
 
Benchmark 7: /tmp/test/VENV-loose.pex/pex -t Moo!
  Time (mean ± σ):      94.4 ms ±   0.7 ms    [User: 69.9 ms, System: 25.5 ms]
  Range (min … max):    93.1 ms …  96.1 ms    31 runs
 
Benchmark 8: /tmp/test/VENV-packed.pex/pex -t Moo!
  Time (mean ± σ):     123.9 ms ±   1.0 ms    [User: 97.6 ms, System: 27.2 ms]
  Range (min … max):   121.8 ms … 125.8 ms    24 runs
 
Benchmark 9: /tmp/test/VENV-zipapp.pex -t Moo!
  Time (mean ± σ):     124.8 ms ±   1.3 ms    [User: 99.5 ms, System: 26.2 ms]
  Range (min … max):   121.9 ms … 127.0 ms    23 runs
 
Benchmark 10: /tmp/test/VENV-sh-boot-loose.pex/pex -t Moo!
  Time (mean ± σ):      19.7 ms ±   0.5 ms    [User: 16.7 ms, System: 2.9 ms]
  Range (min … max):    18.6 ms …  21.1 ms    143 runs
 
Benchmark 11: /tmp/test/VENV-sh-boot-packed.pex/pex -t Moo!
  Time (mean ± σ):      19.8 ms ±   0.5 ms    [User: 16.7 ms, System: 3.0 ms]
  Range (min … max):    18.4 ms …  21.4 ms    143 runs
 
Benchmark 12: /tmp/test/VENV-sh-boot-zipapp.pex -t Moo!
  Time (mean ± σ):      19.3 ms ±   0.5 ms    [User: 16.3 ms, System: 2.9 ms]
  Range (min … max):    18.1 ms …  21.0 ms    140 runs
 
Summary
  /tmp/test/VENV-sh-boot-zipapp.pex -t Moo! ran
    1.02 ± 0.04 times faster than /tmp/test/VENV-sh-boot-loose.pex/pex -t Moo!
    1.03 ± 0.04 times faster than /tmp/test/VENV-sh-boot-packed.pex/pex -t Moo!
    4.90 ± 0.13 times faster than /tmp/test/VENV-loose.pex/pex -t Moo!
    6.43 ± 0.17 times faster than /tmp/test/VENV-packed.pex/pex -t Moo!
    6.48 ± 0.18 times faster than /tmp/test/VENV-zipapp.pex -t Moo!
    9.23 ± 0.25 times faster than /tmp/test/ZIPAPP-loose.pex/pex -t Moo!
    9.46 ± 0.26 times faster than /tmp/test/ZIPAPP-sh-boot-packed.pex/pex -t Moo!
    9.47 ± 0.26 times faster than /tmp/test/ZIPAPP-sh-boot-zipapp.pex -t Moo!
    9.47 ± 0.25 times faster than /tmp/test/ZIPAPP-sh-boot-loose.pex/pex -t Moo!
   17.23 ± 0.44 times faster than /tmp/test/ZIPAPP-packed.pex/pex -t Moo!
   17.49 ± 0.48 times faster than /tmp/test/ZIPAPP-zipapp.pex -t Moo!

Comment on lines +76 to +113
def create_sh_python_redirector_shebang(sh_script_content):
# type: (str) -> Tuple[str, str]
"""Create a shebang block for a Python file that uses /bin/sh to find an appropriate Python.

The script should be POSIX compliant sh and terminate on all execution paths with an
explicit exit or exec.

The returned shebang block will include the leading `#!` but will not include a trailing new
line character.

:param sh_script_content: A POSIX compliant sh script that always explicitly terminates.
:return: A shebang line and trailing block of text that can be combined for use as a shebang
header for a Python file.
"""
# This trick relies on /bin/sh being ubiquitous and the concordance of:
#
# 1. Python: Has triple quoted strings plus allowance for free-floating string values in
# python files.
# 2. sh: Any number of pairs of `'` evaluating away when followed immediately by a
# command string (`''command` -> `command`).
# 3. sh: The `:` noop command which accepts and discards arbitrary args.
# See: https://pubs.opengroup.org/onlinepubs/009604599/utilities/colon.html
# 4. sh: Lazy parsing allowing for invalid sh content immediately following an exit or exec
# line.
#
# The end result is a file that is both a valid sh script with a short shebang and a valid
# Python program.
return "#!/bin/sh", (
dedent(
"""\
'''': pshprs
{sh_script_content}
'''
"""
)
.format(sh_script_content=sh_script_content.rstrip())
.strip()
)
Copy link
Member Author

@jsirois jsirois Jan 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This (and its use in pex/pex_builder.py) is the only bit of real interest in this PR. An old trick, but formalized and improved with the : pshprs sh comment allowing for efficient is_python_script checks of these otherwise not apparently Python scripts.

This trick is what allows using the existing --sh-boot PEX zip header script wholesale as a header to the existing PEX __main__.py launcher in loose and packed PEX directories.

Copy link
Collaborator

@huonw huonw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome!

(Cc'ing the relevant pants issue: pantsbuild/pants#21177 )

@@ -660,6 +660,19 @@ def build(
compress=compress,
bytecode_compile=bytecode_compile,
)
if layout in (Layout.LOOSE, Layout.PACKED):
pex_script = os.path.join(tmp_pex, "pex")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I inferring correctly that this pex script is a new top-level file included in loose and packed PEXes? And is designed to be the "best" way to execute such a pex?

Copy link
Member Author

@jsirois jsirois Jan 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not really new, notice at the end of the block pex just becomes a symlink to __main__.py, but running either file directly is the best way to run the PEX. This is because the file has new content, the sh boot header.

You used to be able to run the PEX in 3 ways:

python the/dir-layout-pex/
python the/dir-layout-pex/__main__.py
the/dir-layout-pex/__main__.py

You still can, Pex will never break you, but whereas all three booted with the same latency before, the third has lower latency now. Added to that are two more ways to launch equivalent to the second and third since pex is symlink to __main__.py:

python the/dir-layout-pex/pex
the/dir-layout-pex/pex

Here the second form has the low latency boot. These forms parallel the root of a PEX venv and have the mildly pleasing aesthetic of -o app.pex vs --layout {loose,packed} -o app leading to execution via ./app.pex vs ./app/pex.

@jsirois jsirois merged commit 0277818 into pex-tool:main Jan 19, 2025
23 checks passed
@jsirois jsirois deleted the sh-boot/more branch January 19, 2025 20:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants