Skip to main content

Benchmarks: pond-ts vs pondjs

pond-ts is a ground-up TypeScript rewrite of pondjs. This page documents how the two libraries compare across all shared core operations.

Summary

pond-ts wins every benchmark — 54 out of 54 operations tested, across three data sizes (1k, 4k, 16k events). The geometric mean speedup is 7.6x, and the advantage grows with data size.

CategorySpeedup at N=16kKey architectural difference
Aggregation25–32xO(N+B) single-pass bucketing vs O(N×B) Pipeline
Alignment32xForward cursor vs repeated binary search
Rate/diff18xDirect array walk vs Pipeline materialization
Fill10–11xSingle-pass per strategy vs Pipeline per column
Transforms3–16xPre-validated constructor skips re-validation
Construction7xPlain frozen objects vs ImmutableJS wrapping
Statistics7–9xDirect array operations vs ImmutableJS iteration
Serialization4xLightweight internal representation
Event access23xArray indexing vs ImmutableJS get()

Zero regressions were found. The narrowest gap is map() at N=1000 (1.6x), still a clear win.

Why it's faster

Three architectural decisions account for most of the improvement:

1. No Pipeline overhead

pondjs routes every operation through a Pipeline abstraction — even simple transforms like select() or rename() construct a pipeline, push events through observable nodes, and collect output into keyed collections. This is flexible but adds constant overhead per event that dominates at scale.

pond-ts operates directly on arrays. Each transform walks the event array once, applies the operation, and passes results to a pre-validated constructor that skips redundant schema checks.

2. No ImmutableJS

pondjs wraps event data in ImmutableJS maps. Every get() call, every setData(), and every event construction pays for ImmutableJS overhead.

pond-ts uses plain frozen JavaScript objects. Object.freeze() provides the same immutability guarantee at near-zero cost. Event access is a property lookup, not a map traversal.

3. Algorithmic improvements

Several operations were re-implemented with better time complexity:

  • Aggregation: O(N+B) single-pass bucketing instead of O(N×B) window scanning per bucket.
  • Rolling windows: O(N) sliding window with incremental add/remove instead of O(N²) recomputation.
  • Alignment: Forward cursor that advances through the source array instead of binary search per output point.
  • includesKey(): O(log N) bisect instead of O(N) linear scan.

Running the benchmarks

npm run build
node bench/vs-pondjs.cjs

The benchmark script measures median execution time over 20 iterations (with 3 warmup rounds) at N = 1000, 4000, and 16000 events.

Detailed results

Construction

Operation N pondjs (ms) pond-ts (ms) Speedup
new TimeSeries() 1000 0.94 0.23 4.1x
new TimeSeries() 4000 2.91 0.53 5.5x
new TimeSeries() 16000 14.36 2.17 6.6x

Construction speedup grows with N because pondjs wraps each event's data in an ImmutableJS map during construction.

Aggregation

Operation N pondjs (ms) pond-ts (ms) Speedup
aggregate(10s, avg) 1000 1.67 0.19 8.9x
aggregate(1m, sum) 1000 0.91 0.08 11.0x
aggregate(10s, avg+max+min) 1000 1.86 0.20 9.1x
aggregate(10s, avg) 4000 4.89 0.20 24.9x
aggregate(1m, sum) 4000 3.86 0.12 31.2x
aggregate(10s, avg+max+min) 4000 7.09 0.34 21.2x
aggregate(10s, avg) 16000 20.35 0.83 24.7x
aggregate(1m, sum) 16000 15.39 0.49 31.5x
aggregate(10s, avg+max+min) 16000 32.41 1.33 24.5x

The largest speedups in the suite. pond-ts's O(N+B) bucket assignment means the number of buckets barely affects total time. pondjs's Pipeline-based fixedWindowRollup does more work per event.

Rate

Operation N pondjs (ms) pond-ts (ms) Speedup
rate(value) 1000 1.06 0.27 4.0x
rate(value) 4000 5.26 0.35 15.1x
rate(value) 16000 23.81 1.35 17.7x

pondjs routes rate() through the Pipeline. pond-ts walks the array once, computing deltas in place.

Fill

Operation N pondjs (ms) pond-ts (ms) Speedup
fill(hold/pad) 1000 0.67 0.29 2.3x
fill(zero) 1000 0.66 0.30 2.2x
fill(linear) 1000 0.65 0.31 2.1x
fill(hold/pad) 4000 3.02 0.81 3.7x
fill(zero) 4000 3.05 0.29 10.4x
fill(linear) 4000 3.09 0.29 10.5x
fill(hold/pad) 16000 12.55 1.20 10.5x
fill(zero) 16000 12.27 1.19 10.3x
fill(linear) 16000 12.66 1.18 10.7x

pondjs's fill() constructs a Pipeline per call. For linear fill with multiple columns, it constructs a separate Pipeline per column. pond-ts handles all strategies in a single pass.

Transforms

Operation N pondjs (ms) pond-ts (ms) Speedup
select(value) 1000 0.75 0.14 5.3x
map(x*2) 1000 0.73 0.44 1.6x
collapse(a+b+c, sum) 1000 1.17 0.27 4.3x
rename(value→measurement) 1000 0.82 0.20 4.2x
select(value) 4000 3.68 0.26 14.2x
map(x*2) 4000 3.43 1.02 3.4x
collapse(a+b+c, sum) 4000 5.09 0.83 6.1x
rename(value→measurement) 4000 4.28 0.45 9.6x
select(value) 16000 16.07 1.01 15.9x
map(x*2) 16000 15.39 4.79 3.2x
collapse(a+b+c, sum) 16000 23.40 3.63 6.4x
rename(value→measurement) 16000 21.43 1.67 12.8x

map() shows the narrowest gap because both libraries must call a user-provided function per event. The difference is entirely in construction overhead: pond-ts uses a pre-validated internal constructor path for derived series.

Alignment

Operation N pondjs (ms) pond-ts (ms) Speedup
align(5s, linear) 1000 1.10 0.13 8.8x
align(5s, linear) 4000 4.19 0.17 24.7x
align(10s, linear) 16000 14.13 0.44 32.3x

pond-ts uses a forward cursor that advances through the source array in sync with the output sequence. pondjs uses binary search per output point, giving O(M log N) vs pond-ts's O(N + M).

Event access

Operation N pondjs (ms) pond-ts (ms) Speedup
at(i).get() full scan 1000 0.23 0.08 2.9x
at(i).get() full scan 4000 0.59 0.17 3.5x
at(i).get() full scan 16000 2.55 0.11 22.9x

Pure data access. The gap is ImmutableJS get() vs plain property lookup.

Serialization

Operation N pondjs (ms) pond-ts (ms) Speedup
toJSON() 1000 0.41 0.14 3.0x
toJSON() 4000 1.27 0.28 4.5x
toJSON() 16000 5.70 1.31 4.4x

Chained operations

Operation N pondjs (ms) pond-ts (ms) Speedup
map → select 1000 1.52 0.35 4.3x
map → select 4000 7.27 1.23 5.9x
map → select 16000 32.25 6.31 5.1x

Each step in pondjs creates a new Pipeline and materializes a new collection. pond-ts chains derived constructors without re-validating the output.

Statistics

Operation N pondjs (ms) pond-ts (ms) Speedup
median(value) 1000 0.29 0.07 4.3x
stdev(value) 1000 0.23 0.07 3.5x
median(value) 4000 1.65 0.25 6.5x
stdev(value) 4000 1.07 0.14 7.9x
median(value) 16000 9.70 1.31 7.4x
stdev(value) 16000 5.22 0.57 9.1x

Capabilities only in pond-ts

Beyond performance, pond-ts adds functionality that pondjs does not have:

  • Live streaming: LiveSeries, LiveView, LiveAggregation, LiveRollingAggregation
  • Live composition: chain filter → diff → fill → aggregate on streaming data
  • filter() as a first-class TimeSeries method (pondjs requires Pipeline)
  • diff(), pctChange(), cumulative(), shift() columnar primitives
  • groupBy() with optional transform callback
  • bfill (backward fill) strategy
  • TypeScript-first schema types that flow through every operation