Skip to content

perf(flow-php/etl): native int/float fast-path in sum() with opt-in exact mode#2522

Merged
norberttech merged 4 commits into
flow-php:1.xfrom
MrHDOLEK:perf/sum-int-float-fast-path
Jul 11, 2026
Merged

perf(flow-php/etl): native int/float fast-path in sum() with opt-in exact mode#2522
norberttech merged 4 commits into
flow-php:1.xfrom
MrHDOLEK:perf/sum-int-float-fast-path

Conversation

@MrHDOLEK

@MrHDOLEK MrHDOLEK commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Sum::aggregate() routed every addition through brick/math: a new Calculator() instance per row, then BigDecimal::of((string) $a)->plus(BigDecimal::of((string) $b)) — two string casts and two decimal parses per aggregated row, plus a fractional-part check and a toInt()/toFloat() conversion. On a 1M-row aggregation this dominated the cost of the most common aggregating function. Found while profiling pipelines against a 1M-row dataset.

Changes

  • sum() accumulates through native int/float arithmetic by default and mirrors the Calculator::add() contract by returning int whenever the running sum has no fractional part (so sum of [2.5, 2.5] still produces an int_entry(5) — guarded by a test),
  • sum(ref('x'), exact: true) opts back into the previous arbitrary-precision decimal path — for decimal-sensitive use cases; the flag follows the ScalarFunction|bool parameter convention, so it also takes lit(...) or any scalar expression evaluated per row (e.g. a boolean column) — plain booleans keep the allocation-free path,
  • a shared Calculator instance now lives in Config and is exposed through FlowContext::calculator() (same pattern as entryFactory()); Sum (exact mode) and Average (per-row aggregate()/apply()) pull it from the context instead of constructing a new instance per row,
  • new tests: shared calculator instance on FlowContext, fast-path vs exact-mode behavior on both the aggregation and the window-function path, and an anchor test pinning the int-when-whole result contract.

Precision note: with the default fast path, float summation is plain IEEE-754 arithmetic. Through result() this is not observable for typical sums — float_entry() already normalizes values via BigDecimal::of((string) $value) at PHP's default precision=14 — and the raw accumulated value only surfaces through WindowFunction::apply(). exact: true keeps decimal-exact accumulation end to end.

Benchmarks

1M rows, PHP 8.3 + opcache, each run in a fresh process. Micro = 1M × Sum::aggregate() on a prebuilt Row; e2e = data_frame()->read(from_csv(...))->aggregate(sum(ref('seller_id')))->run() on a 57 MB / 1M-row CSV.

Scenario 1.x this PR Δ
micro, int values 3.57 µs/row 0.52 µs/row ~7×
micro, float values 5.07 µs/row 0.56 µs/row ~9×
micro, float values, exact: true 5.10 µs/row ≈ previous default
e2e aggregate over 1M-row CSV 6.66–7.03 s 6.48–6.64 s ~−4–6%

The modest e2e delta is expected: the pipeline cost is dominated by Row/Entry construction (a separate concern); the sum's own share of the run drops from ~1.2 s to ~0.1 s. Peak memory unchanged (12 MB).

Resolves: no related roadmap item — standalone performance fix, happy to open an issue if preferred

Change Log


Added

  • FlowContext::calculator() backed by a shared Calculator instance held in Config
  • sum(ref('x'), exact: true) - opt-in arbitrary-precision (brick/math) summation; exact accepts ScalarFunction|bool, so the flag can also be driven per row by a scalar expression or a boolean column

Fixed

Changed

  • sum() aggregation accumulates through native int/float arithmetic by default (returning int whenever the sum has no fractional part) instead of routing every addition through BigDecimal
  • Sum (exact mode) and Average reuse the shared Calculator from FlowContext instead of constructing a new Calculator for every aggregated row

Removed

Deprecated

Security

…xact mode

Sum::aggregate() routed every addition through brick/math: a new
Calculator instance per row, two string casts and two BigDecimal
parses per addition. On a 1M-row aggregation that dominated the cost
of the most common aggregating function.

- sum() now accumulates through native int/float arithmetic and
  mirrors Calculator::add() by returning int whenever the running
  sum has no fractional part
- sum(ref('x'), exact: true) opts back into arbitrary-precision
  decimal arithmetic
- a shared Calculator instance lives in Config and is exposed through
  FlowContext::calculator(); Sum (exact mode) and Average pull it
  from the context instead of constructing one per row

Sum::aggregate() micro-benchmark, 1M rows: int 3.57 -> 0.45 us/row
(8x), float 5.07 -> 0.51 us/row (10x); exact mode keeps the previous
characteristics.
@codecov

codecov Bot commented Jul 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.97%. Comparing base (1b5c5f2) to head (0818bec).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff            @@
##                1.x    #2522   +/-   ##
=========================================
  Coverage     85.97%   85.97%           
- Complexity    22261    22271   +10     
=========================================
  Files          1663     1663           
  Lines         68418    68436   +18     
=========================================
+ Hits          58822    58840   +18     
  Misses         9596     9596           
Components Coverage Δ
etl 88.58% <100.00%> (+0.01%) ⬆️
cli 89.40% <ø> (ø)
lib-array-dot 81.44% <ø> (ø)
lib-azure-sdk 64.44% <ø> (ø)
lib-doctrine-dbal-bulk 93.61% <ø> (ø)
lib-filesystem 85.97% <ø> (ø)
lib-types 90.16% <ø> (ø)
lib-parquet 70.10% <ø> (ø)
lib-parquet-viewer 82.26% <ø> (ø)
lib-snappy 89.82% <ø> (ø)
lib-dremel 0.00% <ø> (ø)
lib-postgresql 88.62% <ø> (ø)
lib-telemetry 86.58% <ø> (ø)
bridge-filesystem-async-aws 92.74% <ø> (ø)
bridge-filesystem-azure 90.45% <ø> (ø)
bridge-monolog-http 96.82% <ø> (ø)
bridge-monolog-telemetry 94.79% <ø> (ø)
bridge-openapi-specification 92.07% <ø> (ø)
symfony-http-foundation 78.57% <ø> (ø)
bridge-psr18-telemetry 100.00% <ø> (ø)
bridge-psr3-telemetry 98.95% <ø> (ø)
bridge-psr7-telemetry 100.00% <ø> (ø)
bridge-telemetry-otlp 90.41% <ø> (ø)
bridge-symfony-http-foundation-telemetry 92.85% <ø> (ø)
bridge-symfony-filesystem-bundle 90.66% <ø> (ø)
bridge-symfony-filesystem-cache 98.18% <ø> (ø)
bridge-symfony-postgresql-bundle 93.39% <ø> (ø)
bridge-symfony-postgresql-cache 94.41% <ø> (ø)
bridge-symfony-postgresql-messenger 98.80% <ø> (ø)
bridge-symfony-postgresql-session 93.65% <ø> (ø)
bridge-symfony-telemetry-bundle 90.23% <ø> (ø)
adapter-chartjs 84.05% <ø> (ø)
adapter-csv 91.66% <ø> (ø)
adapter-doctrine 90.79% <ø> (ø)
adapter-google-sheet 99.18% <ø> (ø)
adapter-http 72.34% <ø> (ø)
adapter-json 88.71% <ø> (ø)
adapter-logger 50.00% <ø> (ø)
adapter-parquet 77.70% <ø> (ø)
adapter-text 74.13% <ø> (ø)
adapter-xml 83.40% <ø> (ø)
adapter-avro 0.00% <ø> (ø)
adapter-excel 94.21% <ø> (ø)
adapter-postgresql 91.06% <ø> (ø)
adapter-seal 85.42% <ø> (ø)
bridge-phpunit-postgresql 75.30% <ø> (ø)
bridge-phpunit-telemetry 87.32% <ø> (ø)
bridge-phpstan-types 0.00% <ø> (ø)
bridge-postgresql-valinor 100.00% <ø> (ø)
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@norberttech norberttech added the hackaton-07-2026 A first Flow Hackaton with Stloyd and MrHDOLEK 🎉 label Jul 11, 2026
@norberttech norberttech added this to the 0.42.0 milestone Jul 11, 2026
MrHDOLEK added 2 commits July 11, 2026 14:06
The exact flag follows the ScalarFunction|bool parameter convention:
it accepts a plain boolean, lit(...) or any scalar expression
evaluated per row (e.g. a boolean column). Plain booleans keep the
allocation-free fast path.
@norberttech norberttech merged commit 77844c9 into flow-php:1.x Jul 11, 2026
39 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in Roadmap Jul 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

hackaton-07-2026 A first Flow Hackaton with Stloyd and MrHDOLEK 🎉 size: S

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants