Skip to content

Label assignment sorts every chunk it does not need to: -26% wall at 10M variables #151

Description

@FBumann

Split out of #146, where the profiling lives. This is the actionable piece: one clause, measured, with the open questions that stop it being a trivial diff.

The statement

executor.py::_label_frame:

INSERT INTO {table}
SELECT {collist},
       {next_label} + ROW_NUMBER() OVER (ORDER BY {order_key}) - 1
FROM {from_clause}
WHERE t_{lead}.ord >= {lo} AND t_{lead}.ord < {hi} AND {where_clause}

The ORDER BY sorts every chunk. Isolated at 10M rows (16GB budget, best of three):

time
OVER (ORDER BY t_snapshot.ord, t_generator.ord) — current 4.03 s
OVER () 1.66 s
ord * card + ord (arithmetic, no window) 1.62 s
no label at all — the join floor 1.41 s

What it buys, end to end

A/B interleaved in one session, best of three, memory_limit=1GB:

case build total vs linopy
dispatch 10M — before 5.50 s 9.38 s 2.44x
dispatch 10M — after 3.48 s 6.97 s 1.81x
transport 9.8M — before 7.20 s 11.86 s 2.59x
transport 9.8M — after 5.44 s 10.24 s 2.24x

Peak RSS also drops (0.88 -> 0.74 GB on dispatch), which is consistent with the sort being what materialises the chunk.

Why the order is not load-bearing for correctness

Labels only have to be a dense bijection, and denseness comes from the chunk filter plus the running offset, not from the sort. What the ORDER BY fixes is which coordinate gets which index — determinism, not validity. Evidence: with the clause removed the suite is 311 passed / 1 failed, and the one failure is test_walkthrough_matches_golden, whose committed output prints primal rows in label order. Every differential and parity test against the linopy oracle passes.

Operational finding 3 already says LP section order is free.

What has to be checked before it lands

  1. solver_direct batching. A is streamed ORDER BY row, and within a row the column indices would no longer be ascending. HiGHS's addRows does not document a sorted-index requirement, but this needs a direct test rather than an assumption — it is the shipped path.
  2. Reproducibility (LP output is not byte-reproducible: the file sinks emit unordered COPYs #109). LP output is already not byte-reproducible, but scrambled labels make runs differ from each other in a new way. If LP output is not byte-reproducible: the file sinks emit unordered COPYs #109 is ever fixed, a deterministic label order is likely part of the fix, so the two decisions are coupled. OVER () under multiple threads gives no ordering guarantee at all.
  3. Locality of A. Ordered labels leave the COO roughly sorted, which may help the terminal GROUP BY row, col and the solver's CSR ingestion. The measurements above are net of any such loss on the lp_file path; solver_direct is unmeasured.
  4. The arithmetic alternative. For an unmasked frame the label is ord_lead * card_rest + ord_rest — no window at all, deterministic, and the same 1.6 s. It only works when nothing is filtered out, so it would be a fast path beside the window rather than a replacement. Worth it if determinism turns out to matter, since it keeps both properties.

The walkthrough golden would need regenerating either way; that file is asserted line for line on purpose, so the diff is the visible part of the decision.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:engineLowering, IR, relational executor, sinksengine:duckdbSpecific to the duckdb executor — invalidated if the polars path wins

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions