blob: 7b28d39097fe3404699bb534dd755f70e3206ced [file]
#!/usr/bin/env python3
# Copyright 2026 Google LLC
#
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import argparse
import os
import shutil
import subprocess
import sys
OPTS_FILE = "src/opts/SkRasterPipeline_opts.h"
SKOPTS_FILE = "src/core/SkOpts.cpp"
BUILD_DIR = "out/Release"
BIN_TEMP_DIR = os.path.join(BUILD_DIR, "nanobenches")
def run_cmd(args, check=True):
"""Utility to run a shell command and print outputs on failure."""
try:
result = subprocess.run(args, capture_output=True, text=True, check=check)
return result.stdout.strip()
except subprocess.CalledProcessError as e:
print(f"Command failed: {' '.join(args)}")
print(f"Stdout:\n{e.stdout}")
print(f"Stderr:\n{e.stderr}")
if check:
sys.exit(e.returncode)
raise e
def check_git_status(baseline):
"""Ensures there are no uncommitted changes to the opts file before we start."""
print(f"Checking Git status of {OPTS_FILE}...")
status = subprocess.run(["git", "diff", "--exit-code", OPTS_FILE], capture_output=True)
if status.returncode != 0:
print(f"ERROR: You have uncommitted changes in {OPTS_FILE}!")
print("Please commit your changes before running this script so Git can safely manage the branches.")
sys.exit(1)
def backup_and_revert_skopts():
"""Reads SkOpts.cpp so we can restore it exactly when done."""
with open(SKOPTS_FILE, "r") as f:
return f.read()
def restore_skopts(original_content):
"""Restores the original content of SkOpts.cpp."""
with open(SKOPTS_FILE, "w") as f:
f.write(original_content)
def configure_skopts(mode):
"""Modifies SkOpts.cpp to isolate a specific architecture: ml4, ml3, or sse2."""
with open(SKOPTS_FILE, "r") as f:
content = f.read()
# Find the Init_* calls we want to block
# Note: under sse2, we disable both ml4 and ml3. Under ml3, we disable ml4. Under ml4, we leave both.
if mode == "sse2":
content = content.replace("if (SkCpu::Supports(SkX64::ML4)) { Init_ml4(); }", "// if (SkCpu::Supports(SkX64::ML4)) { Init_ml4(); }")
content = content.replace("if (SkCpu::Supports(SkX64::ML3)) { Init_ml3(); }", "// if (SkCpu::Supports(SkX64::ML3)) { Init_ml3(); }")
elif mode == "ml3":
content = content.replace("if (SkCpu::Supports(SkX64::ML4)) { Init_ml4(); }", "// if (SkCpu::Supports(SkX64::ML4)) { Init_ml4(); }")
with open(SKOPTS_FILE, "w") as f:
f.write(content)
def compile_binaries(baseline):
"""Orchestrates the checkout, modification, and compilation of all 6 modes."""
os.makedirs(BIN_TEMP_DIR, exist_ok=True)
original_skopts = backup_and_revert_skopts()
try:
# Step 1: Compile the 3 Control (Baseline) versions
print(f"\n--- Checking out Control baseline ({baseline}) ---")
run_cmd(["git", "checkout", baseline, "--", OPTS_FILE])
for target in ["ml4", "ml3", "sse2"]:
print(f"\nConfiguring SkOpts.cpp for Control {target.upper()}...")
restore_skopts(original_skopts)
configure_skopts(target)
print(f"Compiling nanobench_control_{target}...")
run_cmd(["ninja", "-C", BUILD_DIR, "nanobench"])
dest = os.path.join(BIN_TEMP_DIR, f"nanobench_{target}_control")
shutil.copy(os.path.join(BUILD_DIR, "nanobench"), dest)
print(f"Saved binary to {dest}")
# Step 2: Compile the 3 Optimized (With Changes) versions
print("\n--- Checking out HEAD (Optimized) ---")
run_cmd(["git", "checkout", "HEAD", "--", OPTS_FILE])
for target in ["ml4", "ml3", "sse2"]:
print(f"\nConfiguring SkOpts.cpp for Optimized {target.upper()}...")
restore_skopts(original_skopts)
configure_skopts(target)
print(f"Compiling nanobench_with_changes_{target}...")
run_cmd(["ninja", "-C", BUILD_DIR, "nanobench"])
dest = os.path.join(BIN_TEMP_DIR, f"nanobench_{target}_with_changes")
shutil.copy(os.path.join(BUILD_DIR, "nanobench"), dest)
print(f"Saved binary to {dest}")
finally:
# Cleanup: Revert SkOpts.cpp and opts back to clean working branch state
print("\nCleaning up working tree files...")
restore_skopts(original_skopts)
run_cmd(["git", "checkout", "HEAD", "--", OPTS_FILE])
run_cmd(["git", "checkout", "HEAD", "--", SKOPTS_FILE])
def run_benchmarks(bench_args):
"""Runs all 6 saved binaries and prints outputs directly to stdout."""
targets = ["ml4", "ml3", "sse2"]
types = ["control", "with_changes"]
for t in targets:
for run_type in types:
binary_name = f"nanobench_{t}_{run_type}"
binary_path = os.path.join(BIN_TEMP_DIR, binary_name)
if not os.path.exists(binary_path):
print(f"ERROR: Binary {binary_name} not found in {BIN_TEMP_DIR}. Did you run --compile?")
sys.exit(1)
print(f"\n======================================================================")
print(f" Running {binary_name}...")
# Build full run command
cmd = [binary_path] + bench_args
# Run and print output directly to stdout
result = subprocess.run(cmd, text=True)
if result.returncode != 0:
print(f"ERROR running benchmark {binary_name}!")
sys.exit(result.returncode)
def main():
parser = argparse.ArgumentParser(description="Automate compiling and running of SkRasterPipeline microbenchmarks.")
parser.add_argument("--compile", action="store_true", help="Compile and cache control and optimized binaries.")
parser.add_argument("--run", action="store_true", help="Run the compiled benchmark binaries directly to stdout.")
parser.add_argument("--baseline", default="origin/main", help="Git reference for the baseline. Defaults to origin/main.")
# Capture all remaining arguments to forward to nanobench when running
args, remaining = parser.parse_known_args()
# Fail if neither --compile nor --run is specified explicitly
if not args.compile and not args.run:
print("ERROR: You must explicitly specify either --compile or --run")
print("\nUsage examples:")
print(" 1. Compile all target binaries:")
print(" python3 tools/raster_pipeline/run_benchmarks.py --compile")
print("\n 2. Run the benchmarks when the machine is quiet:")
print(" python3 tools/raster_pipeline/run_benchmarks.py --run --match skrp_gather --samples 5 --ms 0")
sys.exit(1)
if args.compile:
check_git_status(args.baseline)
compile_binaries(args.baseline)
if args.run:
# Default bench arguments if none were provided by the user
bench_args = remaining if remaining else ["--match", "skrp_"]
run_benchmarks(bench_args)
if __name__ == "__main__":
main()