Skip to main content

Streaming (bigger than RAM)

iterate yields the chain in fully-materialized chunks. Each is dropped before the next is read, so resident memory stays at about one chunk — which is what lets a 10–100 GB input read in roughly constant RAM.

for chunk in f.iterate("REC::Particle", ["px"], step_size="200 MB"):
hist.fill(ak.flatten(chunk.px))

Each chunk has the same shape arrays() would give you for that slice of events, so anything that works on the whole array works on a chunk.

step_size

Either an event count or a byte budget:

f.iterate("REC::Particle", step_size=1_000_000) # events
f.iterate("REC::Particle", step_size="200 MB") # decompressed bytes
f.iterate("REC::Particle", step_size="1 GB")

Chunks are aligned to record and file boundaries, so a single oversized record is never split across chunks. Byte budgets are measured against decompressed record payloads, which is why "200 MB" is a memory statement rather than a statement about bytes read off disk.

Units: B, KB/MB/GB/TB (powers of 10) and KiB/MiB/GiB/TiB (powers of 2). A non-positive budget raises.

Progress reporting

report=True pairs each chunk with a Report:

for chunk, report in f.iterate("REC::Particle", step_size=1_000_000, report=True):
print(report.entry_start, report.entry_stop, report.file_path)

entry_start / entry_stop are global event indices; file_path is the file the chunk's records came from (chunks are file-aligned, so it's unambiguous).

Multi-file, without opening everything

The module-level iterate takes the same sources as open:

for chunk in ox.iterate("/data/run5042/*.hipo", "REC::Particle", step_size="1 GB"):
...

It's a generator, so a multi-file chain never opens more than it needs at once.

Combining with the other knobs

filter_name, entry_start / entry_stop, library=, threads=, and workers= all apply to iterate exactly as they do to arrays:

for chunk in f.iterate(filter_name="REC::*", library="np", step_size="500 MB"):
...

For the I/O-bound farm case, add workers=N — see Parallel reading.