Skip to content

perf: improve TTFX by type-erasing data movement - #61

Merged
Beforerr merged 1 commit into
mainfrom
push-xokklmvxvyto
Aug 31, 2026
Merged

Beforerr merged 1 commit into
mainfrom
push-xokklmvxvyto

Conversation

@Beforerr

Copy link
Copy Markdown
Member

What changed

  1. Type-erased data movement (src/loading/variable.jl). readblock! is now a thin wrapper; everything below it takes Ptr{UInt8} + element size + Vector{Int} dims/strides:
    • _read_records! — record range → bytes, threaded decompression compiled once per file format instead of per shape.
    • _copy_block! — one strided N-d gather kernel serving both the row→column majority swap and sub-block extraction.
    • Big-endian byte swap keyed on word size, not T.
  2. VVREntry records per-leaf whether it's a CVVR. The old code took the last leaf's type for the whole variable, which is wrong when a compressed variable stores a poorly-compressing block as a plain VVR.
  3. Array(var), collect(var), and show read via readblock! directly — DiskArrays' generic indexing costs ~60 ms compile per shape. Base.summary(::CDFVariable) is now cheap (Base's was ~190 ms/type).
  4. Workload (src/precompile.jl): seeds one variable per common shape (floats ≤3-D, TT2000/Epoch 1-D, strings ≤2-D) with the ops people actually run — show, attribs, Array, var[1], record slice, DateTime.. Rare shapes skipped.

@github-actions

github-actions Bot commented Aug 30, 2026 •

Copy link
Copy Markdown
Contributor

Benchmark Results (Julia v1)

Time benchmarks
main e8dc10c... main / e8dc10c...
elx/full_load 28 ± 1.4 μs 28.2 ± 1.5 μs 0.99 ± 0.073
elx/sum_array 2.2 ± 1.2 μs 2.14 ± 1.2 μs 1.03 ± 0.81
elx/sum_lazy 2.11 ± 1.2 μs 2.07 ± 1.2 μs 1.02 ± 0.85
elx/sum_var_access 2.47 ± 1.2 μs 2.32 ± 1.3 μs 1.06 ± 0.78
elx/var_access 0.257 ± 0.003 μs 0.248 ± 0.008 μs 1.04 ± 0.036
mms/full_load 14.4 ± 1.3 μs 14.8 ± 1.4 μs 0.974 ± 0.13
mms/sum_array 0.0904 ± 0.0036 s 0.0879 ± 0.0018 s 1.03 ± 0.046
mms/sum_slice 3.35 ± 0.36 ms 3.35 ± 0.082 ms 1 ± 0.11
mms/sum_var_access 3.36 ± 0.32 ms 3.34 ± 0.082 ms 1.01 ± 0.1
mms/var_access 0.094 ± 0.001 μs 0.096 ± 0.001 μs 0.979 ± 0.015
time_to_load 0.153 ± 0.0022 s 0.156 ± 0.0033 s 0.983 ± 0.025
Memory benchmarks
main e8dc10c... main / e8dc10c...
elx/full_load 0.217 k allocs: 24.8 kB 0.217 k allocs: 24.8 kB 1
elx/sum_array 12 allocs: 28 kB 12 allocs: 28 kB 1
elx/sum_lazy 12 allocs: 28 kB 12 allocs: 28 kB 1
elx/sum_var_access 14 allocs: 28.3 kB 14 allocs: 28.3 kB 1
elx/var_access 1 allocs: 0.234 kB 1 allocs: 0.234 kB 1
mms/full_load 0.065 k allocs: 6.45 kB 0.065 k allocs: 6.45 kB 1
mms/sum_array 1.04 k allocs: 31.7 MB 1.04 k allocs: 31.7 MB 1
mms/sum_slice 0.069 k allocs: 1.29 MB 0.069 k allocs: 1.29 MB 1
mms/sum_var_access 0.069 k allocs: 1.29 MB 0.069 k allocs: 1.29 MB 1
mms/var_access 1 allocs: 0.219 kB 1 allocs: 0.219 kB 1
time_to_load 0.149 k allocs: 11.2 kB 0.149 k allocs: 11.2 kB 1

@codecov

codecov Bot commented Aug 30, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.36082% with 9 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/loading/variable.jl 97.18% 4 Missing ⚠️
src/variable.jl 82.35% 3 Missing ⚠️
src/parsing.jl 86.66% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@Beforerr
Beforerr force-pushed the push-xokklmvxvyto branch 3 times, most recently from 8a94a21 to d538e41 Compare August 31, 2026 03:53
What changed

1. Type-erased data movement (src/loading/variable.jl). readblock! is now a thin wrapper; everything below it takes Ptr{UInt8} + element size + Vector{Int} dims/strides:
   - _read_records! — record range → bytes, threaded decompression compiled once per file format instead of per shape.
   - _copy_block! — one strided N-d gather kernel serving both the row→column majority swap and sub-block extraction.
   - Big-endian byte swap keyed on word size, not T.
2. Array(var), collect(var), and show read via readblock! directly — DiskArrays' generic indexing costs ~60 ms compile per shape. Base.summary(::CDFVariable) is now cheap (Base's was ~190 ms/type).
3. Workload (src/precompile.jl): seeds one variable per common shape (floats ≤3-D, TT2000/Epoch 1-D, strings ≤2-D) with the ops people actually run — show, attribs, Array, var[1], record slice, DateTime.. Rare shapes skipped.

refactor: VVREntry records per-leaf whether it's a CVVR

The old code took the last leaf's type for the whole variable, which is wrong when a compressed variable stores a poorly-compressing block as a plain VVR.
@Beforerr
Beforerr merged commit a3c02ad into main Aug 31, 2026
4 of 5 checks passed
@Beforerr
Beforerr deleted the push-xokklmvxvyto branch August 31, 2026 03:59
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