feat: PhaseTimer instrumentation + Eve as default math backend - #65
Merged
Conversation
Adds PhaseTimer, a tf::ObserverInterface that accumulates wall-clock time
per named task using Operon::Map<string, double>. Replaces the ad-hoc
sort_time_ / SortTime() members on GeneticAlgorithmBase with a general
Timings() accessor backed by phaseTimes_.
The observer is registered on the executor before the taskflow runs and
captures a shared_ptr in each reportProgress lambda so Timings() reflects
live cumulative data during the run, not just post-run. The final copy
after wait() ensures the last generation is included.
reporter.hpp reads sort_ms from Timings().find("non-dominated sort")
with a safe fallback to 0.0 for GP (which has no sort phase).
Overhead is ~4 µs/generation (~0.2% at typical run lengths), too small
to warrant a compile-time gate.
Eve is 1.21x–1.65x faster than Eigen (median 1.35x, p<0.0001 across all tested dataset/symbol-set combinations) due to better SIMD utilisation, particularly for transcendental functions. The AlignedAllocator<T,32> storage introduced for MDArray provides the alignment Eve needs for optimal vector loads. Eigen remains selectable via -DMATH_BACKEND=Eigen.
…op elapsed - Adds wall_s metric: Python-side perf_counter timing of the full subprocess call, injected into the stats dict alongside parsed columns. More reliable than the parsed elapsed column which was always n/a due to integer-zero formatting at generation 0. - Removes elapsed from REPORT_METRICS and LOWER_IS_BETTER. - Adds --ref-build / --new-build options (default: "build") to select the build subdirectory, enabling comparison across backends without moving binaries (e.g. --ref-build build-eigen --new-build build). - Adds import time (used by wall_s measurement).
- Extract task name into kSortTaskName (string_view constant) to eliminate magic-string coupling between nsga2.cpp and reporter.hpp. Both sites now reference the constant; a rename is a compile error. - Add detail::StringHash (transparent, avalanching) so that PhaseTimer's internal totals_ map uses heterogeneous lookup. on_exit now calls totals_[tv.name()] with no std::string construction after the first insert per unique task name. Timings() converts back to a plain Operon::Map<string, double> so ga_base.hpp stays unaware of the hash. - Document that Reset() is only valid between runs: a mid-run Reset() would have the next reportProgress sync overwrite the cleared map.
foolnotion
added a commit
that referenced
this pull request
Jul 13, 2026
feat: PhaseTimer instrumentation + Eve as default math backend
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
sort_time_/SortTime()onGeneticAlgorithmBasewith a generaltf::ObserverInterfacethat accumulates wall-clock time per named task intoTimings()(Operon::Map<string, double>). The observer is registered before the taskflow runs; eachreportProgresslambda holds ashared_ptrto it and syncsTimings()live so the reporter sees up-to-date data each generation. Overhead is ~4 µs/generation (~0.2%), not worth gating.AlignedAllocator<T,32>storage from the mdspan refactor provides the alignment Eve needs for optimal SIMD vector loads. Eigen remains selectable via-DMATH_BACKEND=Eigen.wall_s), drops the brokenelapsedcolumn, and adds--ref-build/--new-buildoptions for comparing across build directories (e.g. different backends).Test plan
-- MATH: Evein CMake output-DMATH_BACKEND=Eigen— confirm fallback worksoperon_nsgpand checksort_mscolumn is non-zero in outputtools/compare_operon.pywith--only-statsand verifywall_sandsort_msrows appear