prefetch_group
Stable type · Prefetch and windows
Tier: stable · Kind: type · Topic: Prefetch and windows
| Python | Julia | Rust |
|---|---|---|
PrefetchGroup | PrefetchGroup | PrefetchGroup |
Python
class PrefetchGroup
PrefetchGroup(members: Mapping, *, depth: int = 2, max_bytes: Optional[int] = None, fetch_ahead: Optional[int] = None, workers: int = 1, windows: Optional[Sequence[Any]] = None, transform: Optional[Callable[[str, Any], Any]] = None, executor: str = 'thread', _decode_latency: Optional[Callable[[], float]] = None)
PrefetchGroup.boundaries() -> List[_dt.datetime]
PrefetchGroup.start(order: Any = 'forward', from_: Optional[int] = None) -> None
PrefetchGroup.take(k: int) -> EpochSample
PrefetchGroup.ready(k: int) -> bool
PrefetchGroup.cancel(wait: bool = False) -> None
PrefetchGroup.close() -> None
PrefetchGroup.stats() -> Dict[str, Any]
Background decode of upcoming forcing epochs (spec/prefetch.md §3).
Parameters
members(Mapping) —{key: Provider}, every one DISCRETE with an enumerable schedule (non-emptyrefresh_times). A CONST or unbounded member, or an empty map, is aValueError: materialize a CONST source once, outside the group.depth(int) — Epoch slots held ahead of the consumer, decoded or decoding, counting the onetakereturns next (at least 1).max_bytes(Optional[int]) — Optional cap on the bytes of the held slots’ member samples (array bytes beforetransform; a sample shared by several slots counts once). A slot is admitted only if what is held or in flight plus its estimate — each member’s last decoded size — fits; while some size is still unknown, only when nothing else is held. The head slot is always admitted, alone if it is oversize (stats()["oversize_epochs"]).fetch_ahead(Optional[int]) — Slots past the decode window whose files are downloaded (not decoded) into the disk cache; defaults todepth,0disables. Best-effort: it never blocks a take, its errors are dropped (the decode raises the real one), and store-backed and offline members are skipped (there is nothing such a warm could download).workers(int) — Decode workers (threads, or processes withexecutor="process").windows(Optional[Sequence[Any]]) — Optional named windows: each member’s data becomessample_windows_at(B[k], windows). Every window must satisfywith_window’s preconditions for every member, checked here.transform(Optional[Callable[[str, Any], Any]]) — Optionaltransform(key, data) -> Trun on the worker after the decode; its errors are stored like decode errors.executor(str) —"thread"(default) or"process"(members andtransformmust pickle; checked here).
Members
boundaries— The sorted, de-duplicated union of the members’refresh_times. Epochkis[B[k], B[k+1]); the last is open on the right.start— Declare the order epochs will be taken in, cancelling any previous one.take— Block until epochk— the next entry of the order — is decoded, and return it.ready— Whethertake(k)would return at once:kis the next entry of the order and its slot is decoded (or failed — its error is still raised only bytake). Never blocks, never raises for a decode error;Falsebeforestart, aftercancel, and for any epoch other than the next one, decoded or not.cancel— Drop queued work at once. A decode already running (inside a C library, it cannot be interrupted) finishes and its result is discarded;wait=Truereturns only after those decodes finish.takeis an error until the nextstart.close—cancel(wait=True), then join the workers; the group is unusable afterwards. Idempotent.stats— Cumulative statistics since construction (startdoes not reset them):waited_s,decode_s,peak_bytes,epochs_decoded,samples_decoded,samples_reused,oversize_epochs,overlap(§3.9).
Julia
mutable struct PrefetchGroup
PrefetchGroup(keys::Vector{String}, providers::Vector{Provider}, B::Vector{Float64}, depth::Int64, max_bytes::Union{Nothing, Int64}, fetch_ahead::Int64, nworkers::Int64, windows::Union{Nothing, Vector{Dict{String, Any}}}, transform, overlap::Bool, decode_hook, lock::ReentrantLock, cond::Base.GenericCondition{ReentrantLock}, status::Symbol, order::Vector{Int64}, next::Int64, admitted::Int64, stop_after::Int64, window::Vector{EarthSciIO._PrefetchSlot}, queue::Vector{EarthSciIO._PrefetchJob}, live::Dict{Tuple{Int64, Float64}, EarthSciIO._PrefetchJob}, last_taken::Vector{Union{Nothing, EarthSciIO._PrefetchJob}}, last_anchor::Vector{Union{Nothing, Float64}}, last_bytes::Vector{Int64}, running::Int64, workers::Vector{Task}, fetch_queue::Vector{Tuple{Int64, String}}, warmed::Set{String}, warm_through::Int64, fatal, waited_s::Float64, decode_s::Float64, peak_bytes::Int64, epochs_decoded::Int64, samples_decoded::Int64, samples_reused::Int64, oversize_epochs::Int64)
PrefetchGroup(keys, providers, B, depth, max_bytes, fetch_ahead, nworkers, windows, transform, overlap, decode_hook, lock, cond, status, order, next, admitted, stop_after, window, queue, live, last_taken, last_anchor, last_bytes, running, workers, fetch_queue, warmed, warm_through, fatal, waited_s, decode_s, peak_bytes, epochs_decoded, samples_decoded, samples_reused, oversize_epochs)
PrefetchGroup(members::AbstractDict; depth, max_bytes, fetch_ahead, workers, windows, transform, _decode_hook, _overlap)
PrefetchGroup(members; depth = 2, max_bytes = nothing, fetch_ahead = nothing,
workers = 1, windows = nothing, transform = nothing)
Decode upcoming forcing epochs of several DISCRETE providers on background
workers, in a declared order, under a memory budget — bitwise identical to calling
[sample_at] / [refresh] synchronously (spec/prefetch.md §3).
members maps a key (a string; any key is taken as string(key)) to a DISCRETE
[Provider]. All members must share one time unit — the providers’ own Float64
cadence unit — which the group cannot check. A CONST member (materialize it once,
outside the group) or an empty map is an error at construction.
- [
boundaries](g)— the sorted unionBof the members’refresh_times. Epochk(1-based) is[B[k], B[k+1]); per member it issample_at(B[k]), so a coarser member repeats its sample across the epochs that snap to one anchor (decoded once, served again:samples_reused). - [
start!](g, order; from = nothing)—:forward,:reverse, or an explicitVector{Int}of epochs (repeats legal: a revolve schedule).from = ktrims the order to begin at its first entry equal tok(a restart). Cancels any previous order. take!(g, k)— blocks until epochk— which must be the next entry of the order — is decoded, and returns its [EpochSample]. A stored decode error is raised HERE, never earlier, as the errorrefreshwould raise (an offline cold cache raises [CacheMiss], not a wrapper); the order is then over.isready(g, k)— non-blocking: wouldtake!(g, k)return at once?- [
cancel!](g; wait = false)— drop queued work; a decode already running completes and is discarded (wait = truewaits for it). Reusable afterstart!. close(g)—cancel!(g; wait = true), then join the workers; the group is unusable afterwards. Always close a group: its workers wait on it until then.- [
stats](g)— cumulativewaited_s,decode_s,peak_bytes,epochs_decoded,samples_decoded,samples_reused,oversize_epochsandoverlap.
The budget (§3.6): depth (≥ 1) bounds the epoch slots held ahead of the consumer,
decoded or decoding, counting the one take! returns next. max_bytes bounds
their bytes — a member’s array bytes before transform, strings at their UTF-8
length, a sample shared by several slots counted once; a slot is admitted only if
it fits beside what is held, estimated from each member’s last decoded size, and
while some size is still unknown only when nothing else is held. The head slot is
always admitted, so a slot larger than max_bytes is still decoded, alone, and
counted in oversize_epochs. fetch_ahead (default depth, 0 disables) warms
the disk cache — downloads, never decodes — for that many slots past the decode
window, best-effort and skipped for store-backed members. The Julia Provider holds
no decoded file, so there is no decoded-file reuse to bound beyond max_bytes.
windows (a vector of [DimWindow]s or wire Dicts) makes each member’s data
the vector sample_windows_at(p, B[k], windows); every window must pass the
[with_window] preconditions for every member, checked here. transform(key, data) post-processes a sample on the worker; its result is data, and its error
is stored like a decode error.
workers decode tasks are spawned with Threads.@spawn at the first start!. With
Threads.nthreads() == 1 there is nothing to overlap with: the group then decodes
inside take! and reports overlap = false. NetCDF decodes are serialised by the
reader’s process-wide lock; zarr decodes run concurrently.
Two keywords exist for tests only: _decode_hook(key, t) is called on the worker
before each decode (to randomise decode latency, so completion order varies), and
_overlap = false forces the decode-inside-take! fallback.
Rust
pub struct PrefetchGroup<T = NativeDataset> {
/* private fields */
}
impl PrefetchGroup<NativeDataset> { pub fn builder() -> PrefetchBuilder<NativeDataset, NativeDataset> }
impl<T: Send + Sync + 'static> PrefetchGroup<T> { pub fn boundaries(&self) -> Vec<f64> }
impl<T: Send + Sync + 'static> PrefetchGroup<T> { pub fn start(&mut self, order: Order, from: Option<usize>) -> Result<()> }
impl<T: Send + Sync + 'static> PrefetchGroup<T> { pub fn take(&mut self, k: usize) -> Result<EpochSample<T>> }
impl<T: Send + Sync + 'static> PrefetchGroup<T> { pub fn ready(&self, k: usize) -> bool }
impl<T: Send + Sync + 'static> PrefetchGroup<T> { pub fn cancel(&mut self, wait_for_running: bool) }
impl<T: Send + Sync + 'static> PrefetchGroup<T> { pub fn close(&mut self) }
impl<T: Send + Sync + 'static> PrefetchGroup<T> { pub fn stats(&self) -> PrefetchStats }
Background decode of upcoming epochs across several providers, bitwise
identical to synchronous Provider::refresh. See the module documentation
and spec/prefetch.md §3.
use std::sync::Arc;
use earthsciio::{DimWindow, Order, PrefetchGroup};
let w = DimWindow::new().range("lat", 120, 218).range("lon", 300, 497);
let mut g = PrefetchGroup::builder()
.member("U", Arc::new(p_u.with_window(w.clone())?))
.member("V", Arc::new(p_v.with_window(w)?))
.depth(2).max_bytes(8 << 30).workers(4)
.build()?;
let b: Vec<f64> = g.boundaries(); // Unix seconds, as refresh_times
g.start(Order::Forward, None)?;
for k in 0..b.len() {
let ep = g.take(k)?;
let u = ep.get("U").unwrap();
if u.changed { /* copy u.data into the forcing buffer */ }
}
// Drop cancels and joins.