Skip to content

Make longitude range lazy for lazy Grid constructors - #1791

Open
cmdupuis3 wants to merge 7 commits into
UXARRAY:mainfrom
cmdupuis3:cmd/lazy-lon-range
Open

cmdupuis3 wants to merge 7 commits into
UXARRAY:mainfrom
cmdupuis3:cmd/lazy-lon-range

Conversation

@cmdupuis3

@cmdupuis3 cmdupuis3 commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #1792

Overview

_set_desired_longitude_range does if da.max() > 180 on a lazy reduction, three times (node_lon, edge_lon, face_lon). Re-fires on every Grid construction, so every isel and every copy() pays it again.

The basic idea is to use a memoized xr.where so that we don't need to materialize the whole dimension to view the values that are available per chunk.

PR Checklist

General

  • An issue is created and linked
  • Added appropriate labels (if your uxarray repo permissions allow it)
  • Filled out Overview and Expected Usage (if applicable) sections

Testing & Benchmarking

  • There is adequate test coverage of changes from this PR (add new tests if needed)
  • If this PR could affect performance, ran ASV benchmarks and confirmed they show expected behavior (add a new benchmark if necessary)

Documentation and Examples

  • Docstrings updated with any function changes, and included in all new functions
  • User (public) functions added to docs/api.rst; internal (private) function names start with an underscore (_)

AI Disclosure

AI Usage: Claude Opus 5.5

  • I have tested and take responsibility for all AI-generated content in my PR.

cmdupuis3 and others added 3 commits September 25, 2026 18:46
_set_desired_longitude_range decided whether to wrap by asking
lon.max() > 180. On a dask-backed coordinate that reduction is a compute,
and Grid.__init__ calls it -- so opening a chunked grid read and reduced
every longitude array before the caller had asked for anything, and did it
again on every isel and every copy(), each of which builds a new Grid.

The wrap is now xr.where((lon > 180) | (lon < -180), (lon + 180) % 360 - 180,
lon): elementwise, lazy, chunk-parallel, no reduction.

Measured on a synthetic 4M-node UGRID file, chunked at 500k nodes, best of 5:

                       dask computes    wall      tracemalloc peak
    open_grid (before)             3    23.3ms          12.2 MB
    open_grid (after)              2    16.2ms           1.0 MB
    isel      (before)             2
    isel      (after)              1

The computes that remain are a separate site on connectivity rather than
coordinates: _standardize_connectivity's conn.isnull().any() in io/_ugrid.py,
reached twice on the read path, and _slice_face_indices in grid/slice.py
materializing the connectivity it slices by. Neither is touched here.

Three behavioural differences, all from doing this per element rather than
per array.

  * In-range longitudes are now left exactly alone. (lon + 180) - 180 does
    not round-trip, so wrapping the whole array perturbed values that were
    already in range by up to 3e-14 degrees -- in outCSne30, 2.1182935e-14
    became 2.8421709e-14. On the elements that do need wrapping the two forms
    are bit-identical.

  * Longitudes below -180 are normalized. The old test was on the maximum
    alone, so it reached the negative tail only when the same array also held
    a value above 180.

  * Both endpoints are kept, so the interval is the closed [-180, 180].
    Folding 180.0 to -180.0 would match _xyz_to_lonlat_deg, which wraps
    unconditionally into the half-open interval, but it breaks
    antimeridian_face_indices: that reads a face as crossing from the span of
    its longitudes, and a face with one vertex at 180 and the rest near -170
    goes from a span of 350 to a span of 10 and disappears. Caught by
    test_antimeridian_point_on and
    test_to_geodataframe_preserves_antimeridian_faces.

Each variable is wrapped at most once, keyed on the xr.Variable object.
Without that, edge_lat -- which calls this on every property access,
outside its populate guard -- would stack a where layer onto the graph per
access. Keying on the Variable makes the memo
self-invalidating: assigning into _ds replaces that object, so a repopulated
or user-assigned coordinate is wrapped again.

The two Exodus round-trip tests compared a grid against its own
lon -> xyz -> lon reload with assert_allclose(rtol=1e-8), and passed only
because the old whole-array wrap applied to the original the identical
perturbation the reload applies. With the original left alone, the reload's
own error is exposed, and rtol is the wrong instrument for it twice over: a
longitude near zero has no magnitude for a relative tolerance to measure
against (outCSne30 nodes 4372, 4749, 7e-15 degrees apart), and longitude is
periodic, so a node on or one ulp short of the antimeridian reads 180.0 on
the original and -180.0 on the reload -- the same meridian, scored as a
360-degree error (179 nodes of outRLL1deg; outCSne30 nodes 3966, 5155).
Those two assertions now compare the difference modulo 360 to an absolute
tolerance, still ERROR_TOLERANCE. The helper still catches a 1e-7 shift and
still rejects an antipode.

Tier 0.2 of the chunked refactor plan.

Test suite: 962 passed, 1 skipped. test_plot_with_features fails identically
before and after (matplotlib figure size, unrelated).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The elementwise wrap is the right shape for a dask-backed coordinate and the
wrong one for an array already in memory. There was never a compute to defer
on that path, only a scan, and elementwise costs a pass plus ~18 bytes per
node of temporaries -- two bool masks, the arithmetic, the result -- where a
reduction costs a pass and allocates nothing.

That matters because every open_grid/open_dataset call in the benchmark suite
is eager; none pass chunks=. So the previous commit, measured there, was a
regression and nothing else.

Measured on a synthetic 4M-node UGRID file, best of 5. The wrap in isolation:

                              in range         0..360
    old reduction               4.6ms  0MB    36.8ms  64MB
    elementwise only           20.9ms 72MB    38.1ms  72MB
    elementwise + guard         9.3ms  0MB    42.7ms  72MB

and through eager open_grid, where the file read dominates and the peak does
not move at all (180.0 MB in every arm):

                              in range         0..360
    base (cmd/nogil)           145.5ms         181.9ms
    elementwise only           164.4ms         186.1ms
    elementwise + guard        147.9ms         185.9ms

_lon_within_range is a guard, not a decision: when it is true the wrap is the
identity on every element, so skipping it cannot change a value.
test_eager_fast_path_agrees_with_the_where_element_for_element asserts that
directly against the unguarded expression rather than assuming it, over five
inputs including both endpoints, the negative tail and NaN.

The reductions are the thing that made the old code compute, so they are
allowed only where there is nothing to defer -- da.chunks is None. A
dask-backed array skips the guard entirely, which
test_guard_is_skipped_for_dask_backed_arrays pins by asserting the guard does
compute when handed one. The chunked numbers are unchanged: 2 graph
executions, 16.0ms, 1.0 MB peak.

`and` short-circuits, so an array that does need wrapping usually pays a
single max -- the same reduction the old code paid -- before falling through.
The 0..360 column above is that extra max: ~4.6ms on a 190ms open_grid.

Test suite: 973 passed, 1 skipped. test_plot_with_features fails identically
before and after (matplotlib figure size, unrelated).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every other open_grid in the suite is eager, so none of it can see what
Grid construction does to a dask-backed grid -- which is the path the lazy
longitude wrap changed, and the path the rest of the chunked refactor will
keep changing. OpenGridChunked adds time_open_grid and
track_peakmem_open_grid, parametrized over the oQU 480km and 120km meshes
already registered in helpers/_fixtures.py.

Chunks are held at N_CHUNKS=4 per grid dimension, so the graph is the same
shape at both resolutions and only the data under it grows. The file is read
directly rather than through CachedFixtures, because reading it is the
subject.

On MPAS this is where the lazy wrap matters most. node_lon, edge_lon and
face_lon all exist at construction, so the old max() > 180 check ran three
computes per open; the branch runs none. Measured with this benchmark's own
setup, best of 15, against HEAD's tree with coordinates.py taken from
cmd/nogil:

                      time               tracemalloc peak
                  base    branch        base    branch
    480km       60.5ms    56.5ms      3.27MB    3.27MB
    120km       42.3ms    38.0ms      3.53MB    4.02MB

The 120km peak reads higher on the branch, and it is not data. With gc
disabled, building the where graph allocates ~1.9 MB of transient objects,
nearly all in inspect.signature via dask/xarray op dispatch -- the same at
both resolutions and with a single chunk, so it does not scale with the
grid. At 480km it sits under the HDF5 read's own high-water mark; at 120km
the netCDF3 read is cheap enough that it becomes the peak. After a
gc.collect() the branch retains ~30 kB more than base, which is the extra
graph layers. Worth knowing for later steps: this benchmark sees
graph-construction overhead, not only bytes read.

Three things the numbers above depend on:

  * Compare each resolution to its own history, not to the other. The two
    files are different formats -- oQU480.grid.nc is netCDF4/HDF5,
    oQU120.grid.nc is netCDF3 -- and the HDF5 open costs more, so 480km reads
    slower than 120km despite a sixteenth of the data.

  * Each open gets a fresh copy of the chunks dict. match_chunks_to_ugrid
    (core/utils.py) writes the source-format dimension names into the
    dict it is handed, so a reused one gives every sample after the first a
    different argument.

  * The warning "The specified chunks separate the stored chunks" is
    filtered. oQU480 stores layerThickness, ssh and zMid as one chunk of all
    1791 cells, so any n_face chunking splits them and xarray warns once per
    open -- about data variables the grid reader drops.

Checked by calling the class the way asv does (setup(param), then the
time_/track_ methods) under -W error::UserWarning. Not run through asv
itself: its discovery subprocess cannot import uxarray from the uxarray
conda env, where the package is not installed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cmdupuis3 cmdupuis3 self-assigned this Sep 25, 2026
@cmdupuis3 cmdupuis3 added scalability Related to scalability & performance efforts run-benchmark Run ASV benchmark workflow labels Sep 25, 2026
@github-actions

github-actions Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

ASV Benchmarking

Benchmark Comparison Results

Benchmarks that have improved:

Change Before [9609c3a] After [d9cd4ae] Ratio Benchmark (Parameter)
- 69.0±2ms 59.9±1ms 0.87 bench_connectivity.Connectivity.time_node_face('120km')
- 352M 318M 0.9 face_bounds.FaceBoundsColdStartRss.track_peakmem_open_and_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
- 352M 318M 0.9 face_bounds.FaceBoundsColdStartRss.track_peakmem_open_and_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
- 352M 319M 0.91 face_bounds.FaceBoundsColdStartRss.track_peakmem_open_and_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
- 352M 319M 0.91 face_bounds.FaceBoundsColdStartRss.track_peakmem_open_and_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
- 352M 275M 0.78 import.Imports.track_peakmem_import_uxarray
- 2 1 0.5 lazy_grid_construction.LazyGridConstruction.track_computes_isel
- 3 2 0.67 lazy_grid_construction.LazyGridConstruction.track_computes_open_grid_chunked
- 74.5±2ms 64.7±1ms 0.87 lazy_grid_construction.OpenGridChunked.time_open_grid('120km')
- 2.81M 2.11M 0.75 lazy_grid_construction.OpenGridChunked.track_peakmem_open_grid('120km')
- 352M 312M 0.89 mpas_ocean.GradientColdStartRss.track_peakmem_gradient('480km')

Benchmarks that have stayed the same:

Change Before [9609c3a] After [d9cd4ae] Ratio Benchmark (Parameter)
3.87±0.04ms 3.76±0.03ms 0.97 bench_connectivity.Connectivity.time_edge_face('120km')
1.35±0.01ms 1.35±0.02ms 1.00 bench_connectivity.Connectivity.time_edge_face('480km')
3.08±0.06ms 3.06±0.02ms 0.99 bench_connectivity.Connectivity.time_edge_node('120km')
1.03±0.01ms 1.04±0.02ms 1.01 bench_connectivity.Connectivity.time_edge_node('480km')
3.09±0.04ms 3.11±0.06ms 1.01 bench_connectivity.Connectivity.time_face_edge('120km')
1.04±0.01ms 1.03±0.01ms 0.99 bench_connectivity.Connectivity.time_face_edge('480km')
4.36±0.04ms 4.34±0.03ms 1.00 bench_connectivity.Connectivity.time_face_face('120km')
1.61±0ms 1.62±0.02ms 1.01 bench_connectivity.Connectivity.time_face_face('480km')
34.6±0.6μs 36.2±0.4μs 1.04 bench_connectivity.Connectivity.time_face_node('120km')
34.1±0.8μs 35.6±0.6μs 1.04 bench_connectivity.Connectivity.time_face_node('480km')
351±8μs 357±10μs 1.02 bench_connectivity.Connectivity.time_n_nodes_per_face('120km')
236±2μs 246±4μs 1.04 bench_connectivity.Connectivity.time_n_nodes_per_face('480km')
4.12±0.04ms 4.09±0.02ms 0.99 bench_connectivity.Connectivity.time_node_edge('120km')
1.35±0.01ms 1.34±0.01ms 0.99 bench_connectivity.Connectivity.time_node_edge('480km')
3.71±0.04ms 3.65±0.1ms 0.98 bench_connectivity.Connectivity.time_node_face('480km')
5.89±0.08ms 5.92±0.09ms 1.00 face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
1.89±0.03ms 1.86±0.05ms 0.98 face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
5.82±6s 7.34±9ms ~0.00 face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
1.05±0.02ms 1.02±0.01ms 0.97 face_bounds.FaceBounds.time_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
57.3k 57.3k 1.00 face_bounds.FaceBounds.track_nbytes_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
12.3k 12.3k 1.00 face_bounds.FaceBounds.track_nbytes_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
123k 123k 1.00 face_bounds.FaceBounds.track_nbytes_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
128 128 1.00 face_bounds.FaceBounds.track_nbytes_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
1.27M 1.27M 1.00 face_bounds.FaceBounds.track_nbytes_grid_with_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
50.1k 50.1k 1.00 face_bounds.FaceBounds.track_nbytes_grid_with_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
1.48M 1.48M 1.00 face_bounds.FaceBounds.track_nbytes_grid_with_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
712 712 1.00 face_bounds.FaceBounds.track_nbytes_grid_with_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
2.03M 2.03M 1.00 face_bounds.FaceBounds.track_peakmem_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/mpas/QU/oQU480.231010.nc'))
2.03M 2.03M 1.00 face_bounds.FaceBounds.track_peakmem_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/scrip/outCSne8/outCSne8.nc'))
2.19M 2.19M 1.00 face_bounds.FaceBounds.track_peakmem_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/geoflow-small/grid.nc'))
35.5k 35.5k 1.00 face_bounds.FaceBounds.track_peakmem_face_bounds(PosixPath('/home/runner/work/uxarray/uxarray/test/meshfiles/ugrid/quad-hexagon/grid.nc'))
857±20ns 837±9ns 0.98 geometry_kernels.AccucrossKernels.time_accucross
1.89±0.02μs 1.92±0.01μs 1.01 geometry_kernels.AccucrossKernels.time_accucross_pair
316±10ns 310±0.4ns 0.98 geometry_kernels.EFTPrimitives.time_acc_sqrt_re
310±20ns 306±20ns 0.98 geometry_kernels.EFTPrimitives.time_diff_of_products
270±8ns 270±9ns 1.00 geometry_kernels.EFTPrimitives.time_two_prod
276±10ns 265±10ns 0.96 geometry_kernels.EFTPrimitives.time_two_sum
511±9ns 521±20ns 1.02 geometry_kernels.GCAConstLatIntersection.time_gca_const_lat_intersection
546±10ns 546±9ns 1.00 geometry_kernels.GCAConstLatIntersection.time_try_gca_const_lat_intersection
571±9ns 576±20ns 1.01 geometry_kernels.GCAGCAIntersection.time_accux_gca_kernel
641±20ns 661±10ns 1.03 geometry_kernels.GCAGCAIntersection.time_gca_gca_intersection
741±20ns 741±5ns 1.00 geometry_kernels.GCAGCAIntersection.time_try_gca_gca_intersection
29.0±4μs 28.6±0.4μs 0.98 geometry_kernels.OrientPredicates.time_on_minor_arc
28.8±0.2μs 28.8±0.2μs 1.00 geometry_kernels.OrientPredicates.time_orient3d_on_sphere
2.41±0ms 2.42±0.01ms 1.00 geometry_samebody.SameBodyConstLat.time_accux_dispatch
1.05±0.05ms 1.02±0ms 0.97 geometry_samebody.SameBodyConstLat.time_accux_kernel
1.64±0ms 1.65±0.01ms 1.00 geometry_samebody.SameBodyConstLat.time_fp64_dispatch
121±2μs 121±0.1μs 1.00 geometry_samebody.SameBodyConstLat.time_fp64_kernel
22.9±0.2ms 22.9±0.06ms 1.00 geometry_samebody_gcagca.SameBodyGcaGca.time_accux_dispatch
5.53±0ms 5.53±0.01ms 1.00 geometry_samebody_gcagca.SameBodyGcaGca.time_accux_kernel
17.9±0.03ms 17.9±0.2ms 1.00 geometry_samebody_gcagca.SameBodyGcaGca.time_fp64_dispatch
699±2μs 695±4μs 0.99 geometry_samebody_gcagca.SameBodyGcaGca.time_fp64_kernel
709±4ms 711±10ms 1.00 import.Imports.timeraw_import_uxarray
96.3±2ms 88.8±1ms 0.92 lazy_grid_construction.OpenGridChunked.time_open_grid('480km')
2.06M 2.2M 1.07 lazy_grid_construction.OpenGridChunked.track_peakmem_open_grid('480km')
1.56±0.01ms 1.55±0.02ms 0.99 mpas_ocean.CheckNorm.time_check_norm('120km')
1.20±0.02ms 1.22±0.03ms 1.01 mpas_ocean.CheckNorm.time_check_norm('480km')
792±6μs 797±3μs 1.01 mpas_ocean.ConnectivityConstruction.time_face_face_connectivity('120km')
384±10μs 389±9μs 1.01 mpas_ocean.ConnectivityConstruction.time_face_face_connectivity('480km')
519±10μs 523±6μs 1.01 mpas_ocean.ConnectivityConstruction.time_n_nodes_per_face('120km')
428±8μs 420±8μs 0.98 mpas_ocean.ConnectivityConstruction.time_n_nodes_per_face('480km')
3.42±0.01ms 3.54±0.01ms 1.04 mpas_ocean.ConstructFaceLatLon.time_cartesian_averaging('120km')
2.55±0.01ms 2.79±0ms 1.09 mpas_ocean.ConstructFaceLatLon.time_cartesian_averaging('480km')
79.7±0.5ms 79.1±0.6ms 0.99 mpas_ocean.ConstructFaceLatLon.time_welzl('120km')
8.04±0.3ms 7.52±0.2ms 0.94 mpas_ocean.ConstructFaceLatLon.time_welzl('480km')
16.3±0.01ms 16.2±0.01ms 0.99 mpas_ocean.ConstructTreeStructures.time_ball_tree('120km')
819±50μs 842±50μs 1.03 mpas_ocean.ConstructTreeStructures.time_ball_tree('480km')
8.23±0.04ms 8.23±0.02ms 1.00 mpas_ocean.ConstructTreeStructures.time_kd_tree('120km')
490±20μs 480±20μs 0.98 mpas_ocean.ConstructTreeStructures.time_kd_tree('480km')
416±5ms 452±6ms 1.09 mpas_ocean.CrossSections.time_const_lat('120km', 1)
211±3ms 227±3ms 1.08 mpas_ocean.CrossSections.time_const_lat('120km', 2)
112±2ms 116±1ms 1.03 mpas_ocean.CrossSections.time_const_lat('120km', 4)
374±2ms 398±0.3ms 1.07 mpas_ocean.CrossSections.time_const_lat('480km', 1)
185±2ms 199±2ms 1.08 mpas_ocean.CrossSections.time_const_lat('480km', 2)
95.4±2ms 101±2ms 1.06 mpas_ocean.CrossSections.time_const_lat('480km', 4)
352M 338M 0.96 mpas_ocean.CrossSectionsPeakMem.track_peakmem_const_lat('120km', 1)
352M 338M 0.96 mpas_ocean.CrossSectionsPeakMem.track_peakmem_const_lat('120km', 2)
352M 338M 0.96 mpas_ocean.CrossSectionsPeakMem.track_peakmem_const_lat('120km', 4)
352M 321M 0.91 mpas_ocean.CrossSectionsPeakMem.track_peakmem_const_lat('480km', 1)
352M 321M 0.91 mpas_ocean.CrossSectionsPeakMem.track_peakmem_const_lat('480km', 2)
352M 321M 0.91 mpas_ocean.CrossSectionsPeakMem.track_peakmem_const_lat('480km', 4)
18.2±0.2ms 18.3±0.2ms 1.01 mpas_ocean.DualMesh.time_dual_mesh_construction('120km')
2.10±0.06ms 2.13±0.02ms 1.01 mpas_ocean.DualMesh.time_dual_mesh_construction('480km')
11.0±0.4ms 11.3±0.4ms 1.02 mpas_ocean.FaceAreas.time_face_areas('120km')
2.99±0.4ms 2.89±0.3ms 0.97 mpas_ocean.FaceAreas.time_face_areas('480km')
229k 229k 1.00 mpas_ocean.FaceAreas.track_nbytes_face_areas('120km')
14.3k 14.3k 1.00 mpas_ocean.FaceAreas.track_nbytes_face_areas('480km')
2.12M 2.12M 1.00 mpas_ocean.FaceAreas.track_peakmem_face_areas('120km')
728k 728k 1.00 mpas_ocean.FaceAreas.track_peakmem_face_areas('480km')
654±2ms 647±2ms 0.99 mpas_ocean.GeoDataFrame.time_to_geodataframe('120km', False)
39.2±1ms 39.1±0.5ms 1.00 mpas_ocean.GeoDataFrame.time_to_geodataframe('120km', True)
56.5±0.1ms 57.2±0.5ms 1.01 mpas_ocean.GeoDataFrame.time_to_geodataframe('480km', False)
4.00±0.3ms 4.03±0.2ms 1.01 mpas_ocean.GeoDataFrame.time_to_geodataframe('480km', True)
10.8±0.3ms 10.8±0.3ms 1.00 mpas_ocean.Gradient.time_gradient('120km')
1.34±0.02ms 1.31±0.01ms 0.98 mpas_ocean.Gradient.time_gradient('480km')
457k 457k 1.00 mpas_ocean.Gradient.track_nbytes_gradient('120km')
28.7k 28.7k 1.00 mpas_ocean.Gradient.track_nbytes_gradient('480km')
3.2M 3.2M 1.00 mpas_ocean.Gradient.track_peakmem_gradient('120km')
204k 204k 1.00 mpas_ocean.Gradient.track_peakmem_gradient('480km')
352M 333M 0.95 mpas_ocean.GradientColdStartRss.track_peakmem_gradient('120km')
199±5μs 204±4μs 1.02 mpas_ocean.HoleEdgeIndices.time_construct_hole_edge_indices('120km')
97.6±2μs 98.7±0.7μs 1.01 mpas_ocean.HoleEdgeIndices.time_construct_hole_edge_indices('480km')
139±1μs 143±2μs 1.02 mpas_ocean.Integrate.time_integrate('120km')
126±2μs 129±0.8μs 1.02 mpas_ocean.Integrate.time_integrate('480km')
18.4M 18.4M 1.00 mpas_ocean.Integrate.track_nbytes_integrate('120km')
1.2M 1.2M 1.00 mpas_ocean.Integrate.track_nbytes_integrate('480km')
146±0.7ms 147±0.6ms 1.01 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('120km', 'exclude')
144±0.9ms 147±0.9ms 1.02 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('120km', 'include')
146±0.9ms 148±1ms 1.01 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('120km', 'split')
10.4±0.09ms 10.3±0.09ms 1.00 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('480km', 'exclude')
10.4±0.07ms 10.3±0.06ms 0.99 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('480km', 'include')
10.6±0.1ms 10.3±0.1ms 0.97 mpas_ocean.MatplotlibConversion.time_dataarray_to_polycollection('480km', 'split')
210±0.4ms 211±0.4ms 1.00 mpas_ocean.NeighborhoodBuild.time_build('120km', 1.0)
1.11±0s 1.11±0s 1.00 mpas_ocean.NeighborhoodBuild.time_build('120km', 15.0)
437±2ms 437±0.5ms 1.00 mpas_ocean.NeighborhoodBuild.time_build('120km', 5.0)
11.5±0.2ms 11.4±0.2ms 0.99 mpas_ocean.NeighborhoodBuild.time_build('480km', 1.0)
21.7±0.1ms 21.6±0.09ms 1.00 mpas_ocean.NeighborhoodBuild.time_build('480km', 15.0)
14.0±0.1ms 14.0±0.07ms 1.00 mpas_ocean.NeighborhoodBuild.time_build('480km', 5.0)
207±0.7ms 206±0.2ms 1.00 mpas_ocean.NeighborhoodBuild.time_query_radius('120km', 1.0)
1.10±0s 1.10±0.01s 1.00 mpas_ocean.NeighborhoodBuild.time_query_radius('120km', 15.0)
430±0.7ms 430±0.4ms 1.00 mpas_ocean.NeighborhoodBuild.time_query_radius('120km', 5.0)
11.0±0.02ms 11.2±0.08ms 1.01 mpas_ocean.NeighborhoodBuild.time_query_radius('480km', 1.0)
21.1±0.07ms 21.0±0.02ms 0.99 mpas_ocean.NeighborhoodBuild.time_query_radius('480km', 15.0)
13.7±0.03ms 13.6±0.02ms 1.00 mpas_ocean.NeighborhoodBuild.time_query_radius('480km', 5.0)
1.19 1.19 1.00 mpas_ocean.NeighborhoodBuild.track_mean_neighbors('120km', 1.0)
612.76 612.76 1.00 mpas_ocean.NeighborhoodBuild.track_mean_neighbors('120km', 15.0)
74.17 74.17 1.00 mpas_ocean.NeighborhoodBuild.track_mean_neighbors('120km', 5.0)
1.0 1.0 1.00 mpas_ocean.NeighborhoodBuild.track_mean_neighbors('480km', 1.0)
37.29 37.29 1.00 mpas_ocean.NeighborhoodBuild.track_mean_neighbors('480km', 15.0)
6.57 6.57 1.00 mpas_ocean.NeighborhoodBuild.track_mean_neighbors('480km', 5.0)
728k 728k 1.00 mpas_ocean.NeighborhoodBuild.track_nbytes_neighbors('120km', 1.0)
141M 141M 1.00 mpas_ocean.NeighborhoodBuild.track_nbytes_neighbors('120km', 15.0)
17.4M 17.4M 1.00 mpas_ocean.NeighborhoodBuild.track_nbytes_neighbors('120km', 5.0)
43k 43k 1.00 mpas_ocean.NeighborhoodBuild.track_nbytes_neighbors('480km', 1.0)
563k 563k 1.00 mpas_ocean.NeighborhoodBuild.track_nbytes_neighbors('480km', 15.0)
123k 123k 1.00 mpas_ocean.NeighborhoodBuild.track_nbytes_neighbors('480km', 5.0)
5.72M 5.72M 1.00 mpas_ocean.NeighborhoodBuild.track_peakmem_build('120km', 1.0)
145M 145M 1.00 mpas_ocean.NeighborhoodBuild.track_peakmem_build('120km', 15.0)
21.5M 21.5M 1.00 mpas_ocean.NeighborhoodBuild.track_peakmem_build('120km', 5.0)
362k 362k 1.00 mpas_ocean.NeighborhoodBuild.track_peakmem_build('480km', 1.0)
825k 825k 1.00 mpas_ocean.NeighborhoodBuild.track_peakmem_build('480km', 15.0)
384k 384k 1.00 mpas_ocean.NeighborhoodBuild.track_peakmem_build('480km', 5.0)
32.5±0.2ms 32.2±0.3ms 0.99 mpas_ocean.NeighborhoodDask.time_mean('120km', 'grid_chunks')
14.8±0.1ms 14.6±0.08ms 0.99 mpas_ocean.NeighborhoodDask.time_mean('120km', 'numpy')
29.6±0.4ms 29.6±0.3ms 1.00 mpas_ocean.NeighborhoodDask.time_mean('120km', 'time_chunks')
7.59±0.1ms 7.83±0.2ms 1.03 mpas_ocean.NeighborhoodDask.time_mean('480km', 'grid_chunks')
539±4μs 545±10μs 1.01 mpas_ocean.NeighborhoodDask.time_mean('480km', 'numpy')
5.23±0.1ms 5.46±0.1ms 1.04 mpas_ocean.NeighborhoodDask.time_mean('480km', 'time_chunks')
5.83M 5.77M 0.99 mpas_ocean.NeighborhoodDask.track_peakmem_mean('120km', 'grid_chunks')
2.75M 2.75M 1.00 mpas_ocean.NeighborhoodDask.track_peakmem_mean('120km', 'numpy')
5.68M 5.68M 1.00 mpas_ocean.NeighborhoodDask.track_peakmem_mean('120km', 'time_chunks')
177k 177k 1.00 mpas_ocean.NeighborhoodDask.track_peakmem_mean('480km', 'numpy')
539k 547k 1.01 mpas_ocean.NeighborhoodDask.track_peakmem_mean('480km', 'time_chunks')
10.8±0s 10.7±0s 0.99 mpas_ocean.NeighborhoodReduce.time_dataset_reduce('120km', 'mean')
11.5±0.01s 11.3±0s 0.99 mpas_ocean.NeighborhoodReduce.time_dataset_reduce('120km', 'median')
193±0.4ms 191±0.4ms 0.99 mpas_ocean.NeighborhoodReduce.time_dataset_reduce('480km', 'mean')
198±1ms 196±1ms 0.99 mpas_ocean.NeighborhoodReduce.time_dataset_reduce('480km', 'median')
1.14±0s 1.14±0s 1.00 mpas_ocean.NeighborhoodReduce.time_neighborhood_reduce('120km', 'mean')
1.32±0s 1.31±0.01s 1.00 mpas_ocean.NeighborhoodReduce.time_neighborhood_reduce('120km', 'median')
22.1±0.09ms 22.2±0.1ms 1.00 mpas_ocean.NeighborhoodReduce.time_neighborhood_reduce('480km', 'mean')
23.3±0.09ms 23.1±0.2ms 0.99 mpas_ocean.NeighborhoodReduce.time_neighborhood_reduce('480km', 'median')
27.2±0.08ms 27.2±0.08ms 1.00 mpas_ocean.NeighborhoodReduce.time_reduce('120km', 'mean')
201±0.7ms 200±0.2ms 1.00 mpas_ocean.NeighborhoodReduce.time_reduce('120km', 'median')
427±30μs 480±40μs ~1.12 mpas_ocean.NeighborhoodReduce.time_reduce('480km', 'mean')
1.48±0.03ms 1.55±0.05ms 1.05 mpas_ocean.NeighborhoodReduce.time_reduce('480km', 'median')
239k 239k 1.00 mpas_ocean.NeighborhoodReduce.track_peakmem_reduce('120km', 'mean')
245k 245k 1.00 mpas_ocean.NeighborhoodReduce.track_peakmem_reduce('120km', 'median')
19.5k 19.5k 1.00 mpas_ocean.NeighborhoodReduce.track_peakmem_reduce('480km', 'mean')
19.9k 19.9k 1.00 mpas_ocean.NeighborhoodReduce.track_peakmem_reduce('480km', 'median')
308±9μs 319±10μs 1.03 mpas_ocean.PointInPolygon.time_face_search_lonlat('120km')
288±5μs 291±6μs 1.01 mpas_ocean.PointInPolygon.time_face_search_lonlat('480km')
287±20μs 276±5μs 0.96 mpas_ocean.PointInPolygon.time_face_search_xyz('120km')
272±9μs 264±6μs 0.97 mpas_ocean.PointInPolygon.time_face_search_xyz('480km')
97.6±0.3ms 96.4±2ms 0.99 mpas_ocean.RemapDownsample.time_bilinear_remapping
13.5±0.07ms 13.5±0.1ms 1.00 mpas_ocean.RemapDownsample.time_inverse_distance_weighted_remapping
11.9±0.05ms 12.1±0.07ms 1.01 mpas_ocean.RemapDownsample.time_nearest_neighbor_remapping
940±10ms 937±3ms 1.00 mpas_ocean.RemapUpsample.time_bilinear_remapping
20.5±0.4ms 21.2±0.2ms 1.03 mpas_ocean.RemapUpsample.time_inverse_distance_weighted_remapping
8.40±0.03ms 8.41±0.05ms 1.00 mpas_ocean.RemapUpsample.time_nearest_neighbor_remapping
6.70±0.05ms 6.59±0.1ms 0.98 mpas_ocean.ZonalAverage.time_zonal_average('120km')
3.75±0.06ms 3.74±0.05ms 1.00 mpas_ocean.ZonalAverage.time_zonal_average('480km')
352M 339M 0.96 mpas_ocean.ZonalAveragePeakMem.track_peakmem_zonal_average('120km')
352M 323M 0.92 mpas_ocean.ZonalAveragePeakMem.track_peakmem_zonal_average('480km')
1.031033609495087 1.025415043400093 0.99 nogil_scaling.GILScaling.track_gil_scaling
5.13±0.08ms 5.30±0.02ms 1.03 quad_hexagon.QuadHexagon.time_open_dataset
4.32±0.03ms 4.52±0.07ms 1.05 quad_hexagon.QuadHexagon.time_open_grid
408 408 1.00 quad_hexagon.QuadHexagon.track_nbytes_open_dataset
392 392 1.00 quad_hexagon.QuadHexagon.track_nbytes_open_grid
72.8k 73.6k 1.01 quad_hexagon.QuadHexagon.track_peakmem_open_dataset
72.3k 72.7k 1.01 quad_hexagon.QuadHexagon.track_peakmem_open_grid

Benchmarks that have got worse:

Change Before [9609c3a] After [d9cd4ae] Ratio Benchmark (Parameter)
+ 461±9ns 566±90ns 1.23 geometry_kernels.GCAConstLatIntersection.time_accux_constlat_kernel
+ 668k 758k 1.13 mpas_ocean.NeighborhoodDask.track_peakmem_mean('480km', 'grid_chunks')

@cmdupuis3
cmdupuis3 marked this pull request as ready for review September 28, 2026 21:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

run-benchmark Run ASV benchmark workflow scalability Related to scalability & performance efforts

Projects

None yet

Development

Successfully merging this pull request may close these issues.

_set_desired_longitude_range forces materialization in open_grid

1 participant