Skip to content

Commit 10b7f41

Browse files
authored
Add ASV benchmarks for 6 modules changed in v0.9.5 (#1156)
* Add ASV benchmarks for 6 uncovered modules (#1137) normalize, diffusion, erosion, balanced_allocation, dasymetric, and reproject all received memory-guard or lazy-reduction fixes in the v0.9.5 cycle but had no ASV benchmark coverage. Each new module follows the existing Benchmarking base-class pattern with appropriate grid sizes and backend params. * Add Rescale, Standardize, Diffusion, Dasymetric to CI benchmark filter (#1137) These four are fast enough (<5ms at 300px) for CI. Erosion and Reproject are excluded because they're too compute-heavy for PR checks. * Fix code review findings in benchmark modules (#1137) - dasymetric.py: add call parens to has_cuda_and_cupy() guard - reproject.py: move import to module level to keep it out of timed methods
1 parent fb1916f commit 10b7f41

File tree

7 files changed

+192
-2
lines changed

7 files changed

+192
-2
lines changed

.github/workflows/benchmarks.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,15 @@ jobs:
3737
run: |
3838
asv continuous origin/master HEAD \
3939
--factor $ASV_FACTOR \
40-
--bench "^(Slope|Proximity|Zonal|CostDistance|Focal)" \
40+
--bench "^(Slope|Proximity|Zonal|CostDistance|Focal|Rescale|Standardize|Diffusion|Dasymetric)" \
4141
| tee bench_output.txt
4242
4343
- name: Run benchmarks (push to master)
4444
if: github.event_name == 'push'
4545
working-directory: benchmarks
4646
run: |
4747
asv run HEAD^! \
48-
--bench "^(Slope|Proximity|Zonal|CostDistance|Focal)" \
48+
--bench "^(Slope|Proximity|Zonal|CostDistance|Focal|Rescale|Standardize|Diffusion|Dasymetric)" \
4949
| tee bench_output.txt
5050
5151
- name: Check for regressions
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import numpy as np
2+
import xarray as xr
3+
4+
from xrspatial.balanced_allocation import balanced_allocation
5+
6+
from .common import get_xr_dataarray
7+
8+
9+
class BalancedAllocation:
10+
# Memory-intensive: holds N_sources cost surfaces simultaneously.
11+
# Keep grids small to avoid OOM during benchmarking.
12+
params = ([100, 300], ["numpy", "dask"])
13+
param_names = ("nx", "type")
14+
15+
def setup(self, nx, type):
16+
ny = nx // 2
17+
# Friction surface: positive values everywhere.
18+
friction = get_xr_dataarray((ny, nx), type)
19+
if type == "dask":
20+
import dask.array as da
21+
friction.data = da.fabs(friction.data) + 0.1
22+
else:
23+
friction.data = np.abs(friction.data) + 0.1
24+
self.friction = friction
25+
26+
# Source raster: place 4 source points in corners.
27+
sources = np.zeros((ny, nx), dtype=np.float32)
28+
margin = max(1, nx // 10)
29+
sources[margin, margin] = 1
30+
sources[margin, nx - margin - 1] = 2
31+
sources[ny - margin - 1, margin] = 3
32+
sources[ny - margin - 1, nx - margin - 1] = 4
33+
34+
x = np.linspace(-180, 180, nx)
35+
y = np.linspace(-90, 90, ny)
36+
37+
if type == "dask":
38+
import dask.array as da
39+
data = da.from_array(sources, chunks=(max(1, ny // 2), max(1, nx // 2)))
40+
else:
41+
data = sources
42+
43+
self.raster = xr.DataArray(data, coords=dict(y=y, x=x), dims=["y", "x"])
44+
45+
def time_balanced_allocation(self, nx, type):
46+
balanced_allocation(self.raster, self.friction, max_iterations=10)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import numpy as np
2+
import xarray as xr
3+
4+
from xrspatial.dasymetric import disaggregate
5+
6+
from .common import get_xr_dataarray
7+
8+
9+
class Dasymetric:
10+
params = ([100, 300, 1000], ["numpy", "cupy", "dask"])
11+
param_names = ("nx", "type")
12+
13+
def setup(self, nx, type):
14+
ny = nx // 2
15+
16+
# Zones: 4 rectangular blocks.
17+
zones_np = np.zeros((ny, nx), dtype=np.int32)
18+
zones_np[: ny // 2, : nx // 2] = 1
19+
zones_np[: ny // 2, nx // 2 :] = 2
20+
zones_np[ny // 2 :, : nx // 2] = 3
21+
zones_np[ny // 2 :, nx // 2 :] = 4
22+
23+
x = np.linspace(-180, 180, nx)
24+
y = np.linspace(-90, 90, ny)
25+
26+
if type == "dask":
27+
import dask.array as da
28+
zdata = da.from_array(zones_np, chunks=(max(1, ny // 2), max(1, nx // 2)))
29+
elif type == "cupy":
30+
from xrspatial.utils import has_cuda_and_cupy
31+
if not has_cuda_and_cupy():
32+
raise NotImplementedError()
33+
import cupy
34+
zdata = cupy.asarray(zones_np)
35+
else:
36+
zdata = zones_np
37+
38+
self.zones = xr.DataArray(zdata, coords=dict(y=y, x=x), dims=["y", "x"])
39+
40+
# Values: one total per zone.
41+
self.values = {1: 1000.0, 2: 2000.0, 3: 1500.0, 4: 2500.0}
42+
43+
# Weight surface: use the standard Gaussian bump.
44+
weight = get_xr_dataarray((ny, nx), type)
45+
# Make weights non-negative.
46+
if type == "dask":
47+
import dask.array as da
48+
weight.data = da.fabs(weight.data) + 0.01
49+
elif type == "cupy":
50+
import cupy
51+
weight.data = cupy.abs(weight.data) + 0.01
52+
else:
53+
weight.data = np.abs(weight.data) + 0.01
54+
self.weight = weight
55+
56+
def time_disaggregate_weighted(self, nx, type):
57+
disaggregate(self.zones, self.values, self.weight, method="weighted")
58+
59+
def time_disaggregate_binary(self, nx, type):
60+
disaggregate(self.zones, self.values, self.weight, method="binary")

benchmarks/benchmarks/diffusion.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from xrspatial.diffusion import diffuse
2+
3+
from .common import Benchmarking
4+
5+
6+
class Diffusion(Benchmarking):
7+
params = ([100, 300, 1000], ["numpy", "cupy", "dask"])
8+
param_names = ("nx", "type")
9+
10+
def __init__(self):
11+
super().__init__(func=None)
12+
13+
def time_diffuse_1step(self, nx, type):
14+
diffuse(self.xr, diffusivity=1.0, steps=1)
15+
16+
def time_diffuse_10steps(self, nx, type):
17+
diffuse(self.xr, diffusivity=1.0, steps=10)

benchmarks/benchmarks/erosion.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from xrspatial.erosion import erode
2+
3+
from .common import get_xr_dataarray
4+
5+
6+
class Erosion:
7+
# Erosion is a global operation that materialises dask arrays.
8+
# Keep grid sizes small and iteration counts low so benchmarks
9+
# finish in reasonable time.
10+
params = ([100, 300], ["numpy", "cupy", "dask"])
11+
param_names = ("nx", "type")
12+
13+
def setup(self, nx, type):
14+
ny = nx // 2
15+
self.xr = get_xr_dataarray((ny, nx), type)
16+
17+
def time_erode_500(self, nx, type):
18+
erode(self.xr, iterations=500, seed=42)
19+
20+
def time_erode_5000(self, nx, type):
21+
erode(self.xr, iterations=5000, seed=42)

benchmarks/benchmarks/normalize.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from xrspatial.normalize import rescale, standardize
2+
3+
from .common import Benchmarking
4+
5+
6+
class Rescale(Benchmarking):
7+
def __init__(self):
8+
super().__init__(func=rescale)
9+
10+
def time_rescale(self, nx, type):
11+
return self.time(nx, type)
12+
13+
14+
class Standardize(Benchmarking):
15+
def __init__(self):
16+
super().__init__(func=standardize)
17+
18+
def time_standardize(self, nx, type):
19+
return self.time(nx, type)

benchmarks/benchmarks/reproject.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from .common import get_xr_dataarray
2+
3+
try:
4+
from xrspatial.reproject import reproject as _reproject
5+
_HAS_PYPROJ = True
6+
except ImportError:
7+
_HAS_PYPROJ = False
8+
9+
10+
class Reproject:
11+
params = ([100, 300, 1000], ["numpy", "dask"])
12+
param_names = ("nx", "type")
13+
14+
def setup(self, nx, type):
15+
if not _HAS_PYPROJ:
16+
raise NotImplementedError("pyproj required")
17+
18+
ny = nx // 2
19+
self.xr = get_xr_dataarray((ny, nx), type)
20+
# Tag with WGS84 so reproject knows the source CRS.
21+
self.xr.attrs["crs"] = "EPSG:4326"
22+
23+
def time_reproject_to_mercator(self, nx, type):
24+
_reproject(self.xr, "EPSG:3857")
25+
26+
def time_reproject_to_utm(self, nx, type):
27+
_reproject(self.xr, "EPSG:32610")

0 commit comments

Comments
 (0)