From 8707c1673bc5beaae88d22a20b0794fc1d84e1fe Mon Sep 17 00:00:00 2001 From: cmdupuis3 Date: Wed, 22 Jul 2026 12:40:01 -0500 Subject: [PATCH 1/3] ASV peakmem benchmark fix Three benchmarks reported a near-identical "improvement" on every PR regardless of what the PR touched: 578M -> 391M 0.68 face_bounds.FaceBounds.peakmem_face_bounds(geoflow-small) 708M -> 390M 0.55 face_bounds.FaceBounds.peakmem_face_bounds(quad-hexagon) 498M -> 384M 0.77 mpas_ocean.Gradient.peakmem_gradient('480km') They were not measuring the operation under test. asv's peakmem_* records the max RSS of the whole process, and per asv's docs it "also counts memory usage during the setup routine". Profiling the face_bounds params: grid import +open_grid +.bounds attributable to op quad-hexagon ( 24K) 236MB 265MB 301MB 36MB (12%) geoflow-small (1.1M) 236MB 263MB 487MB 224MB (46%) outCSne8 ( 48K) 235MB 281MB 317MB 35MB (11%) oQU480 (4.6M) 236MB 270MB 306MB 36MB (12%) `import uxarray` alone is ~226 MB and constant to within 1 MB. oQU480 is 190x larger than quad-hexagon yet both attributed ~36 MB to Grid.bounds -- the benchmark was nearly insensitive to its own workload. The part that did vary was numba: uxarray has 81 @njit(cache=True) kernels and both affected paths go through them (uxarray/grid/bounds.py, uxarray/core/gradient.py). With a cold JIT cache the quad-hexagon case peaked at 599 MB, with a warm one 298 MB -- a ratio of 0.50, matching the ratios seen in CI. Which side of an `asv continuous` comparison paid the compile cost depended on run order, not on the code under review. peakmem_* could not be repaired in place, because there was nothing to measure. Across every operation the five peakmem benchmarks covered, against every mesh in the suite including the 98 MB / 28,571-face oQU120: Grid.bounds ~1 MB open_grid 0-10 MB (lazy; mostly allocator noise) gradient 0-0.1 MB integrate 0.0 MB open_dataset 0.0 MB Two better instruments were evaluated and rejected: - Sampled peak RSS. Validates cleanly (a known 200 MB allocation measures as 200.0 MB) and is immune to process history. But the warm-up call needed to keep JIT out of the measurement leaves freed pages in the allocator, so RSS *growth* undercounts -- every operation measured 0.0-0.1 MB this way. - tracemalloc. Measures allocation volume rather than RSS growth, so it would sidestep page reuse, but it does not observe numba NRT allocations, which is where uxarray's array memory is allocated. What remains is deterministic size accounting via track_* benchmarks, which also sidesteps the setup confound entirely -- track_* does not count setup. Verified byte-identical across cold JIT, warm JIT, and an independent second cold JIT for all 14 parameter combinations, and it scales correctly with the mesh (quad-hexagon reports 128 bytes = 4 faces x 32). It does not capture transient peaks, but the measurements above show those are ~1 MB, far below anything worth gating on, and no available instrument captures them reliably here. The rationale is recorded in benchmarks/_memsize.py so the next person does not reintroduce peakmem_*. The commented-out mem_* stubs in quad_hexagon.py, an earlier attempt at the same thing, are removed. Co-Authored-By: Claude Opus 4.8 --- benchmarks/_memsize.py | 66 ++++++++++++++++++++++++++++++++++++++ benchmarks/face_bounds.py | 17 ++++++++-- benchmarks/mpas_ocean.py | 18 ++++++++--- benchmarks/quad_hexagon.py | 22 ++++++------- 4 files changed, 103 insertions(+), 20 deletions(-) create mode 100644 benchmarks/_memsize.py diff --git a/benchmarks/_memsize.py b/benchmarks/_memsize.py new file mode 100644 index 000000000..bc00a3b0e --- /dev/null +++ b/benchmarks/_memsize.py @@ -0,0 +1,66 @@ +"""Deterministic memory-size accounting for the benchmark suite. + +This module backs the ``track_nbytes_*`` benchmarks that replaced the former +``peakmem_*`` ones. + +Why ``peakmem_*`` was removed +----------------------------- +asv's ``peakmem_*`` records the maximum resident set size of the *whole +process*, and per asv's own docs it "also counts memory usage during the setup +routine". For uxarray that number is almost entirely fixed overhead: + + grid import +open_grid +.bounds attributable to op + quad-hexagon ( 24K) 236MB 265MB 301MB 36MB (12%) + geoflow-small (1.1M) 236MB 263MB 487MB 224MB (46%) + outCSne8 ( 48K) 235MB 281MB 317MB 35MB (11%) + oQU480 (4.6M) 236MB 270MB 306MB 36MB (12%) + +``import uxarray`` alone is ~226 MB and constant to within 1 MB. oQU480 is 190x +larger than quad-hexagon yet both attributed ~36 MB to ``Grid.bounds`` -- the +benchmark was nearly insensitive to its own workload. The part that did vary was +numba: with a cold JIT cache the quad-hexagon case peaked at 599 MB, with a warm +one 298 MB. Which side of an ``asv continuous`` comparison paid the compile cost +depended on run order, so unrelated PRs kept reporting identical ~0.55-0.77 +"improvements". + +Why not sampled peak RSS +------------------------ +Sampling current RSS around the call and reporting the delta is immune to +process history, and it validates cleanly (a known 200 MB allocation measures as +200.0 MB). But after a warm-up call -- which is required to keep JIT compilation +out of the measurement -- the allocator reuses the pages it just freed, so RSS +*growth* systematically undercounts. Every operation the old benchmarks covered +measured 0.0-0.1 MB that way, including on the 98 MB / 28,571-face oQU120 mesh. + +Why not tracemalloc +------------------- +It measures allocation volume rather than RSS growth, which would sidestep the +page-reuse problem, but it does not observe numba NRT allocations -- and that is +where uxarray's array memory is allocated. + +What is left +------------ +Deterministic size accounting. It is bit-reproducible across runs, platforms and +JIT states (verified), scales with the mesh, and catches the memory regressions +that actually matter in an array library: dtype widening, densified +connectivity, and newly cached arrays. It does not capture transient peaks -- +but no available instrument captures those reliably here, and the measurements +above show the transients are ~1 MB, far below anything worth gating on. +""" + +__all__ = ["grid_nbytes", "dataset_nbytes"] + + +def grid_nbytes(uxgrid): + """Total size of the arrays a ``Grid`` currently holds, in bytes. + + Counts whatever has been materialized so far, so calling this after an + operation that caches results onto the grid (``bounds``, ``face_areas``, + connectivity) reports that operation's contribution to the grid's footprint. + """ + return uxgrid._ds.nbytes + + +def dataset_nbytes(uxds): + """Total size of a ``UxDataset``, in bytes, including its grid.""" + return uxds.nbytes + grid_nbytes(uxds.uxgrid) diff --git a/benchmarks/face_bounds.py b/benchmarks/face_bounds.py index b249e7b99..6f2faf2eb 100644 --- a/benchmarks/face_bounds.py +++ b/benchmarks/face_bounds.py @@ -2,6 +2,7 @@ from pathlib import Path import uxarray as ux +from ._memsize import grid_nbytes current_path = Path(os.path.dirname(os.path.realpath(__file__))).parents[0] @@ -25,6 +26,16 @@ def time_face_bounds(self, grid_path): """Time to obtain ``Grid.face_bounds``""" self.uxgrid.bounds - def peakmem_face_bounds(self, grid_path): - """Peak memory usage obtain ``Grid.face_bounds.""" - face_bounds = self.uxgrid.bounds + def track_nbytes_face_bounds(self, grid_path): + """Size of the materialized ``Grid.face_bounds`` array.""" + return self.uxgrid.bounds.nbytes + + track_nbytes_face_bounds.unit = "bytes" + + def track_nbytes_grid_with_bounds(self, grid_path): + """Grid footprint after populating bounds -- catches cached arrays that + ``bounds`` adds to the ``Grid`` beyond the returned array itself.""" + self.uxgrid.bounds + return grid_nbytes(self.uxgrid) + + track_nbytes_grid_with_bounds.unit = "bytes" diff --git a/benchmarks/mpas_ocean.py b/benchmarks/mpas_ocean.py index 659c19014..39e413fca 100644 --- a/benchmarks/mpas_ocean.py +++ b/benchmarks/mpas_ocean.py @@ -5,6 +5,7 @@ import numpy as np import uxarray as ux +from ._memsize import grid_nbytes current_path = Path(os.path.dirname(os.path.realpath(__file__))) @@ -60,16 +61,25 @@ class Gradient(DatasetBenchmark): def time_gradient(self, resolution): self.uxds[data_var].gradient() - def peakmem_gradient(self, resolution): - grad = self.uxds[data_var].gradient() + def track_nbytes_gradient(self, resolution): + """Size of the gradient result.""" + return self.uxds[data_var].gradient().nbytes + + track_nbytes_gradient.unit = "bytes" class Integrate(DatasetBenchmark): def time_integrate(self, resolution): self.uxds[data_var].integrate() - def peakmem_integrate(self, resolution): - integral = self.uxds[data_var].integrate() + def track_nbytes_integrate(self, resolution): + """Grid footprint after integrating. ``integrate`` returns a scalar, so + the memory that matters is what it caches onto the ``Grid`` (face + areas) to get there.""" + self.uxds[data_var].integrate() + return grid_nbytes(self.uxds.uxgrid) + + track_nbytes_integrate.unit = "bytes" class GeoDataFrame(DatasetBenchmark): diff --git a/benchmarks/quad_hexagon.py b/benchmarks/quad_hexagon.py index 4364f39b0..683e13b1b 100644 --- a/benchmarks/quad_hexagon.py +++ b/benchmarks/quad_hexagon.py @@ -2,6 +2,7 @@ from pathlib import Path import uxarray as ux +from ._memsize import dataset_nbytes, grid_nbytes current_path = Path(os.path.dirname(os.path.realpath(__file__))).parents[0] @@ -14,23 +15,18 @@ def time_open_grid(self): """Time to open a `Grid`""" ux.open_grid(grid_path) - # def mem_open_grid(self): - # """Memory Occupied by a `Grid`""" - # return ux.open_grid(grid_path) - - def peakmem_open_grid(self): - """Peak memory usage of a `Grid`""" - uxgrid = ux.open_grid(grid_path) + def track_nbytes_open_grid(self): + """Memory occupied by a `Grid`""" + return grid_nbytes(ux.open_grid(grid_path)) + track_nbytes_open_grid.unit = "bytes" def time_open_dataset(self): """Time to open a `UxDataset`""" ux.open_dataset(grid_path, data_path) - # def mem_open_dataset(self): - # """Memory occupied by a `UxDataset`""" - # return ux.open_dataset(grid_path, data_path) + def track_nbytes_open_dataset(self): + """Memory occupied by a `UxDataset`, including its grid""" + return dataset_nbytes(ux.open_dataset(grid_path, data_path)) - def peakmem_open_dataset(self): - """Peak memory usage of a `UxDataset`""" - uxds = ux.open_dataset(grid_path, data_path) + track_nbytes_open_dataset.unit = "bytes" From 2113022a5e742e47b8f2f7beeb75f0fd3f7ea84c Mon Sep 17 00:00:00 2001 From: cmdupuis3 Date: Wed, 22 Jul 2026 13:14:45 -0500 Subject: [PATCH 2/3] Add process-scope peakmem benchmarks The previous commit removed peakmem_* because the per-operation memory it claimed to measure was ~1 MB against a reported 400-700 MB. But the larger question those benchmarks were reaching for -- "how much memory does it take to open this grid and compute on it, from a cold start" -- is real, and asv can answer it with its own peakmem_* once two things are controlled: 1. Process history. ru_maxrss is a high-water mark that never falls, so imports, setup() and earlier work in the same process set a floor under the result. asv already spawns a separate process per benchmark *and per parameter* (runner._run_benchmark_single_param), so this is handled as long as the benchmark classes declare no setup() -- asv counts setup memory towards peakmem_*, which is exactly the confound its own docs warn about. 2. numba JIT cache warmth. Compiling uxarray's 81 @njit(cache=True) kernels costs a few hundred MB of transient RSS, so an otherwise identical run peaked at 599 MB cold and 298 MB warm. Whichever side of an `asv continuous` comparison happened to compile paid that cost. setup_cache handles (2). asv runs it in its own process (runner.Spawner.create_setup_cache), so LLVM's footprint never enters the high-water mark of the processes that do the measuring; they load the compiled kernels from numba's on-disk cache instead. It runs once per commit, so both sides of a comparison are warmed symmetrically. Every parameter is warmed, not just one -- the grids do not all reach the same njit signatures. Measured via `asv run --python=same --quick -b peakmem`, twice warm and once after deleting every .nbi/.nbc in the installed uxarray: warm warm COLD spread import uxarray 280M 282M 280M 0.7% open+bounds quad-hexagon 349M 349M 346M 0.9% open+bounds geoflow-small 348M 348M 348M 0.0% open+bounds outCSne8 365M 364M 370M 1.6% open+bounds oQU480 354M 356M 351M 1.4% gradient 480km 358M 355M 354M 1.1% gradient 120km 371M 370M 381M 2.9% The cold column is the condition that used to halve the result; the cache repopulated from 0 to 33 entries during that run, confirming setup_cache did the compiling. Everything sits inside 2.9%, against asv's default 10% factor. peakmem_import_uxarray is included deliberately. ~280 MB of every row above is just importing uxarray, so tracking it on its own means a heavy new top-level import shows up as itself instead of silently inflating everything else. Also moves _memsize into benchmarks/helpers/ and gives it an __init__.py, so the package structure is explicit rather than relying on namespace packages. Co-Authored-By: Claude Opus 4.8 --- benchmarks/_memsize.py | 66 ---------------------------------- benchmarks/face_bounds.py | 30 +++++++++++++++- benchmarks/helpers/__init__.py | 0 benchmarks/helpers/_memsize.py | 12 +++++++ benchmarks/import.py | 9 +++++ benchmarks/mpas_ocean.py | 27 +++++++++++++- benchmarks/quad_hexagon.py | 2 +- 7 files changed, 77 insertions(+), 69 deletions(-) delete mode 100644 benchmarks/_memsize.py create mode 100644 benchmarks/helpers/__init__.py create mode 100644 benchmarks/helpers/_memsize.py diff --git a/benchmarks/_memsize.py b/benchmarks/_memsize.py deleted file mode 100644 index bc00a3b0e..000000000 --- a/benchmarks/_memsize.py +++ /dev/null @@ -1,66 +0,0 @@ -"""Deterministic memory-size accounting for the benchmark suite. - -This module backs the ``track_nbytes_*`` benchmarks that replaced the former -``peakmem_*`` ones. - -Why ``peakmem_*`` was removed ------------------------------ -asv's ``peakmem_*`` records the maximum resident set size of the *whole -process*, and per asv's own docs it "also counts memory usage during the setup -routine". For uxarray that number is almost entirely fixed overhead: - - grid import +open_grid +.bounds attributable to op - quad-hexagon ( 24K) 236MB 265MB 301MB 36MB (12%) - geoflow-small (1.1M) 236MB 263MB 487MB 224MB (46%) - outCSne8 ( 48K) 235MB 281MB 317MB 35MB (11%) - oQU480 (4.6M) 236MB 270MB 306MB 36MB (12%) - -``import uxarray`` alone is ~226 MB and constant to within 1 MB. oQU480 is 190x -larger than quad-hexagon yet both attributed ~36 MB to ``Grid.bounds`` -- the -benchmark was nearly insensitive to its own workload. The part that did vary was -numba: with a cold JIT cache the quad-hexagon case peaked at 599 MB, with a warm -one 298 MB. Which side of an ``asv continuous`` comparison paid the compile cost -depended on run order, so unrelated PRs kept reporting identical ~0.55-0.77 -"improvements". - -Why not sampled peak RSS ------------------------- -Sampling current RSS around the call and reporting the delta is immune to -process history, and it validates cleanly (a known 200 MB allocation measures as -200.0 MB). But after a warm-up call -- which is required to keep JIT compilation -out of the measurement -- the allocator reuses the pages it just freed, so RSS -*growth* systematically undercounts. Every operation the old benchmarks covered -measured 0.0-0.1 MB that way, including on the 98 MB / 28,571-face oQU120 mesh. - -Why not tracemalloc -------------------- -It measures allocation volume rather than RSS growth, which would sidestep the -page-reuse problem, but it does not observe numba NRT allocations -- and that is -where uxarray's array memory is allocated. - -What is left ------------- -Deterministic size accounting. It is bit-reproducible across runs, platforms and -JIT states (verified), scales with the mesh, and catches the memory regressions -that actually matter in an array library: dtype widening, densified -connectivity, and newly cached arrays. It does not capture transient peaks -- -but no available instrument captures those reliably here, and the measurements -above show the transients are ~1 MB, far below anything worth gating on. -""" - -__all__ = ["grid_nbytes", "dataset_nbytes"] - - -def grid_nbytes(uxgrid): - """Total size of the arrays a ``Grid`` currently holds, in bytes. - - Counts whatever has been materialized so far, so calling this after an - operation that caches results onto the grid (``bounds``, ``face_areas``, - connectivity) reports that operation's contribution to the grid's footprint. - """ - return uxgrid._ds.nbytes - - -def dataset_nbytes(uxds): - """Total size of a ``UxDataset``, in bytes, including its grid.""" - return uxds.nbytes + grid_nbytes(uxds.uxgrid) diff --git a/benchmarks/face_bounds.py b/benchmarks/face_bounds.py index 6f2faf2eb..9009755e8 100644 --- a/benchmarks/face_bounds.py +++ b/benchmarks/face_bounds.py @@ -2,7 +2,7 @@ from pathlib import Path import uxarray as ux -from ._memsize import grid_nbytes +from .helpers._memsize import grid_nbytes current_path = Path(os.path.dirname(os.path.realpath(__file__))).parents[0] @@ -39,3 +39,31 @@ def track_nbytes_grid_with_bounds(self, grid_path): return grid_nbytes(self.uxgrid) track_nbytes_grid_with_bounds.unit = "bytes" + + +class FaceBoundsPeakMem: + """Peak memory of a cold start: import uxarray, open a grid, get its bounds. + + Deliberately defines no ``setup``. asv counts setup memory towards + ``peakmem_*``, and asv gives each parameter its own process, so with the JIT + warmed in ``setup_cache`` the measured process does exactly the work named in + the benchmark and nothing else. + """ + + params = FaceBounds.params + param_names = ["grid_path"] + + def setup_cache(self): + """Compile the njit kernels before anything is measured. + + asv runs this in its own process, so LLVM's few hundred MB stays out of + the measured ones -- they load from numba's on-disk cache instead. + Without it the first measured process compiles and the rest do not, which + is what made the old ``peakmem_*`` swing ~2x with run order. All params + are warmed; the grids do not reach the same njit signatures. + """ + for grid_path in self.params: + ux.open_grid(grid_path).bounds + + def peakmem_open_and_bounds(self, grid_path): + ux.open_grid(grid_path).bounds diff --git a/benchmarks/helpers/__init__.py b/benchmarks/helpers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/benchmarks/helpers/_memsize.py b/benchmarks/helpers/_memsize.py new file mode 100644 index 000000000..cf13f70f8 --- /dev/null +++ b/benchmarks/helpers/_memsize.py @@ -0,0 +1,12 @@ + +__all__ = ["grid_nbytes", "dataset_nbytes"] + + +def grid_nbytes(uxgrid): + """Total size of the arrays a ``Grid`` currently holds, in bytes.""" + return uxgrid._ds.nbytes + + +def dataset_nbytes(uxds): + """Total size of a ``UxDataset``, in bytes, including its grid.""" + return uxds.nbytes + grid_nbytes(uxds.uxgrid) diff --git a/benchmarks/import.py b/benchmarks/import.py index e53515f2a..291672cff 100644 --- a/benchmarks/import.py +++ b/benchmarks/import.py @@ -3,3 +3,12 @@ class Imports: def timeraw_import_uxarray(self): return "import uxarray" + + def peakmem_import_uxarray(self): + """Peak memory of a process that has imported uxarray. + + This is the floor under every other ``peakmem_*`` result, so it is worth + tracking on its own: a heavy new top-level import shows up here rather + than silently inflating everything else. + """ + import uxarray # noqa: F401 diff --git a/benchmarks/mpas_ocean.py b/benchmarks/mpas_ocean.py index 39e413fca..221df4d57 100644 --- a/benchmarks/mpas_ocean.py +++ b/benchmarks/mpas_ocean.py @@ -5,7 +5,7 @@ import numpy as np import uxarray as ux -from ._memsize import grid_nbytes +from .helpers._memsize import grid_nbytes current_path = Path(os.path.dirname(os.path.realpath(__file__))) @@ -82,6 +82,31 @@ def track_nbytes_integrate(self, resolution): track_nbytes_integrate.unit = "bytes" +class GradientPeakMem: + """Peak memory of a cold start: import uxarray, open a dataset, take a gradient. + + Not a :class:`DatasetBenchmark` subclass -- that would open the dataset in + ``setup``, and asv counts setup memory towards ``peakmem_*``. + """ + + param_names = ["resolution"] + params = [["480km", "120km"]] + + def setup_cache(self): + """Compile the njit kernels before anything is measured. + + See :meth:`face_bounds.FaceBoundsPeakMem.setup_cache` -- asv runs this in + its own process, keeping LLVM's footprint out of the measured ones. + """ + for resolution in self.params[0]: + grid, data = file_path_dict[resolution] + ux.open_dataset(grid, data)[data_var].gradient() + + def peakmem_gradient(self, resolution): + grid, data = file_path_dict[resolution] + ux.open_dataset(grid, data)[data_var].gradient() + + class GeoDataFrame(DatasetBenchmark): param_names = DatasetBenchmark.param_names + ['exclude_antimeridian'] params = DatasetBenchmark.params + [[True, False]] diff --git a/benchmarks/quad_hexagon.py b/benchmarks/quad_hexagon.py index 683e13b1b..1e9dd50ad 100644 --- a/benchmarks/quad_hexagon.py +++ b/benchmarks/quad_hexagon.py @@ -2,7 +2,7 @@ from pathlib import Path import uxarray as ux -from ._memsize import dataset_nbytes, grid_nbytes +from .helpers._memsize import dataset_nbytes, grid_nbytes current_path = Path(os.path.dirname(os.path.realpath(__file__))).parents[0] From 7a17f020c25588f10cbe4d917a2106189317a35e Mon Sep 17 00:00:00 2001 From: cmdupuis3 Date: Wed, 22 Jul 2026 13:58:03 -0500 Subject: [PATCH 3/3] Clean up Claude comments --- benchmarks/face_bounds.py | 17 ++--------------- benchmarks/import.py | 7 +------ benchmarks/mpas_ocean.py | 10 ++-------- 3 files changed, 5 insertions(+), 29 deletions(-) diff --git a/benchmarks/face_bounds.py b/benchmarks/face_bounds.py index 9009755e8..d860bfdb1 100644 --- a/benchmarks/face_bounds.py +++ b/benchmarks/face_bounds.py @@ -42,26 +42,13 @@ def track_nbytes_grid_with_bounds(self, grid_path): class FaceBoundsPeakMem: - """Peak memory of a cold start: import uxarray, open a grid, get its bounds. - - Deliberately defines no ``setup``. asv counts setup memory towards - ``peakmem_*``, and asv gives each parameter its own process, so with the JIT - warmed in ``setup_cache`` the measured process does exactly the work named in - the benchmark and nothing else. - """ + """Peak memory of a cold start: import uxarray, open a grid, get its bounds.""" params = FaceBounds.params param_names = ["grid_path"] def setup_cache(self): - """Compile the njit kernels before anything is measured. - - asv runs this in its own process, so LLVM's few hundred MB stays out of - the measured ones -- they load from numba's on-disk cache instead. - Without it the first measured process compiles and the rest do not, which - is what made the old ``peakmem_*`` swing ~2x with run order. All params - are warmed; the grids do not reach the same njit signatures. - """ + """Compile the njit kernels before anything is measured.""" for grid_path in self.params: ux.open_grid(grid_path).bounds diff --git a/benchmarks/import.py b/benchmarks/import.py index 291672cff..98296ba6a 100644 --- a/benchmarks/import.py +++ b/benchmarks/import.py @@ -5,10 +5,5 @@ def timeraw_import_uxarray(self): return "import uxarray" def peakmem_import_uxarray(self): - """Peak memory of a process that has imported uxarray. - - This is the floor under every other ``peakmem_*`` result, so it is worth - tracking on its own: a heavy new top-level import shows up here rather - than silently inflating everything else. - """ + """Peak memory of a process that has imported uxarray.""" import uxarray # noqa: F401 diff --git a/benchmarks/mpas_ocean.py b/benchmarks/mpas_ocean.py index 221df4d57..d9befabe7 100644 --- a/benchmarks/mpas_ocean.py +++ b/benchmarks/mpas_ocean.py @@ -73,9 +73,7 @@ def time_integrate(self, resolution): self.uxds[data_var].integrate() def track_nbytes_integrate(self, resolution): - """Grid footprint after integrating. ``integrate`` returns a scalar, so - the memory that matters is what it caches onto the ``Grid`` (face - areas) to get there.""" + """Grid footprint after integrating.""" self.uxds[data_var].integrate() return grid_nbytes(self.uxds.uxgrid) @@ -93,11 +91,7 @@ class GradientPeakMem: params = [["480km", "120km"]] def setup_cache(self): - """Compile the njit kernels before anything is measured. - - See :meth:`face_bounds.FaceBoundsPeakMem.setup_cache` -- asv runs this in - its own process, keeping LLVM's footprint out of the measured ones. - """ + """Compile the njit kernels before anything is measured.""" for resolution in self.params[0]: grid, data = file_path_dict[resolution] ux.open_dataset(grid, data)[data_var].gradient()