8000 bench: add benchmarks for window functions, temporal metadata, UDFs, and post-7.4 perf fixes · Issue #81 · project-minigraf/minigraf · GitHub
[go: up one dir, main page]

Skip to content

bench: add benchmarks for window functions, temporal metadata, UDFs, and post-7.4 perf fixes #81

Closed

Description

@adityamukho

Background

The benchmark suite (benches/minigraf_bench.rs) and BENCHMARKS.md were last updated at Phase 7.4 / v0.13.1. Since then, Phases 7.5–7.7b have added several new query features and a series of O(n²)→O(n log n) performance fixes, none of which have corresponding benchmarks.

This issue tracks all missing benchmarks and the required BENCHMARKS.md update.


Missing benchmark groups

1. Window functions (Phase 7.7a)

Window functions (running_sum, running_count, running_avg, rank, row_number, etc.) run incrementally over an ordered result set using the AggState accumulator path. They are fundamentally different from batch aggregates (separate code path in the executor) and need their own benchmark group.

Add a bench_window function:

fn bench_window(c: &mut Criterion) {
    // Setup: DB with N entities, each with a :val Integer attribute.
    // Query: (query [:find ?e (running_sum ?v) :where [?e :val ?v]])
    // Measure at 1K and 10K facts, sample_size(100).
    let mut group = c.benchmark_group("window/running_sum");
    for &n in &[1_000usize, 10_000] {
        let label = helpers::label(n);
        let db = helpers::preload_db(n);  // helper that inserts N :val facts
        group.bench_with_input(BenchmarkId::from_parameter(label), &n, |b, _| {
            b.iter(|| {
                db.execute("(query [:find ?e (running_sum ?v) :where [?e :val ?v]])")
                  .unwrap()
            });
        });
    }
    group.finish();

    // Also benchmark rank / row_number to capture ordering overhead.
    let mut group = c.benchmark_group("window/rank");
    // similar structure
    group.finish();
}

Register it in criterion_group!(benches, ..., bench_window, ...).


2. Temporal metadata queries (Phase 7.6)

Phase 7.6 added pseudo-attributes ?tx-time, ?valid-from, ?valid-to (and ?tx-count, ?asserted) that can be bound in query :where clauses. These require extra projection work per result row and have not been measured.

Add a bench_temporal_metadata function:

fn bench_temporal_metadata(c: &mut Criterion) {
    // Query: (query [:find ?e ?t :where [?e :val _ :tx-time ?t]])
    // Measure at 1K and 10K facts.
    let mut group = c.benchmark_group("temporal_metadata/tx_time");
    for &n in &[1_000usize, 10_000] {
        let db = helpers::preload_db(n);
        group.bench_with_input(BenchmarkId::from_parameter(helpers::label(n)), &n, |b, _| {
            b.iter(|| {
                db.execute("(query [:find ?e ?t :where [?e :val ?v :tx-time ?t]])")
                  .unwrap()
            });
        });
    }
    group.finish();

    // Also benchmark :valid-from / :valid-to projection.
}

3. UDF aggregates and predicates (Phase 7.7b)

Minigraf::register_aggregate and Minigraf::register_predicate let callers plug in custom Rust functions. The overhead of the closure dispatch path vs. the built-in function-pointer path should be measured.

Add a bench_udf function:

fn bench_udf(c: &mut Criterion) {
    // Register a UDF aggregate that sums integers (same semantics as built-in sum).
    // Run at 1K and 10K, compare against built-in sum to isolate dispatch overhead.
    let mut group = c.benchmark_group("udf/aggregate_sum_dispatch");
    for &n in &[1_000usize, 10_000] {
        let db = helpers::preload_db(n);
        db.register_aggregate("udf_sum", /* ... closure ... */).unwrap();
        group.bench_with_input(BenchmarkId::from_parameter(helpers::label(n)), &n, |b, _| {
            b.iter(|| {
                db.execute("(query [:find (udf_sum ?v) :where [?e :val ?v]])")
                  .unwrap()
            });
        });
    }
    group.finish();

    // Also benchmark a UDF predicate filter.
}

4. Post-7.4 performance fixes — re-run affected groups

Several O(n²) bugs were fixed after Phase 7.4. The existing benchmark numbers in BENCHMARKS.md predate these fixes and are now stale. Re-run these groups and update the table values:

Fix PR Affected benchmark group
count-distinct/sum-distinct O(n log n) #73 aggregation/count_scale, new aggregation/count_distinct_scale
Aggregation grouping O(n log n) #71 aggregation/grouped_count_scale, aggregation/with_grouped_sum
Or/OrJoin deduplication O(n log n) #68 disjunction/or_scale, disjunction/or_join_scale
contains_fact O(n) in semi-naive #72 recursion/chain, recursion/fanout
Regex precompile at parse time #67 any query with matches? predicate — add a query/regex_filter sub-benchmark
Query optimizer index hints #69 query/point_entity, query/join_3pattern

For count-distinct, also add a dedicated benchmark that exercises the distinct-dedup path with many duplicates:

// In bench_aggregation, add:
let mut group = c.benchmark_group("aggregation/count_distinct_scale");
// 1K and 10K facts, 50% duplicate values.

BENCHMARKS.md update instructions

After running the new and updated benchmarks, update BENCHMARKS.md as follows:

  1. Add new sections after the existing "Expression Clauses" section:

    • ## Window Functions — table with running_sum, rank, row_number at 1K / 10K, plus a brief O(N) scaling note.
    • ## Temporal Metadata — table with tx_time, valid_from, valid_at projection at 1K / 10K.
    • ## UDF Dispatch Overhead — table comparing UDF aggregate vs. built-in aggregate at 1K / 10K.
  2. Update stale numbers in:

    • ## Aggregation — update count_scale, grouped_count_scale, with_grouped_sum 10K numbers; add count_distinct_scale row.
    • ## Disjunction — update or_scale, or_join_scale 10K numbers.
    • ## Recursive Rules — update chain and fanout numbers.
    • ## Query Latency — update point_entity 10K if optimizer hints produce a measurable change; add regex_filter row if a new sub-benchmark is added.
  3. Add a Phase 7.4 → current summary table (similar to the existing "Phase 7.3 → Phase 7.4 Summary" table) capturing the before/after for each perf fix. Example structure:

    ## Phase 7.4 → v0.17.0 Performance Summary
    
    | Fix | Metric | Before | After | Change |
    |---|---|---|---|---|
    | count-distinct O(n log n) | aggregation/count_distinct_scale 10K | ? ms | ? ms | ? |
    | aggregation grouping O(n log n) | aggregation/grouped_count_scale 10K | 51.550 ms | ? ms | ? |
    | Or dedup O(n log n) | disjunction/or_scale 10K | 68.929 s | ? s | ? |
    | contains_fact O(n) | recursion/chain depth_10 | 2.75 ms | ? ms | ? |
  4. Update the version reference at the top of BENCHMARKS.md from v0.13.1 to the current version and update the "Core query benchmarks were updated in..." line.


How to run

# Run all benchmarks (slow — ~30 min total)
cargo bench

# Run only new groups
cargo bench -- "window"
cargo bench -- "temporal_metadata"
cargo bench -- "udf"

# Re-run specific stale groups
cargo bench -- "aggregation"
cargo bench -- "disjunction"
cargo bench -- "recursion"

Criterion writes HTML reports to target/criterion/. Copy the median time from each report into the relevant BENCHMARKS.md table.


Helpers

The benches/helpers/mod.rs module contains preload_db(n) and related setup. Add any new fixture helpers there (e.g., a preload_db_with_duplicates(n, dup_fraction) for the count-distinct benchmark, or a preload_db_with_temporal(n) that inserts facts with explicit valid_from/valid_to for the temporal metadata benchmark).

Acceptance criteria

  • bench_window function added and registered in criterion_group!
  • bench_temporal_metadata function added and registered
  • bench_udf function added and registered
  • count_distinct_scale sub-benchmark added to bench_aggregation
  • regex_filter sub-benchmark added to bench_query (for matches? predicate)
  • All stale 10K numbers in BENCHMARKS.md updated with post-fix measurements
  • New sections added to BENCHMARKS.md for window, temporal metadata, UDF
  • Phase 7.4 → v0.17.0 summary table added to BENCHMARKS.md
  • BENCHMARKS.md version reference updated at top

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestperformanceBenchmark performance regressions

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      0