Skip to content

Commit a41dd0f

Browse files
committed
r.proj: add thread scaling benchmark script
Add a benchmark that sweeps the nprocs= thread count from 1 to 8 at two memory caps and plots the time, speedup, and efficiency metrics, following the r.param.scale benchmark template with grass.benchmark. It builds a source project and reprojects a generated raster from EPSG:4326 into EPSG:3857 in a temporary database, so it is self-contained.
1 parent 25e82f5 commit a41dd0f

1 file changed

Lines changed: 135 additions & 0 deletions

File tree

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
"""Benchmarking of r.proj thread scaling
2+
raster (2D)
3+
4+
This follows the r.param.scale benchmark structure, sweeping raster size at a
5+
fixed memory and then memory at a fixed raster size, and plotting the time,
6+
speedup, and efficiency metrics with grass.benchmark. r.proj sweeps its compute
7+
thread count through the nprocs= option. Each cell generates a source raster in
8+
an EPSG:4326 project and reprojects it into EPSG:3857 in a temporary database,
9+
so the script is self-contained. Run it with
10+
grass --exec python benchmark_r_proj.py or from any GRASS session.
11+
"""
12+
13+
import os
14+
import tempfile
15+
16+
from grass.exceptions import CalledModuleError, GrassError
17+
from grass.pygrass.modules import Module
18+
import grass.script as gs
19+
import grass.benchmark as bm
20+
21+
# Baselines held fixed while one dimension is swept.
22+
BASE_MAPSIZE = 50e6 # cells
23+
BASE_MEMORY = 300 # MB
24+
MAPSIZES = [10e6, 50e6, 100e6]
25+
MEMORIES = [50, 100, 300, 1000]
26+
METRICS = ["time", "speedup", "efficiency"]
27+
MAX_NPROCS = 8
28+
REPEAT = 3
29+
30+
SRC_PROJECT = "src4326"
31+
DST_PROJECT = "dst3857"
32+
INPUT = "benchmark_r_proj_reference"
33+
OUTPUT = "benchmark_r_proj"
34+
35+
36+
def main():
37+
gisdbase = tempfile.mkdtemp(prefix="bench_r_proj_")
38+
gs.create_project(os.path.join(gisdbase, SRC_PROJECT), epsg="4326")
39+
gs.create_project(os.path.join(gisdbase, DST_PROJECT), epsg="3857")
40+
41+
# Sweep raster size at the baseline memory.
42+
results = []
43+
for mapsize in MAPSIZES:
44+
benchmark(
45+
gisdbase,
46+
size=int(mapsize**0.5),
47+
memory=BASE_MEMORY,
48+
label=f"r.proj_{int(mapsize / 1e6)}M",
49+
results=results,
50+
)
51+
plot(results, "rastersize")
52+
53+
# Sweep memory at the baseline raster size.
54+
results = []
55+
for memory in MEMORIES:
56+
benchmark(
57+
gisdbase,
58+
size=int(BASE_MAPSIZE**0.5),
59+
memory=memory,
60+
label=f"r.proj_memory_{memory}MB",
61+
results=results,
62+
)
63+
plot(results, "memory")
64+
65+
66+
def benchmark(gisdbase, size, memory, label, results):
67+
generate_input(gisdbase, size)
68+
with gs.setup.init(
69+
os.path.join(gisdbase, DST_PROJECT), env=os.environ.copy()
70+
) as session:
71+
env = session.env
72+
# Output region from r.proj's own suggested bounds for this input.
73+
text = gs.read_command(
74+
"r.proj",
75+
project=SRC_PROJECT,
76+
mapset="PERMANENT",
77+
dbase=gisdbase,
78+
input=INPUT,
79+
method="nearest",
80+
flags="g",
81+
env=env,
82+
)
83+
region = dict(token.split("=") for token in text.split())
84+
gs.run_command("g.region", env=env, **region)
85+
86+
module = Module(
87+
"r.proj",
88+
project=SRC_PROJECT,
89+
mapset="PERMANENT",
90+
dbase=gisdbase,
91+
input=INPUT,
92+
output=OUTPUT,
93+
method="nearest",
94+
memory=memory,
95+
env_=env,
96+
run_=False,
97+
overwrite=True,
98+
)
99+
results.append(
100+
bm.benchmark_nprocs(
101+
module, label=label, max_nprocs=MAX_NPROCS, repeat=REPEAT
102+
)
103+
)
104+
105+
106+
def generate_input(gisdbase, size):
107+
"""Generate the size by size source raster in the EPSG:4326 project,
108+
mirroring the r.param.scale benchmark by trying r.surf.fractal and falling
109+
back to r.random.surface when fractal is unavailable, for example in a build
110+
without FFTW."""
111+
with gs.setup.init(
112+
os.path.join(gisdbase, SRC_PROJECT), env=os.environ.copy()
113+
) as session:
114+
env = session.env
115+
gs.run_command(
116+
"g.region", n=50, s=40, w=-110, e=-90, rows=size, cols=size, env=env
117+
)
118+
try:
119+
Module("r.surf.fractal", output=INPUT, overwrite=True, env_=env)
120+
except (CalledModuleError, GrassError):
121+
Module("r.random.surface", output=INPUT, overwrite=True, env_=env)
122+
123+
124+
def plot(results, sweep):
125+
for metric in METRICS:
126+
bm.nprocs_plot(
127+
results,
128+
filename=f"r_proj_{sweep}_{metric}.svg",
129+
title=f"r.proj {sweep} {metric}",
130+
metric=metric,
131+
)
132+
133+
134+
if __name__ == "__main__":
135+
main()

0 commit comments

Comments
 (0)