Performance benchmarking suite for signature computation libraries with strict isolation and methodological fairness.
This benchmark suite uses an Orchestrator-Adapter architecture to ensure fair comparisons:
- Orchestrator (src/orchestrator.py): Main driver that coordinates benchmark execution
- Adapters: Individual scripts for each library, running in isolated environments
- Python adapters use
uv runwith ephemeral environments - Julia adapters use isolated
JULIA_PROJECTenvironments
- Python adapters use
- Manual Timing Loops: Identical timing methodology across languages (no timeit, pyperf, or BenchmarkTools)
- iisignature (Python, industry standard)
- pysiglib (Python, PyTorch-based)
- chen-signatures (Python, with autodiff support)
- ChenSignatures.jl (Julia)
- Strict Isolation: Each library runs in its own ephemeral environment
- Methodological Fairness: Identical manual timing loops across all languages
- Setup/Kernel Separation: Only the computation is timed, not data preparation
- Multiple Operations: signature, logsignature, sigdiff (autodiff)
- Scaling Analysis: Varies N (path length), d (dimension), m (signature level)
The newest full benchmark is checked into the repo at runs/benchmark_20251202_223554/ so results are browseable without rerunning.
- Data:
results.csv - Plots:
plot_heatmap.png,plot_speedup_slowest.png,plot_profile.png,plot_box.png,plot_line.png - Config snapshots:
benchmark_sweep.yaml,libraries_registry.yaml
To run the benchmarks, you need:
- Julia β₯ 1.10
- Python β₯ 3.9
- uv β fast Python package manager
Install uv:
# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows (PowerShell)
irm https://astral.sh/uv/install.ps1 | iexuv run run_benchmarks.pyThis single command will:
- Run all benchmarks with configured libraries
- Generate comparison plots automatically
- Show you the output directory
If you want more control:
Benchmarks only:
uv run src/orchestrator.pyPlots only:
# Generate all plot types on latest run
uv run src/plotting.py --plot-type all
# Generate specific plot type
uv run src/plotting.py --plot-type heatmap
# Use specific run directory
uv run src/plotting.py runs/benchmark_TIMESTAMP --plot-type speedup
# List available plot types
uv run src/plotting.py --list-plotsThe plotting tool generates multiple visualization types:
line- Original 3Γ3 grid of line plots showing absolute performanceheatmap- Complete performance landscape across all parameter combinations (log scale)speedup- Relative performance showing speedup vs baseline (default: slowest library)profile- Performance profile showing how often each library is competitivebox- Distribution of performance across all benchmarks with outliersall- Generate all plot types at once (default)
Examples:
# Generate all plots
uv run src/plotting.py
# Only heatmap
uv run src/plotting.py -t heatmap
# Speedup vs fastest library instead of slowest
uv run src/plotting.py -t speedup --baseline fastest
# Speedup vs specific library
uv run src/plotting.py -t speedup --baseline iisignatureEach run creates a timestamped folder in runs/ containing:
results.csv- All benchmark resultsplot_line.png- Line plot (original 3Γ3 grid)plot_heatmap.png- Heatmap of all benchmarksplot_speedup_slowest.png- Speedup plot vs slowestplot_profile.png- Performance profileplot_box.png- Box plot distributionbenchmark_sweep.yaml- Config snapshotlibraries_registry.yaml- Registry snapshot
.
βββ config/
β βββ benchmark_sweep.yaml # Parameter sweep configuration
β βββ libraries_registry.yaml # Library adapter registry
βββ src/
β βββ orchestrator.py # Main benchmark driver
β βββ plotting.py # Plot generation
β βββ common/ # Shared utilities (injectable)
β βββ __init__.py
β βββ adapter.py # BenchmarkAdapter base class
β βββ paths.py # Path generation utilities
βββ adapters/
β βββ python/ # Python adapter scripts
β β βββ run_iisignature.py
β β βββ run_pysiglib.py
β β βββ run_chen.py
β βββ julia/ # Julia adapter project
β βββ Project.toml
β βββ run_chen.jl
βββ runs/ # Benchmark output folders
βββ pyproject.toml # Orchestrator dependencies
Each benchmark run creates a timestamped folder:
runs/benchmark_20251201_143022/
βββ benchmark_sweep.yaml # Config snapshot
βββ libraries_registry.yaml # Registry snapshot
βββ results.csv # Aggregated benchmark results
βββ plot_line.png # Line plot (3Γ3 grid)
βββ plot_heatmap.png # Heatmap visualization
βββ plot_speedup_slowest.png # Speedup plot
βββ plot_profile.png # Performance profile
βββ plot_box.png # Box plot distribution
Benchmark Sweep (config/benchmark_sweep.yaml)
Defines the parameter grid:
path_kind: "sin" # "linear" or "sin"
Ns: [200, 400, 800] # Path lengths
Ds: [2, 5, 7] # Dimensions
Ms: [2, 3, 4] # Signature levels
operations:
- signature
- logsignature
- sigdiff
repeats: 10 # Timing loop iterations
runs_dir: "runs"Library Registry (config/libraries_registry.yaml)
Defines available adapters and their dependencies:
libraries:
iisignature:
type: python
script: "adapters/python/run_iisignature.py"
deps: ["iisignature", "numpy"]
operations: ["signature", "logsignature"]
ChenSignatures.jl:
type: julia
dir: "adapters/julia"
script: "run_chen.jl"
operations: ["signature", "logsignature"]Note: Large configurations (high N Γ d Γ m) can take significant time. Start small for testing.
All benchmarks output to a unified CSV format:
N,d,m,path_kind,operation,language,library,method,path_type,t_ms
200,2,2,sin,signature,python,iisignature,sig,ndarray,0.123
200,2,2,sin,signature,julia,ChenSignatures.jl,signature_path,Vector{SVector},0.089
Columns:
N,d,m: Problem parameters (path length, dimension, signature level)path_kind: Path generator type (linearorsin)operation: Operation type (signature,logsignature, orsigdiff)language:juliaorpythonlibrary: Implementation namemethod: Specific method/function calledpath_type: Input data structure (ndarray,Vector{SVector},torch, etc.)t_ms: Average time in milliseconds (averaged overrepeatsiterations)
Each library benchmark runs in complete isolation:
Python adapters:
uv run --with <dep1> --with <dep2> python <script> '<json_config>'uv runcreates an ephemeral environment per invocation- Dependencies are injected via
--withflags - Adapters add
src/tosys.pathto access common utilities - No shared virtualenv or global state
Julia adapters:
JULIA_PROJECT=adapters/julia julia run_chen.jl '<json_config>'- Each adapter has its own
Project.toml - No shared global Julia environment
All adapters use the same timing methodology (no timeit/pyperf/BenchmarkTools):
Python:
# Warmup (untimed)
for _ in range(3):
func()
# Timed loop with GC disabled
gc.disable()
t0 = time.perf_counter()
for _ in range(repeats):
func()
t1 = time.perf_counter()
gc.enable()
avg_time_ms = ((t1 - t0) / repeats) * 1000Julia:
# Warmup (untimed)
for _ in 1:3
func()
end
# Timed loop with GC disabled
GC.enable(false)
t0 = time_ns()
for _ in 1:repeats
func()
end
t1 = time_ns()
GC.enable(true)
avg_time_ms = ((t1 - t0) / repeats) / 1e6Adapters separate "Setup" (untimed) from "Kernel" (timed):
def run_signature(self, path, d, m):
# Setup phase (untimed): data casting, basis preparation
path = np.ascontiguousarray(path, dtype=np.float64)
# Return kernel closure (only this is timed)
return lambda: iisignature.sig(path, m)Line Plot (plot_line.png):
- 3Γ3 grid showing absolute timing (ms)
- Rows: vary N, d, or m
- Columns: different operations
- Lower lines = faster performance
Heatmap (plot_heatmap.png):
- Shows ALL parameter combinations at once
- Darker colors (blue/purple) = faster
- Lighter colors (yellow) = slower
- Numbers show actual milliseconds
- Best for seeing the complete performance landscape
Speedup Plot (plot_speedup_slowest.png):
- Same layout as line plot but shows relative performance
- Y-axis = speedup factor (higher is better)
- Dashed line at 1.0 = baseline (slowest library)
- Easier to see winners than absolute timing
Performance Profile (plot_profile.png):
- X-axis: performance ratio (time / best_time)
- Y-axis: fraction of benchmarks
- Curves hugging the left edge = consistently fast
- Used in academic optimization papers
Box Plot (plot_box.png):
- Shows distribution across all benchmarks
- Box = quartiles, line = median
- Circles = outliers
- Log scale if data spans multiple orders of magnitude
From SUMMARY.txt in comparison runs:
ChenSignatures.jl(Matrix) vs iisignature: avg speedup = 5.00x
ChenSignatures.jl(Vector{SVector}) vs iisignature: avg speedup = 5.40x
- Matrix vs Vector{SVector}: Vector{SVector} is typically 5-10% faster
- vs iisignature: Julia is 5-7Γ faster
- vs pysiglib: Julia is 25-30Γ faster
From SUMMARY.txt in validation runs:
Total tests: 24
Passed: 24
Failed: 0
Pass rate: 100.0%
All implementations should agree within numerical tolerance (rel_err < 1e-7).
- Create adapter script in
adapters/python/oradapters/julia/ - Inherit from BenchmarkAdapter (Python) or implement equivalent (Julia)
- Register in config/libraries_registry.yaml:
my-library: type: python script: "adapters/python/run_mylib.py" deps: ["my-library", "numpy"] operations: ["signature"]
Edit src/common/paths.py:
def make_path_custom(d: int, N: int) -> np.ndarray:
# Your custom path generation logic
pass
def make_path(d: int, N: int, kind: str) -> np.ndarray:
# Add to dispatcher
if kind == "custom":
return make_path_custom(d, N)For Julia, update adapters/julia/run_chen.jl similarly.
cd adapters/julia
julia --project=. -e 'using Pkg; Pkg.instantiate()'The orchestrator dependencies should be automatically available via pyproject.toml. If you have issues:
uv syncReduce grid size in config/benchmark_sweep.yaml:
Ns: [200, 400] # Fewer values
Ds: [2, 5]
Ms: [2, 3]
repeats: 5 # Fewer iterationsCheck the library is available:
# Python
uv run python -c "import iisignature; print(iisignature.__version__)"
# Julia
julia --project=adapters/julia -e 'using ChenSignatures'