From ea24622f4310886421ebd4aada59a6b13fd73123 Mon Sep 17 00:00:00 2001 From: Scott Roy Date: Wed, 10 Jun 2026 11:18:06 -0700 Subject: [PATCH] Add benchmarking script (#20188) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Adds a standalone microbenchmark for the ImageProcessor reuse APIs and a companion script to diff two runs, so kernel/pipeline changes (e.g. the NEON deinterleave switch) can be measured reproducibly. New directory xplat/executorch/extension/image/benchmark/: * image_processor_benchmark.cpp (cxx_binary) — times process_into (BGRA/RGBA) and process_yuv_into (NV12/NV21) over a sweep of common input sizes × target sizes. Per cell it runs variants covering execution path (CPU / GPU / size-default), resize mode (stretch / letterbox), orientation (upright + 90°), cropped ROI, and the allocating process() vs process_into(). Each row reports mean/median/p95/stddev over 100 iters (10 warmup) on a synthetic gradient input; a row that fails is reported as ERROR rather than timed. * Flags (all optional): --format=bgra|rgba|nv12|nv21, --unit=cpu|gpu|default (both default to all), --out=PATH (writes a clean results table; the input-size sweep and rotation always run). Output is grouped under === API-section banners with a column legend, and --- per-cell separators. * compare_benchmarks.py (python_binary, stdlib-only) — matches rows by (API section, input→target cell, variant) and prints per-row base / new speedup plus a summary bucketed by execution path (CPU / GPU / default). * README.md — usage, the build-mode caveat, and the capture→compare workflow. * BUCK / TARGETS / targets.bzl — build defs. Note: benchmark only with an optimized build (-c cxx.extra_cxxflags=-Os); the default buck2 run is -O0 and unrepresentative. Differential Revision: D108048181 --- extension/image/benchmark/BUCK | 5 + extension/image/benchmark/README.md | 73 +++ extension/image/benchmark/TARGETS | 5 + .../image/benchmark/compare_benchmarks.py | 122 ++++ .../benchmark/image_processor_benchmark.cpp | 585 ++++++++++++++++++ extension/image/benchmark/targets.bzl | 29 + 6 files changed, 819 insertions(+) create mode 100644 extension/image/benchmark/BUCK create mode 100644 extension/image/benchmark/README.md create mode 100644 extension/image/benchmark/TARGETS create mode 100644 extension/image/benchmark/compare_benchmarks.py create mode 100644 extension/image/benchmark/image_processor_benchmark.cpp create mode 100644 extension/image/benchmark/targets.bzl diff --git a/extension/image/benchmark/BUCK b/extension/image/benchmark/BUCK new file mode 100644 index 00000000000..0a42614a385 --- /dev/null +++ b/extension/image/benchmark/BUCK @@ -0,0 +1,5 @@ +load(":targets.bzl", "define_common_targets") + +oncall("executorch") + +define_common_targets() diff --git a/extension/image/benchmark/README.md b/extension/image/benchmark/README.md new file mode 100644 index 00000000000..eafdb1f5ef2 --- /dev/null +++ b/extension/image/benchmark/README.md @@ -0,0 +1,73 @@ +# ImageProcessor benchmark + +A microbenchmark for the `ImageProcessor` reuse APIs (`process_into` and +`process_yuv_into`) plus a companion script to compare two runs. + +## What it measures + +`image_processor_benchmark` sweeps common input sizes × target sizes and, per +cell, times a set of variants: + +- **API**: `process_into` (BGRA/RGBA) and `process_yuv_into` (NV12/NV21) +- **execution path**: CPU, GPU, and the size-threshold default +- **resize mode**: stretch, letterbox +- **orientation**: upright and 90° rotate +- **other**: cropped ROI, and the allocating `process()` vs `process_into()` + +Each row reports mean / median / p95 / stddev over 100 measured iterations +(10 warmup). + +## Build mode matters + +Always benchmark an **optimized** build. The default `buck2 run` compiles at +`-O0`, where the hand-written NEON kernels are unrepresentative. Pass `-c cxx.extra_cxxflags=-Os` to match +how ExecuTorch ships: + +```bash +buck2 run -c cxx.extra_cxxflags=-Os \ + fbsource//xplat/executorch/extension/image/benchmark:image_processor_benchmark +``` + +## Options + +| Flag | Default | Meaning | +|------|---------|---------| +| `--format=bgra\|rgba\|nv12\|nv21` | all | restrict to one color / YUV format | +| `--unit=cpu\|gpu\|default` | all | restrict to one execution path | +| `--out=PATH` | stdout | write the results table to PATH | + +The input-size sweep and the rotation variant always run. Writing with `--out` +keeps the file free of buck build-log lines (which go to stderr). + +## Comparing two runs + +Capture a baseline and a candidate, then diff them: + +```bash +TARGET=fbsource//xplat/executorch/extension/image/benchmark:image_processor_benchmark +buck2 run -c cxx.extra_cxxflags=-Os $TARGET -- --out=/tmp/base.txt +# ... make your change ... +buck2 run -c cxx.extra_cxxflags=-Os $TARGET -- --out=/tmp/new.txt + +python3 xplat/executorch/extension/image/benchmark/compare_benchmarks.py \ + /tmp/base.txt /tmp/new.txt +# or via buck: +buck2 run fbsource//xplat/executorch/extension/image/benchmark:compare_benchmarks \ + -- /tmp/base.txt /tmp/new.txt +``` + +`compare_benchmarks.py` matches rows by (API section, input→target cell, variant) +and prints the per-row `base / new` speedup plus a summary bucketed by execution +path (CPU / GPU / default). Cross-run and thermal drift shift all rows together, +so compare the buckets against each other rather than reading any single ratio +absolutely. + +For a clean A/B, capture both files back-to-back on an otherwise idle machine. + +## Files + +- `image_processor_benchmark.cpp` — the benchmark binary; buck target + `:image_processor_benchmark` (run with `buck2 run`) +- `compare_benchmarks.py` — compares two result files (stdlib only); buck target + `:compare_benchmarks` (run with `buck2 run …:compare_benchmarks -- BASE NEW`) +- `BUCK` / `TARGETS` / `targets.bzl` — build definitions diff --git a/extension/image/benchmark/TARGETS b/extension/image/benchmark/TARGETS new file mode 100644 index 00000000000..0a42614a385 --- /dev/null +++ b/extension/image/benchmark/TARGETS @@ -0,0 +1,5 @@ +load(":targets.bzl", "define_common_targets") + +oncall("executorch") + +define_common_targets() diff --git a/extension/image/benchmark/compare_benchmarks.py b/extension/image/benchmark/compare_benchmarks.py new file mode 100644 index 00000000000..3251ce2571f --- /dev/null +++ b/extension/image/benchmark/compare_benchmarks.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +# Copyright (c) Meta Platforms, Inc. and affiliates. +# All rights reserved. +# +# This source code is licensed under the BSD-style license found in the +# LICENSE file in the root directory of this source tree. + +"""Compare two image_processor_benchmark result files. + +Each input is the output of `image_processor_benchmark --out=PATH` (or its +stdout). Rows are matched by (API section, input->target cell, variant label) +and the per-row speedup base/new is reported. + +The summary buckets rows by execution path (CPU / GPU / default). Cross-run and +thermal drift shift all rows together, so compare the buckets against each other +rather than reading any single ratio absolutely. + +Usage: + compare_benchmarks.py BASE.txt NEW.txt [--metric=median|mean] +""" + +import argparse +import re +import statistics +import sys + +ROW_RE = re.compile( + r"^(?P