Architecture¶
AMIO (Asynchronous Multidimensional I/O) is a C++20 library that decouples
scientific compute loops in NOAA NWS Earth system models from physical storage
architectures. Host applications interact with AMIO exclusively through a flat
C99 FFI surface and a Fortran 2003 iso_c_binding wrapper.
Design Principles¶
- Synchronous Snapshot, Asynchronous Serialize — A write call is synchronous with respect to capturing the host pointer (deep copy into staging pool), but asynchronous with respect to the backend write.
- Pointer-Swap Reads — Read calls return a non-owning
Memory_Viewthat points into a staging pool buffer already populated by the prefetch queue. - Strict ABI Isolation — Public headers contain only C99 types. All C++
dependencies are private to the
AMIO_Corebuild.
Layered Architecture¶
flowchart TB
subgraph HOST["Host Application - Fortran 2003+ / C / C++"]
FORTRAN["UFS / FV3 / JEDI Fortran kernels"]
C_HOST["C / C++ host code"]
end
subgraph FFI["AMIO Public ABI - C99 plus iso_c_binding"]
FORT_MOD["AMIO_Fortran_Module<br/>iso_c_binding wrappers"]
C_API["AMIO_C_API<br/>extern C linkage, void* handles"]
end
subgraph CORE["AMIO_Core - libamio, C++20, private build"]
BOUNDARY["C-Boundary<br/>handle table, error translation"]
VIEW["Memory_View<br/>kokkos/mdspan"]
STAGE["Staging_Pool"]
PFQ["Prefetch_Queue"]
WORKER["Worker_Pool<br/>eckit thread primitives"]
FACTORY["Backend_Factory<br/>eckit::Factory"]
CFG["Config_Loader<br/>eckit::YAMLConfiguration"]
DIAG["Diag / Exception Bridge<br/>eckit::Exception to AMIO_ERR_*"]
end
subgraph DRIVERS["Backend Drivers - private, polymorphic"]
NC4["NetCDF_Driver<br/>netCDF-cxx4 plus Parallel HDF5"]
ZARR["Zarr_Driver<br/>TensorStore or NCZarr fallback"]
G2["GRIB2_Driver<br/>nceplibs-g2c"]
end
subgraph STORAGE["Storage Layer"]
LUSTRE["Lustre / GPFS via MPI-IO"]
OBJ["S3 / GCS / HTTPS"]
FILE["POSIX files"]
end
FORTRAN --> FORT_MOD
FORT_MOD --> C_API
C_HOST --> C_API
C_API --> BOUNDARY
BOUNDARY --> VIEW
BOUNDARY --> CFG
VIEW --> STAGE
STAGE --> WORKER
PFQ --> WORKER
WORKER --> FACTORY
FACTORY --> NC4
FACTORY --> ZARR
FACTORY --> G2
NC4 --> LUSTRE
NC4 --> FILE
ZARR --> OBJ
ZARR --> FILE
G2 --> FILE
DIAG -.surfaces.-> BOUNDARY
WORKER -.exceptions.-> DIAG
End-to-End Write Path¶
The write path is the core of AMIO's design. The key invariant is that the
host pointer is safe to reuse immediately after amio_write returns.
sequenceDiagram
autonumber
participant H as Host (Fortran/C)
participant F as AMIO_Fortran_Module
participant C as AMIO_C_API (C-Boundary)
participant V as Memory_View (mdspan)
participant S as Staging_Pool
participant Q as Worker_Pool Queue
participant W as Worker thread
participant FX as Backend_Factory
participant D as Backend_Driver
participant ST as Storage layer
H->>F: amio_write_f(handle, buf, shape, dtype)
F->>C: amio_write via extern C
C->>V: wrap raw pointer + shape into mdspan view
C->>S: acquire buffer (capacity, timeout)
alt no buffer in time
S-->>C: backpressure
C-->>F: AMIO_ERR_STAGING_BACKPRESSURE
F-->>H: error, host pointer untouched
else buffer acquired
Note over V,S: Snapshot: host-to-host memcpy
V->>S: deep copy (synchronous)
C->>Q: enqueue WriteTask(buffer, dataset_id, var_id, seq)
C-->>F: write_handle (Opaque_Handle), success
F-->>H: returns within 10 ms, host may now mutate buf
end
Q->>W: dequeue (FIFO per dataset+variable)
W->>FX: lookup Backend_Driver for dataset
FX->>D: dispatch via eckit Factory
W->>D: encode buffer + metadata
D->>ST: serialize bytes (MPI-IO, HTTP, or file)
ST-->>D: ack or error
alt success
D-->>W: ok
W->>S: release buffer back to pool
else failure
D-->>W: throws eckit::Exception
W->>C: record failure against write_handle
W->>S: release buffer back to pool
end
Key Properties¶
- Pointer release boundary (between steps 6–7): when
amio_writereturns, no worker thread holds a pointer to the host buffer. - Order preservation: FIFO discipline per
(dataset, variable)with a sequence counter assigned at enqueue time.
End-to-End Read Prefetch Path¶
sequenceDiagram
autonumber
participant H as Host
participant C as AMIO_C_API
participant PFQ as Prefetch_Queue
participant W as Worker_Pool
participant D as Backend_Driver
participant ST as Storage
participant S as Staging_Pool buffer
Note over C,PFQ: open_read schedules N fetches ahead
C->>PFQ: schedule t in [0, N-1]
loop background prefetch
PFQ->>W: dequeue fetch task
W->>S: acquire buffer for timestep t
W->>D: read t (optional bbox/strides)
D->>ST: byte-range request
ST-->>D: bytes
D-->>W: decoded into Staging_Pool buffer
W->>PFQ: mark t complete
end
H->>C: amio_read(handle, t)
alt completed buffer for t
C->>S: take ownership ref
C-->>H: Memory_View (non-owning, points into S)
C->>PFQ: schedule t+N (maintain look-ahead)
else not completed
C->>W: wait t with read_timeout
alt completes in time
W-->>C: ready
C-->>H: Memory_View
else timeout
W-->>C: failure
C-->>H: AMIO_ERR_TIMEOUT
end
end
H->>C: amio_release_view(view_handle)
C->>S: drop ref, return buffer to pool if last ref
TensorStore Discovery and NCZarr Fallback¶
At CMake configure time, AMIO determines which Zarr backend to use:
flowchart TD
A[CMake Configure] --> B{AMIO_FORCE_NCZARR=ON?}
B -->|Yes| F[NCZarr fallback mode]
B -->|No| C{find_package TensorStore}
C -->|Found| D[TensorStore mode]
C -->|Not found| E{netCDF-c has NCZarr?}
E -->|Yes| F
E -->|No| G[FATAL_ERROR: no Zarr backend]
Memory Ownership Model¶
| Pointer / Handle | Allocated by | Owned by | Released when | Visible to host? |
|---|---|---|---|---|
| Host source pointer | Host application | Host | Host's discretion after amio_write returns |
Yes |
| Memory_View over host pointer (write) | C-Boundary stack | C-Boundary | End of C-Boundary call | No |
| Staging_Pool buffer (write) | amio_init |
Staging_Pool | Worker completes write task | No |
| Staging_Pool buffer (read) | amio_init |
Staging_Pool | Last Memory_View ref released |
No |
| Memory_View (read, returned to host) | C-Boundary | Host (via view_handle) | amio_release_view |
Yes |
| Opaque_Handle (core, dataset, io, view) | Handle table | C-Boundary | Corresponding finalize/close/release | Yes |
Build Target Topology¶
flowchart LR
subgraph PUBLIC["Public (installed)"]
PH["amio_public_headers<br/>(INTERFACE, C99-only)"]
CORE["amio_core<br/>(SHARED, libamio.so)"]
FORT["amio_fortran<br/>(STATIC)"]
end
subgraph PRIVATE["Private (not installed)"]
DNC["driver_netcdf<br/>(STATIC)"]
DZ["driver_zarr<br/>(STATIC)"]
DG["driver_grib2<br/>(STATIC)"]
end
FORT --> PH
FORT --> CORE
CORE --> PH
CORE -.PRIVATE.-> DNC
CORE -.PRIVATE.-> DZ
CORE -.PRIVATE.-> DG
Downstream consumers only see AMIO::amio_core (for C/C++) or
AMIO::amio_fortran (for Fortran). No third-party headers leak through
the public surface.