Skip to content

StandardPipeline.show_details uses Python builtin max()/min() on the fitness tensor — O(P) interpreter time per generation (~3 s/gen at pop 1M) #13

Description

@xavierhillroy

Summary

StandardPipeline.show_details (default is_show_details=True) computes fitness statistics with
Python's builtin max()/min() over the fitness tensor
(src/evogp/pipeline/standard.py lines 96–97, commit 5538d75):

max_f, min_f, mean_f, std_f = (
    max(valid_fitness),      # Python builtin -> element-by-element tensor iteration
    min(valid_fitness),      # Python builtin
    torch.mean(valid_fitness),
    torch.std(valid_fitness),
)

The builtins iterate the tensor element-by-element in the Python interpreter (one __getitem__
plus comparison per element), so this logging step costs O(pop_size) interpreter time every
generation
, and at large populations it dominates the entire evolutionary loop.

Measurements (RTX 5070 Ti, torch 2.9.0+cu130, evogp @ 5538d75)

Microbenchmark on a 1M-element fitness tensor:

import torch, time
t = -torch.rand(1_000_000) * 100
t0 = time.perf_counter(); m = max(t);       print(time.perf_counter() - t0)  # 1.48 s
t0 = time.perf_counter(); m = torch.max(t); print(time.perf_counter() - t0)  # 0.0003 s

Pipeline-level impact (SR, pop 1M, 100 generations, D=64, TournamentSelection + DefaultCrossover

  • DefaultMutation):
time / generation
is_show_details=True (default) ~2.5 s
is_show_details=False ~0.09 s

i.e. with the default setting, ~96% of wall-clock at this population size is the two builtin
calls in the logging path — not the (genuinely fast) evolutionary pipeline. At pop 10k the
logging is still ~90% of run time (~2.6 s vs ~0.28 s per 100 generations).

This matters beyond aesthetics: anyone timing StandardPipeline.run() at large populations with
the default settings (e.g. for benchmarking or framework comparisons) will measure the logging,
not EvoGP — we hit exactly this while running a comparison study and initially mis-measured
EvoGP's whole-run performance by ~25× before tracing it here.

Suggested fix (one line each)

max_f, min_f, mean_f, std_f = (
    torch.max(valid_fitness),
    torch.min(valid_fitness),
    torch.mean(valid_fitness),
    torch.std(valid_fitness),
)

(len(valid_fitness) in the print is fine; optionally the whole stats block could stay on GPU
and transfer scalars only.)

Environment

  • evogp installed from source at commit 5538d75 (pip install --no-build-isolation .)
  • PyTorch 2.9.0+cu130, CUDA 13.1, NVIDIA RTX 5070 Ti (sm_120), Linux

I will open a PR :) — and thanks for the framework; the pipeline itself
benchmarks impressively once the logging is bypassed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions