Skip to content

feat(perf): compute routing and lookup hashes once - #26249

Draft
devanbenz wants to merge 11 commits into
apache:mainfrom
devanbenz:partition_lookup_experiment
Draft

devanbenz wants to merge 11 commits into
apache:mainfrom
devanbenz:partition_lookup_experiment

Conversation

@devanbenz

@devanbenz devanbenz commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Work in progress/experiment, attempting to resolve a few performance issues with large CASE statements during dynamic filter scans. This commit reduces the amount of copies required during a predicate lookup. Specifically this was seen as a bottleneck on Q17 of TPC-H (~6s -> 2.20s): #26169 (comment)

query polars main (pushdown) this branch (pushdown) this branch / main this branch / polars
q01 2.00 2.59 2.82 1.09x 1.42x
q02 0.17 0.95 0.64 0.67x 3.74x
q03 1.50 2.65 2.09 0.79x 1.40x
q04 0.77 2.25 1.60 0.71x 2.08x
q05 1.71 3.25 2.41 0.74x 1.41x
q06 0.62 1.61 1.68 1.04x 2.71x
q07 1.95 3.43 2.38 0.69x 1.22x
q08 2.80 5.64 5.14 0.91x 1.84x
q09 4.92 9.14 7.95 0.87x 1.61x
q10 2.47 2.88 2.86 0.99x 1.16x
q11 0.16 0.90 0.49 0.54x 2.96x
q12 0.65 2.44 2.02 0.83x 3.11x
q13 2.41 3.16 2.83 0.90x 1.17x
q14 1.03 1.32 1.31 0.99x 1.27x
q15 0.98 3.24 3.13 0.97x 3.19x
q16 0.44 0.55 0.43 0.79x 0.99x
q17 1.41 5.57 2.20 0.39x 1.55x
q18 2.59 5.86 6.18 1.06x 2.39x
q19 1.09 1.73 1.76 1.02x 1.61x
q20 1.15 2.10 1.74 0.83x 1.52x
q21 3.62 12.76 11.12 0.87x 3.07x
q22 0.43 0.96 0.53 0.56x 1.23x
total 34.9 75.0 63.3 0.84x 1.82x

@github-actions github-actions Bot added the physical-plan Changes to the physical-plan crate label Oct 12, 2026
@codecov-commenter

codecov-commenter commented Oct 12, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.15284% with 34 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.92%. Comparing base (13a65f0) to head (810516f).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...physical-plan/src/joins/hash_join/shared_bounds.rs 81.90% 17 Missing and 2 partials ⚠️
...atafusion/physical-plan/src/joins/join_hash_map.rs 83.33% 8 Missing ⚠️
...fusion/physical-plan/src/joins/key_range_bitmap.rs 94.52% 0 Missing and 4 partials ⚠️
...usion/physical-plan/src/joins/stream_join_utils.rs 0.00% 3 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main   #26249    +/-   ##
========================================
  Coverage   82.92%   82.92%            
========================================
  Files        1154     1154            
  Lines      457790   458001   +211     
  Branches   457790   458001   +211     
========================================
+ Hits       379637   379815   +178     
- Misses      55135    55162    +27     
- Partials    23018    23024     +6     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@devanbenz devanbenz changed the title feat(perf): compute routing and lookup hashes for case once feat(perf): compute routing and lookup hashes once Oct 12, 2026

@tschwarzinger tschwarzinger left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I can gather, I like the approach of a specialized partitioning expression and after looking at the code for some time it makes sense. I also can't think of an easy way from the top of my head to improve the CASE evaluation in such a way that this specialized expression isn't necessary.

I've left a few notes that you can think of integrating. As I am not too familiar with the joining ecosystem I can't really approve it but to me the approach makes sense.

I think the only thing where this could be counter-productive is, if the data source can apply pruning with the CASE expression but not with the new special-purpose expression. I am not sure whether this happening or is even worth doing. Probably an optimization that transforms small multi-partition build sides into an IN LIST expression is the best way for this scenario.

Comment thread datafusion/physical-plan/src/joins/hash_join/partitioned_hash_eval.rs Outdated
Comment thread datafusion/physical-plan/src/joins/hash_join/partitioned_hash_eval.rs Outdated
Comment thread datafusion/physical-plan/src/joins/hash_join/shared_bounds.rs Outdated
};
merged_bounds = match (merged_bounds, real_partition_ids.len()) {
(None, 1) => Some(bounds.clone()),
(Some(merged), _) => merged.union(&bounds),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it expected that the union fails here or would that be a bug?

In case of the latter, maybe returning an error would be better than silently dropping it.

@devanbenz

Copy link
Copy Markdown
Contributor Author

@tschwarzinger I'm actually thinking more about this and how I can get away from specialization that requires adding a whole new Expr. I was thinking about maybe folding this functionality in to https://docs.rs/datafusion/latest/datafusion/physical_plan/joins/struct.HashTableLookupExpr.html and just slightly modifying the existing operator 🤔

I'll get back to this PR later today or tomorrow, but, that's where my head is at right now.

@adriangb

Copy link
Copy Markdown
Contributor

I am not sure whether this happening or is even worth doing. Probably an optimization that transforms small multi-partition build sides into an IN LIST expression is the best way for this scenario.

We already emit in list expressions for small builds sides, is there something I'm missing?

@devanbenz

devanbenz commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor Author

@tschwarzinger @adriangb

I think the only thing where this could be counter-productive is, if the data source can apply pruning with the CASE expression but not with the new special-purpose expression. I am not sure whether this happening or is even worth doing.

I was able to reproduce this and generate a table + query (with the help of my buddy claude) to get pruning in the CASE expr working, but, not working in my specialization:

Parquet file information:


 Probe: /tmp/df-prune/probe.parquet (2.7 MB)

 - Schema: k Int64 NOT NULL, v Int64 NOT NULL (v = k % 7)
 - Rows: 1,000,000, with k = 0…999999, sorted
 - Row groups: 100, each holding 10,000 rows with no overlap

 ┌───────────┬───────┬────────┬────────┐
 │ row group │ rows  │ k min  │ k max  │
 ├───────────┼───────┼────────┼────────┤
 │ 0         │ 10000 │ 0      │ 9999   │
 ├───────────┼───────┼────────┼────────┤
 │ 1         │ 10000 │ 10000  │ 19999  │
 ├───────────┼───────┼────────┼────────┤
 │ …         │ …     │ …      │ …      │
 ├───────────┼───────┼────────┼────────┤
 │ 98        │ 10000 │ 980000 │ 989999 │
 ├───────────┼───────┼────────┼────────┤
 │ 99        │ 10000 │ 990000 │ 999999 │

Results of pruning:

┌────────────────────────┬───────────────────────────────────────────────────────────┬─────────────────┐
 │ build                  │ probe scan filter                                         │ row groups read │
 ├────────────────────────┼───────────────────────────────────────────────────────────┼─────────────────┤
 │ base (9551051f1, main) │ CASE hash_repartition % 4 WHEN 0 THEN … AND hash_lookup … │ 2 of 100        │
 ├────────────────────────┼───────────────────────────────────────────────────────────┼─────────────────┤
 │ PR (4c7c51a5c)         │ k >= 0 AND k <= 999999 AND partitioned_hash_lookup(4)     │ 100 of 100      │
 └────────────────────────┴───────────────────────────────────────────────────────────┴─────────────────┘

Where the query is like so (including building parquet files):

-- Does the probe-side scan prune row groups using the hash join dynamic filter?
-- Base: CASE hash % N WHEN i THEN hash_lookup_i ... -> pruning sees per-partition KEY_RANGE_BITMAPs.
-- PR:   partitioned_hash_lookup(...) -> pruning only sees the merged [min, max] bounds.

-- probe: k = 0..999_999 sorted, 100 row groups of 10_000 rows
COPY (SELECT value AS k, value % 7 AS v FROM generate_series(0, 999999) ORDER BY k)
TO '/tmp/df-prune/probe.parquet' STORED AS PARQUET
OPTIONS ('format.max_row_group_size' '10000');

-- build: 2000 distinct keys (~500 per partition > 150 -> hash-table strategy, not IN LIST),
-- clustered at both ends so the [min, max] bounds cover the whole probe range
COPY (
  SELECT value AS k FROM generate_series(0, 999)
  UNION ALL
  SELECT value AS k FROM generate_series(999000, 999999)
) TO '/tmp/df-prune/build.parquet' STORED AS PARQUET;

CREATE EXTERNAL TABLE probe STORED AS PARQUET LOCATION '/tmp/df-prune/probe.parquet';
CREATE EXTERNAL TABLE build STORED AS PARQUET LOCATION '/tmp/df-prune/build.parquet';

-- force a Partitioned (multi-partition build) hash join with `build` on the build side
SET datafusion.execution.target_partitions = 4;
SET datafusion.optimizer.repartition_joins = true;
SET datafusion.optimizer.hash_join_single_partition_threshold = 0;
SET datafusion.optimizer.hash_join_single_partition_threshold_rows = 0;
SET datafusion.optimizer.join_reordering = false;

EXPLAIN ANALYZE
SELECT count(*), sum(p.v)
FROM build b JOIN probe p ON b.k = p.k;

SELECT count(*), sum(p.v) FROM build b JOIN probe p ON b.k = p.k;

Going to continue playing with this and getting it to a much better state.

@tschwarzinger

Copy link
Copy Markdown
Contributor

We already emit in list expressions for small builds sides, is there something I'm missing?

What I meant was over multiple partitions. I am not sure whether we have anything there.

For example:

CASE hash(key) % 2
   WHEN 0 THEN IN LIST (1)
   WHEN 1 THEN IN LIST (2)
END

becomes

key IN LIST (1,2)

@adriangb

Copy link
Copy Markdown
Contributor

Exploration also needs to consider number of partitions involved (i've looked at implementations before that are faster at 1->64 partitions but get slower after that), selectivity of the join, size of each side, pruning potential, size/length of the values being hashed, etc. There are many factors 😓

@tschwarzinger

Copy link
Copy Markdown
Contributor

Thank you for investigating that!

From what I can tell, the pruning for hash lookups is done via KeyRangeBitmapPruningExpr. Maybe if we can extend the existing hash lookup expression and we can support HashTableLookupExpr::pruning_bitmap for multiple partitions, the existing pruning implementation "comes for free".

@adriangb adriangb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this PR, @devanbenz.

I ran some benchmarks, results look good. TPC-H SF100, CPU time vs main, lower is better. N is the number of partitions:

Query N=16 N=128
Q3 0.74 0.52
Q5 0.72 0.46
Q9 0.64 0.38
Q21 0.67 0.41

This PR also looses pushdown:

-- Data: probe sorted by k, small row groups and pages; build has 5,000 sparse keys.
COPY (SELECT value AS k, value * 2 AS v FROM generate_series(0, 1999999) ORDER BY k)
  TO '/tmp/mre/probe.parquet' OPTIONS (max_row_group_size 20000, data_page_row_count_limit 2000);
COPY (SELECT value AS k FROM generate_series(0, 1999999) WHERE value % 200000 < 500)
  TO '/tmp/mre/build.parquet';

CREATE EXTERNAL TABLE probe STORED AS PARQUET LOCATION '/tmp/mre/probe.parquet';
CREATE EXTERNAL TABLE build STORED AS PARQUET LOCATION '/tmp/mre/build.parquet';
SET datafusion.execution.target_partitions = 16;
SET datafusion.execution.parquet.pushdown_filters = false;
SET datafusion.optimizer.hash_join_single_partition_threshold = 0;
SET datafusion.optimizer.hash_join_single_partition_threshold_rows = 0;
EXPLAIN ANALYZE SELECT count(*) FROM build b JOIN probe p ON b.k = p.k;

Probe DataSourceExec metrics:

main this PR
predicate CASE hash_repartition % 16 WHEN 0 THEN ... AND hash_lookup ... k@0 >= 0 AND k@0 <= 1800499 AND partitioned_hash_lookup(16)
pruning_predicate min/max AND KEY_RANGE_BITMAP(...) per partition min/max only
row_groups_pruned_statistics 100 total → 14 matched 100 total → 91 matched
page_index_rows_pruned 280.0 K total → 26.05 K matched 1.82 M total → 1.80 M matched
output_rows 26.05 K 1.80 M

Some more issues we should resolve before moving forward with this in review comments.

lookups.push(PartitionLookup::Map(Arc::clone(map)));
Some(lookups)
}
_ => None,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR falls back to CASE at high target_partitions. That is where the CASE path costs the most: its cost grows about linearly with N (microbenchmark: about 6 / 50 / 150 ns/row at N=1 / 16 / 128).

Since this arm returns None as soon as one partition is not a hash map, and at high N each partition gets few keys, so some partitions pick InList (the per-partition 150-distinct threshold). Then the whole filter falls back to CASE hash_repartition % N.

MRE (uses probe.parquet from the other comment):

SET datafusion.execution.target_partitions = 128;
SET datafusion.optimizer.hash_join_single_partition_threshold = 0;
SET datafusion.optimizer.hash_join_single_partition_threshold_rows = 0;
EXPLAIN ANALYZE SELECT count(*)
FROM (SELECT value AS k FROM generate_series(1, 19200)) b
JOIN probe p ON b.k * 97 = p.k;

HASH_JOIN_SEED,
lookups,
)) as Arc<dyn PhysicalExpr>;
let bounds = merged_bounds

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The merged bounds ignore canceled partitions; is that intended? It looks safe because a canceled partition’s rows have no consumer.

A CanceledUnknown partition pushes PartitionLookup::Const(true) but adds nothing to the bounds. The bounds predicate is then ANDed for every probe row. So a row routed to the canceled partition is removed if its key is outside the other partitions' range. That row can have a match in the canceled partition.

The CASE path keeps these rows, because each partition has its own bounds and a canceled partition gives true.

Possible fix (needs checking): do not use the merged bounds when a partition is canceled.

Suggested change
let bounds = merged_bounds
let bounds = merged_bounds.filter(|_| !has_canceled_unknown)

on_columns: Vec<PhysicalExprRef>,
routing_state: SeededRandomState,
lookup_state: SeededRandomState,
partitions: Vec<PartitionLookup>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to the pruning regression (see the comment on shared_bounds.rs): this expression has no equivalent of HashTableLookupExpr::pruning_bitmap(), so PruningPredicate cannot use it.

Suggestion:

  • Store one combined Option<Arc<KeyRangeBitmap>> here. It is the union of the per-partition bitmaps. If any partition has no bitmap (or is Const(true)), use None.
  • Expose it with pruning_bitmap(), and let pruning_predicate.rs handle PartitionedLookupExpr the same way as HashTableLookupExpr.
pub(crate) struct PartitionedLookupExpr {
    on_columns: Vec<PhysicalExprRef>,
    routing_state: SeededRandomState,
    lookup_state: SeededRandomState,
    partitions: Vec<PartitionLookup>,
    /// Union of the per-partition key-range bitmaps, for row group and page pruning.
    pruning_bitmap: Option<Arc<KeyRangeBitmap>>,
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adriangb I'm actually thinking about utilizing HashTableLookupExpr instead of creating a new specialized operator--while keeping it's API surface the same/internals the same if possible so I can utilize PruningPredicate and all the other niceties of HashTableLookupExpr.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also see: #26249 (comment) where my operator failed to prune while CASE did it as expected.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New changes allow pruning, I went ahead and utilized HashTableLookupExpr. Using

-- Data: probe sorted by k, small row groups and pages; build has 5,000 sparse keys.
COPY (SELECT value AS k, value * 2 AS v FROM generate_series(0, 1999999) ORDER BY k)
  TO '/tmp/mre/probe.parquet' OPTIONS (max_row_group_size 20000, data_page_row_count_limit 2000);
COPY (SELECT value AS k FROM generate_series(0, 1999999) WHERE value % 200000 < 500)
  TO '/tmp/mre/build.parquet';

CREATE EXTERNAL TABLE probe STORED AS PARQUET LOCATION '/tmp/mre/probe.parquet';
CREATE EXTERNAL TABLE build STORED AS PARQUET LOCATION '/tmp/mre/build.parquet';
SET datafusion.execution.target_partitions = 16;
SET datafusion.execution.parquet.pushdown_filters = false;
SET datafusion.optimizer.hash_join_single_partition_threshold = 0;
SET datafusion.optimizer.hash_join_single_partition_threshold_rows = 0;
EXPLAIN ANALYZE SELECT count(*) FROM build b JOIN probe p ON b.k = p.k;

Here is the DataSourceExec

|                   |             DataSourceExec: file_groups={16 groups: [[tmp/mre/probe.parquet:0..657492], [tmp/mre/probe.parquet:657492..1314984], [tmp/mre/probe.parquet:1314984..1972476], [tmp/mre/probe.parquet:1972476..2629968], [tmp/mre/probe.parquet:2629968..3287460], ...]}, projection=[k], output_ordering=[k@0 ASC NULLS LAST], file_type=parquet, predicate=DynamicFilter [ k@0 >= 0 AND k@0 <= 1800499 AND hash_lookup ], dynamic_rg_pruning=eligible, pruning_predicate=k_null_count@1 != row_count@2 AND k_max@0 >= 0 AND k_null_count@1 != row_count@2 AND k_min@3 <= 1800499 AND k_null_count@1 != row_count@2 AND KEY_RANGE_BITMAP(k_min@3, k_max@0, 2508/1048576 buckets), required_guarantees=[], metrics=[output_rows=26.05 K, elapsed_compute=18.74ms, output_bytes=896.0 KB, output_batches=14, files_ranges_pruned_statistics=16 total → 16 matched, row_groups_pruned_statistics=100 total → 14 matched, page_index_pages_pruned=140 total → 14 matched, page_index_rows_pruned=280.0 K total → 26.05 K matched, limit_pruned_row_groups=0 total → 0 matched, batches_split=0, bytes_processed=10.0 MB, bytes_scanned=328.0 KB, file_open_errors=0, file_scan_errors=0, files_opened=16, files_processed=16, num_predicate_creation_errors=0, page_index_load_skipped=5, predicate_evaluation_errors=0, pushdown_rows_matched=0, pushdown_rows_pruned=0, row_groups_pruned_dynamic_filter=0, predicate_cache_inner_records=0, predicate_cache_records=0, bloom_filter_eval_time=82.82µs, metadata_load_time=2.66ms, page_index_eval_time=2.14ms, row_pushdown_eval_time=43ns, statistics_eval_time=5.01ms, time_elapsed_opening=54.47ms, time_elapsed_processing=78.27ms, time_elapsed_scanning_total=42.34ms, time_elapsed_scanning_until_data=32.50ms, output_rows_skew=37.62%, scan_efficiency_ratio=3.19% (335.9 K/10.52 M)] |
|                   |

More specifically the pruning metrics:

metrics=[output_rows=26.05 K, elapsed_compute=18.74ms, output_bytes=896.0 KB, output_batches=14, files_ranges_pruned_statistics=16 total → 16 matched, row_groups_pruned_statistics=100 total → 14 matched, page_index_pages_pruned=140 total → 14 matched, page_index_rows_pruned=280.0 K total → 26.05 K matched,

@github-actions github-actions Bot added the auto detected api change Auto detected API change label Oct 19, 2026
@github-actions github-actions Bot removed the auto detected api change Auto detected API change label Oct 19, 2026
@github-actions github-actions Bot added core Core DataFusion crate sqllogictest SQL Logic Tests (.slt) labels Oct 20, 2026
Building one hash table over every build partition's keys ran on a single
thread at the dynamic filter barrier, so every probe stream waited on it
(about 0.7s for a 15M key build at TPC-H SF10).

HashTableLookupExpr now has a routed mode: each probe row is looked up in
the table of the build partition its key was repartitioned to (hash or
range routing), sharing the partitions' tables as they are. It stays a
HashTableLookupExpr, so pruning keeps using its merged pruning bitmap.

Removes union_hash_maps and JoinHashMapType::hash_values.
@devanbenz

devanbenz commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor Author

@adriangb When you get a moment could you please run your same benchmarks against this branch again. I pushed up some changes that I was cooking on over the past couple days to improve performance.

  • Removed the new specialized Expr and am using HashTableLookupExpr, so pruning is now working
  • Removed the union so we just collect pointers to existing hash tables to merge them, this was taking up most of the compute time in my previous impl.

Locally I'm seeing:

Config #26442 (geomean) #26442 (worst query) #26249 (geomean) #26249 (worst query) A/A control (main vs main)
pushdown on, 16 partitions 0.76 [0.68, 0.83] 1.02 (Q1) 0.76 [0.69, 0.83] 1.01 (Q1) 0.99
pushdown on, 128 partitions 0.47 [0.38, 0.57] 1.01 (Q6) 0.48 [0.40, 0.59] 1.01 (Q6) 1.00
pushdown off, 16 partitions 1.02 [1.01, 1.04] 1.17 (Q9) 1.01 [1.00, 1.03] 1.23 (Q18)* 1.00
pushdown off, 128 partitions 0.98 [0.90, 1.04] 1.42 (Q9) 0.95 [0.87, 1.00] 1.07 (Q7) 1.00

* Caused by one outlier round (241.8s vs ~125s in the other two rounds).
(171.8s vs ~127s).

Using the following machine

i7-12700K (20 threads, x86), 32 GB RAM


With pushdown filters off - all tpch:

TPC-H SF10, wall time in seconds (median of 5 hot runs, lower is better). PR #26249 at `1e3caf793`, PR #26442 at `4b3684cae`.
Default settings (`pushdown_filters = false`, 20 partitions), each query in its own process.
query PR #26442 PR #26249 #26249 / #26442
q01 0.439 0.447 1.02x
q02 0.109 0.100 0.91x
q03 0.303 0.290 0.96x
q04 0.134 0.133 0.99x
q05 0.502 0.474 0.94x
q06 0.123 0.127 1.04x
q07 0.792 0.647 0.82x
q08 0.417 0.364 0.87x
q09 0.882 0.629 0.71x
q10 0.409 0.402 0.98x
q11 0.072 0.068 0.95x
q12 0.138 0.134 0.97x
q13 0.301 0.300 1.00x
q14 0.190 0.189 0.99x
q15 0.363 0.358 0.98x
q16 0.086 0.088 1.01x
q17 0.744 0.751 1.01x
q18 0.704 0.638 0.91x
q19 0.240 0.242 1.01x
q20 0.320 0.333 1.04x
q21 1.047 0.995 0.95x
q22 0.063 0.063 1.01x
total 8.38 7.77 0.93x

With pushdown filters on - all tpch

query PR #26442 PR #26249 #26249 / #26442
q01 0.370 0.381 1.03x
q02 0.094 0.093 0.99x
q03 0.255 0.256 1.00x
q04 0.192 0.215 1.12x
q05 0.262 0.282 1.08x
q06 0.268 0.274 1.02x
q07 0.335 0.344 1.03x
q08 0.367 0.341 0.93x
q09 0.981 0.823 0.84x
q10 0.335 0.357 1.07x
q11 0.077 0.071 0.92x
q12 0.265 0.282 1.06x
q13 0.303 0.327 1.08x
q14 0.169 0.170 1.00x
q15 0.460 0.466 1.01x
q16 0.089 0.090 1.01x
q17 0.254 0.248 0.98x
q18 0.441 0.432 0.98x
q19 0.254 0.254 1.00x
q20 0.238 0.225 0.95x
q21 1.215 1.120 0.92x
q22 0.072 0.075 1.04x
total 7.30 7.13 0.98x

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core Core DataFusion crate physical-plan Changes to the physical-plan crate sqllogictest SQL Logic Tests (.slt)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants