Skip to content

[PERF] Reduce or eliminate intermediate matches arrays in conditional_join #1687

Description

@samukweku

Summary

conditional_join uses a flat matches array as a survivor mask while applying multiple predicates over candidate ranges. This avoids materializing all (left, right) pairs, but the mask can still be as large as the sum of all candidate range widths and is rescanned/reallocated across conditions.

Relevant implementation:

  • janitor/functions/_conditional_join/_helpers.py: _get_positive_matches and build_indices_matches
  • Rust comparison kernels receiving matches and counts_array

Problem

For a left row with candidate range [start, end), the current representation reserves one mask entry for every candidate, including candidates eliminated by earlier predicates. With broad ranges and several conditions, this can create substantial memory traffic even when the final result is selective.

The same intermediate state is also unnecessary for keep="first" and keep="last", where only one surviving right position per left row is required.

Possible directions

Investigate and benchmark the following alternatives:

  1. Fused predicate evaluation in Rust: evaluate all conditions in one candidate scan without an intermediate matches tape.
  2. Direct first/last selection: scan candidates in the requested direction and stop at the first complete match.
  3. Compact survivor representation: use CSR-style row offsets plus surviving right positions after selective predicates.
  4. Bit-packed masks: replace the int8 mask with a uint64 bitset if the tape remains necessary.
  5. Interval/segment survivors for predicates whose matches remain contiguous under sorted right-side data.

Suggested scope

Start with a benchmark and a fused/direct-selection implementation for keep="first" and keep="last"; retain the current path as a fallback for keep="all", irregular predicates, and aggregation paths until equivalent behavior is established.

Acceptance criteria

  • Preserve output ordering and null semantics, including nullable dtypes and !=.
  • Preserve join_agg, include_join_positions, and return_building_blocks behavior.
  • Compare peak memory and runtime against the current implementation for broad ranges, selective predicates, multiple conditions, and high-cardinality inputs.
  • Add regression tests covering empty ranges, duplicate values, nulls, sorted/unsorted right inputs, and all keep modes.
  • Document when the existing matches representation remains the preferred path.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions