diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml new file mode 100644 index 0000000..b40bc1f --- /dev/null +++ b/.github/workflows/benchmarks.yml @@ -0,0 +1,64 @@ +name: Benchmarks + +on: [push, pull_request] + +env: + RUSTFLAGS: -C debuginfo=0 # Do not produce debug symbols to keep memory usage down + RUST_BACKTRACE: 1 + +jobs: + benchmarks: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + # we only use macos for fast testing + os: [macos-latest] + python-version: ['3.11'] + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Create virtual environment + env: + BIN: ${{ matrix.os == 'macos-latest' && 'Scripts' || 'bin' }} + run: | + python -m venv venv + + - name: Set up Rust + run: rustup show + + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + + - name: Install Plugin + run: | + brew install jpeg-xl + export DEP_JXL_LIB=/opt/homebrew/Cellar/jpeg-xl/0.10.3/lib + export DEP_BROTLI_LIB=/opt/homebrew/Cellar/brotli/1.1.0/lib + export DEP_HWY_LIB=/opt/homebrew/Cellar/highway/1.2.0/lib + source venv/bin/activate + pip install maturin + maturin develop --features dynamic + + - name: Run benchmarks scripts + run: | + source venv/bin/activate + python benchmarks/benchmarks_encode.py -i test/images/bench.png -o benchmarks-results-${{ matrix.python-version }}.json + + - name: Upload pytest test results + uses: actions/upload-artifact@v4 + with: + name: benchmarks-results-${{ matrix.python-version }} + path: benchmarks-results-${{ matrix.python-version }}.json + # Use always() to always run this step to publish test results when there are test failures + if: ${{ always() }} + + diff --git a/.gitignore b/.gitignore index da5e2df..564ea50 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ /target Cargo.lock +# Generated files +*.json + # Byte-compiled / optimized / DLL files __pycache__/ .pytest_cache/ diff --git a/benchmarks/benchmarks_encode.py b/benchmarks/benchmarks_encode.py new file mode 100644 index 0000000..a2f1ab0 --- /dev/null +++ b/benchmarks/benchmarks_encode.py @@ -0,0 +1,67 @@ +import json +import shlex +import subprocess +from argparse import ArgumentParser +from io import BytesIO +from time import time + +from PIL import Image + +import pillow_jxl + + +def encode_plugin(filename, quality, effort): + bio = BytesIO() + t = time() + Image.open(filename).save(bio, format="jxl", quality=quality, effort=effort) + d = time() - t + return len(bio.getvalue()), d + + +def encode_cli(filename, quality, effort): + cmd = f'cjxl -q {quality} -e {effort} "{filename}" -' + t = time() + p = subprocess.run(shlex.split(cmd), check=True, capture_output=True) + d = time() - t + return len(p.stdout), d + + +def main(args): + filename = args.image + quality = args.quality + + result = { + "image": filename, + "quality": quality, + "effort": [], + "plugin_size": [], + "plugin_time": [], + "refenc_size": [], + "refenc_time": [], + } + print("Quality:", quality) + for effort in range(1, 10): + size_plugin, time_plugin = encode_plugin(filename, quality, effort) + size_refenc, time_refenc = encode_cli(filename, quality, effort) + print(f"\nEffort {effort}") + print(f" plugin [size: {size_plugin}, duration: {time_plugin:.3f}]") + print(f" reference [size: {size_refenc}, duration: {time_refenc:.3f}]") + + result["effort"].append(effort) + result["plugin_size"].append(size_plugin) + result["plugin_time"].append(time_plugin) + result["refenc_size"].append(size_refenc) + result["refenc_time"].append(time_refenc) + + if args.output_json: + with open(args.output_json, "w") as f: + json.dump(result, f, indent=4) + + +if __name__ == "__main__": + parser = ArgumentParser() + parser.add_argument("-i", "--image", help="Image file for encode benchmark", required=True) + parser.add_argument("-q", "--quality", type=int, default=98, help="Quality level") + parser.add_argument("-o", "--output-json", help="Output JSON file") + args = parser.parse_args() + main(args) \ No newline at end of file diff --git a/test/images/bench.png b/test/images/bench.png new file mode 100644 index 0000000..9607dd5 Binary files /dev/null and b/test/images/bench.png differ