Skip to content

Optional: validate one file's lines across all cores - #27

Open
dionmcm wants to merge 1 commit into
upstream/structural-single-passfrom
upstream/structural-split-files
Open

Optional: validate one file's lines across all cores#27
dionmcm wants to merge 1 commit into
upstream/structural-single-passfrom
upstream/structural-split-files

Conversation

@dionmcm

@dionmcm dionmcm commented Aug 31, 2026

Copy link
Copy Markdown
Member

Optional. 1.38x for a meaningful amount of concurrency in a hot path. Raised separately from #26 so that trade is a separate decision; #26 is 2.86x without any of this.

Stacked on #26. One file, +122/-27.

Problem

After #26 the structural phase runs at about 2.2 of 8 cores. Work is parallel across files, and file sizes are skewed — the largest of 76 files is 20% of all bytes. Once the small files finish, two or three threads grind through the giants, so the largest file sets the floor.

A zip entry cannot be seeked, so byte ranges are not available. Each file keeps one sequential reader that hands batches of 2,000 lines to a worker pool, so one file's lines use every core.

Measured

Same host, same release, 8 cores:

column pass structural phase
#26 31.8s 38.5s
+ this 21.7s 27.8s

Both runs processed 75 files and 45,311,214 lines. Cumulatively from upstream: 110.3s to 27.8s.

Cost

  • Two pools. The outer is unbounded, holds one task per file, and mostly reads and waits; the inner is fixed at core count and validates. Nothing on the inner pool submits further work, so an outer task waiting on inner tasks cannot starve them. That argument is load-bearing and not obvious from the code.
  • A semaphore caps lines held in memory across all files, so a reader cannot outrun the validators.
  • The header is processed separately and before any data batch, because populateExtendedRefsetAdditionalFieldNames redefines the field list every data line is validated against. Correctness-critical ordering that did not exist when the loop was sequential.
  • Line numbers are carried into each batch rather than counted while reading.
  • Report rows for one file are no longer produced in line order. They carry line numbers, so anything sorting by them is unaffected.

Verification

clean upstream-develop   213 run, 0 failures, 142 errors, 21 skipped
#26                      235 run, 0 failures, 142 errors, 21 skipped
+ this PR                235 run, 0 failures, 142 errors, 21 skipped

Case against

The gain scales with cores left idle by the tail, so 1.38x on eight cores would be more on a 32 or 64-core node and less on four. Our sizing measurements recommend RVF workers at 4-8 cores, which is close to this change's least favourable case. #26 is worth merging regardless of what happens to this one.

@dionmcm
dionmcm force-pushed the upstream/structural-split-files branch from 469ee6c to 25d0dd2 Compare August 31, 2026 00:36
OPTIONAL. This buys 1.38x on an 8-core host for a meaningful amount of
concurrency machinery in a hot path, and the judgement of whether that trade is
worth taking is yours. Raised separately from the previous PR for exactly that
reason - that one is 2.86x with none of this risk.

After the previous PR the phase runs at about 2.2 of 8 cores, because work is
parallel across FILES and RF2 file sizes are extremely skewed: the largest of 76
files is 20% of all bytes. Once the small files are done, two or three threads
grind through the giants while the rest idle, so the largest single file sets the
floor.

A zip entry cannot be seeked, so byte ranges are not available. Instead each file
keeps ONE sequential reader that hands batches of 2,000 lines to a worker pool,
so a single file's lines use every core.

Measured on the same host and release:

  ColumnPatternTester   31.8s -> 21.7s
  structural phase      38.5s -> 27.8s    1.38x

Both runs processed 75 files and 45,311,214 lines.

What the complexity actually is, so it can be weighed:

* Two pools. The outer, unbounded, holds one task per file and mostly reads and
  waits; the inner is fixed at the core count and validates. Nothing on the inner
  pool submits further work, so an outer task waiting on inner tasks cannot
  starve them - the deadlock a single shared pool would risk.
* A semaphore caps lines held in memory across all files at once, so a reader
  cannot outrun the validators and fill the heap with a 1GB file.
* The header is now processed separately and before any data batch, because
  populateExtendedRefsetAdditionalFieldNames REDEFINES the field list that every
  data line is validated against. That ordering is correctness-critical and is
  easy for a later edit to break; it did not matter when everything was
  sequential.
* Line numbers are carried into each batch rather than counted as lines are read.
* Report rows for one file are no longer produced in line order. They carry line
  numbers, so anything sorting by them is unaffected, but anything relying on
  arrival order would notice.

Suite: 233 tests run, 0 failures, 142 errors, 21 skipped - identical to the
previous PR and the same 142 as clean develop.

Worth knowing before deciding: the gain scales with the cores left idle by the
tail, so 1.38x on eight cores would be considerably more on a 32 or 64-core node
and considerably less on four. If RVF workers are sized at 4-8 cores - which is
what our measurements recommend - this is close to its least favourable case.
@dionmcm
dionmcm force-pushed the upstream/structural-split-files branch from 25d0dd2 to 0f19305 Compare August 31, 2026 00:48
@dionmcm dionmcm changed the title OPTIONAL: validate one file's lines across all cores (1.38x, with real concurrency cost) Optional: validate one file's lines across all cores Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant