Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
bdc7175
Synthetic FF PP NetCDF and loading benchmarks.
trexfeathers Dec 17, 2021
eb3d772
Remove legacy benchmark data directory handling.
trexfeathers Jan 5, 2022
94c855b
GitHub benchmark action fixed PY_VER.
trexfeathers Jan 5, 2022
1e7b6a0
Missing licence headers.
trexfeathers Jan 5, 2022
83eef27
Cache generated benchmark data.
trexfeathers Jan 5, 2022
94aff73
ALWAYS cache benchmark generated data.
trexfeathers Jan 5, 2022
85ee84e
Remaining benchmark modules migrated.
trexfeathers Jan 5, 2022
fe82b24
Also add StructuredFF benchmark.
trexfeathers Jan 5, 2022
7fe7d5c
Revert "ALWAYS cache benchmark generated data."
trexfeathers Jan 6, 2022
0a806f1
Revert "Cache generated benchmark data."
trexfeathers Jan 6, 2022
7561195
Improved benchmark GHA env caching (2min faster?)
trexfeathers Jan 6, 2022
9b9e27d
Convert all benchmarks to generate synth data in a fixed environment.
trexfeathers Jan 10, 2022
d503ce6
[pre-commit.ci] pre-commit autoupdate (#4560)
pre-commit-ci[bot] Feb 1, 2022
961ec56
Benchmark docstrings.
trexfeathers Feb 1, 2022
81c4bcf
Merge remote-tracking branch 'upstream/main' into all_benchmarks
trexfeathers Feb 1, 2022
acc7097
Merge branch 'all_benchmarks' into migrate_benchmarks
trexfeathers Feb 1, 2022
df4faa0
Proof benchmarks against Connectivity name changes.
trexfeathers Feb 1, 2022
6261c5e
Regionds combine benchmark licence header.
trexfeathers Feb 2, 2022
64d1f93
Rethink sample_mesh and sample_meshcoord in benchmarks.
trexfeathers Feb 3, 2022
023b1e9
Force realised arrays for benchmark generated data.
trexfeathers Feb 14, 2022
4af2171
Revert "[pre-commit.ci] pre-commit autoupdate (#4560)"
trexfeathers Feb 17, 2022
91291af
Merge remote-tracking branch 'upstream/main' into migrate_benchmarks
trexfeathers Feb 17, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions benchmarks/benchmarks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,69 @@
# See COPYING and COPYING.LESSER in the root of the repository for full
# licensing details.
"""Common code for benchmarks."""
import resource

from .generate_data import BENCHMARK_DATA, run_function_elsewhere

ARTIFICIAL_DIM_SIZE = int(10e3) # For all artificial cubes, coords etc.


def disable_repeat_between_setup(benchmark_object):
"""
Decorator for benchmarks where object persistence would be inappropriate.

E.g:
* Benchmarking data realisation
* Benchmarking Cube coord addition

Can be applied to benchmark classes/methods/functions.

https://asv.readthedocs.io/en/stable/benchmarks.html#timing-benchmarks

"""
# Prevent repeat runs between setup() runs - object(s) will persist after 1st.
benchmark_object.number = 1
# Compensate for reduced certainty by increasing number of repeats.
# (setup() is run between each repeat).
# Minimum 5 repeats, run up to 30 repeats / 20 secs whichever comes first.
benchmark_object.repeat = (5, 30, 20.0)
# ASV uses warmup to estimate benchmark time before planning the real run.
# Prevent this, since object(s) will persist after first warmup run,
# which would give ASV misleading info (warmups ignore ``number``).
benchmark_object.warmup_time = 0.0

return benchmark_object


class TrackAddedMemoryAllocation:
"""
Context manager which measures by how much process resident memory grew,
during execution of its enclosed code block.

Obviously limited as to what it actually measures : Relies on the current
process not having significant unused (de-allocated) memory when the
tested codeblock runs, and only reliable when the code allocates a
significant amount of new memory.

Example:
with TrackAddedMemoryAllocation() as mb:
initial_call()
other_call()
result = mb.addedmem_mb()

"""

@staticmethod
def process_resident_memory_mb():
return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024.0

def __enter__(self):
self.mb_before = self.process_resident_memory_mb()
return self

def __exit__(self, *_):
self.mb_after = self.process_resident_memory_mb()

def addedmem_mb(self):
"""Return measured memory growth, in Mb."""
return self.mb_after - self.mb_before
3 changes: 2 additions & 1 deletion benchmarks/benchmarks/aux_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@

import numpy as np

from benchmarks import ARTIFICIAL_DIM_SIZE
from iris import aux_factory, coords

from . import ARTIFICIAL_DIM_SIZE


class FactoryCommon:
# TODO: once https://github.com/airspeed-velocity/asv/pull/828 is released:
Expand Down
20 changes: 19 additions & 1 deletion benchmarks/benchmarks/coords.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@

import numpy as np

from benchmarks import ARTIFICIAL_DIM_SIZE
from iris import coords

from . import ARTIFICIAL_DIM_SIZE, disable_repeat_between_setup


def setup():
"""General variables needed by multiple benchmark classes."""
Expand Down Expand Up @@ -92,6 +93,23 @@ def setup(self):
def create(self):
return coords.AuxCoord(**self.create_kwargs)

def time_points(self):
_ = self.component.points

def time_bounds(self):
_ = self.component.bounds


@disable_repeat_between_setup
class AuxCoordLazy(AuxCoord):
"""Lazy equivalent of :class:`AuxCoord`."""

def setup(self):
super().setup()
self.create_kwargs["points"] = self.component.lazy_points()
self.create_kwargs["bounds"] = self.component.lazy_bounds()
self.setup_common()


class CellMeasure(CoordCommon):
def setup(self):
Expand Down
44 changes: 42 additions & 2 deletions benchmarks/benchmarks/cube.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@

import numpy as np

from benchmarks import ARTIFICIAL_DIM_SIZE
from iris import analysis, aux_factory, coords, cube

from . import ARTIFICIAL_DIM_SIZE, disable_repeat_between_setup
from .generate_data.stock import sample_meshcoord

def setup():

def setup(*params):
"""General variables needed by multiple benchmark classes."""
global data_1d
global data_2d
Expand Down Expand Up @@ -170,6 +172,44 @@ def setup(self):
self.setup_common()


class MeshCoord:
params = [
6, # minimal cube-sphere
int(1e6), # realistic cube-sphere size
ARTIFICIAL_DIM_SIZE, # To match size in :class:`AuxCoord`
]
param_names = ["number of faces"]

def setup(self, n_faces):
mesh_kwargs = dict(
n_nodes=n_faces + 2, n_edges=n_faces * 2, n_faces=n_faces
)

self.mesh_coord = sample_meshcoord(sample_mesh_kwargs=mesh_kwargs)
self.data = np.zeros(n_faces)
self.cube_blank = cube.Cube(data=self.data)
self.cube = self.create()

def create(self):
return cube.Cube(
data=self.data, aux_coords_and_dims=[(self.mesh_coord, 0)]
)

def time_create(self, n_faces):
_ = self.create()

@disable_repeat_between_setup
def time_add(self, n_faces):
self.cube_blank.add_aux_coord(self.mesh_coord, 0)

@disable_repeat_between_setup
def time_remove(self, n_faces):
self.cube.remove_coord(self.mesh_coord)

def time_return(self, n_faces):
_ = self.cube


class Merge:
def setup(self):
self.cube_list = cube.CubeList()
Expand Down
9 changes: 9 additions & 0 deletions benchmarks/benchmarks/experimental/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright Iris contributors
#
# This file is part of Iris and is released under the LGPL license.
# See COPYING and COPYING.LESSER in the root of the repository for full
# licensing details.
"""
Benchmark tests for the experimental module.

"""
195 changes: 195 additions & 0 deletions benchmarks/benchmarks/experimental/ugrid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# Copyright Iris contributors
#
# This file is part of Iris and is released under the LGPL license.
# See COPYING and COPYING.LESSER in the root of the repository for full
# licensing details.
"""
Benchmark tests for the experimental.ugrid module.

"""

from copy import deepcopy

import numpy as np

from iris.experimental import ugrid

from .. import ARTIFICIAL_DIM_SIZE, disable_repeat_between_setup
from ..generate_data.stock import sample_mesh


class UGridCommon:
"""
A base class running a generalised suite of benchmarks for any ugrid object.
Object to be specified in a subclass.

ASV will run the benchmarks within this class for any subclasses.

ASV will not benchmark this class as setup() triggers a NotImplementedError.
(ASV has not yet released ABC/abstractmethod support - asv#838).

"""

params = [
6, # minimal cube-sphere
int(1e6), # realistic cube-sphere size
]
param_names = ["number of faces"]

def setup(self, *params):
self.object = self.create()

def create(self):
raise NotImplementedError

def time_create(self, *params):
"""Create an instance of the benchmarked object. create() method is
specified in the subclass."""
self.create()

def time_return(self, *params):
"""Return an instance of the benchmarked object."""
_ = self.object


class Connectivity(UGridCommon):
def setup(self, n_faces):
self.array = np.zeros([n_faces, 3], dtype=np.int)
super().setup(n_faces)

def create(self):
return ugrid.Connectivity(
indices=self.array, cf_role="face_node_connectivity"
)

def time_indices(self, n_faces):
_ = self.object.indices

def time_location_lengths(self, n_faces):
# Proofed against the Connectivity name change (633ed17).
if getattr(self.object, "src_lengths", False):
meth = self.object.src_lengths
else:
meth = self.object.location_lengths
_ = meth()

def time_validate_indices(self, n_faces):
self.object.validate_indices()


@disable_repeat_between_setup
class ConnectivityLazy(Connectivity):
"""Lazy equivalent of :class:`Connectivity`."""

def setup(self, n_faces):
super().setup(n_faces)
self.array = self.object.lazy_indices()
self.object = self.create()


class Mesh(UGridCommon):
def setup(self, n_faces, lazy=False):
####
# Steal everything from the sample mesh for benchmarking creation of a
# brand new mesh.
source_mesh = sample_mesh(
n_nodes=n_faces + 2,
n_edges=n_faces * 2,
n_faces=n_faces,
lazy_values=lazy,
)

def get_coords_and_axes(location):
search_kwargs = {f"include_{location}s": True}
return [
(source_mesh.coord(axis=axis, **search_kwargs), axis)
for axis in ("x", "y")
]

self.mesh_kwargs = dict(
topology_dimension=source_mesh.topology_dimension,
node_coords_and_axes=get_coords_and_axes("node"),
connectivities=source_mesh.connectivities(),
edge_coords_and_axes=get_coords_and_axes("edge"),
face_coords_and_axes=get_coords_and_axes("face"),
)
####

super().setup(n_faces)

self.face_node = self.object.face_node_connectivity
self.node_x = self.object.node_coords.node_x
# Kwargs for reuse in search and remove methods.
self.connectivities_kwarg = dict(cf_role="edge_node_connectivity")
self.coords_kwarg = dict(include_faces=True)

# TODO: an opportunity for speeding up runtime if needed, since
# eq_object is not needed for all benchmarks. Just don't generate it
# within a benchmark - the execution time is large enough that it
# could be a significant portion of the benchmark - makes regressions
# smaller and could even pick up regressions in copying instead!
self.eq_object = deepcopy(self.object)

def create(self):
return ugrid.Mesh(**self.mesh_kwargs)

def time_add_connectivities(self, n_faces):
self.object.add_connectivities(self.face_node)

def time_add_coords(self, n_faces):
self.object.add_coords(node_x=self.node_x)

def time_connectivities(self, n_faces):
_ = self.object.connectivities(**self.connectivities_kwarg)

def time_coords(self, n_faces):
_ = self.object.coords(**self.coords_kwarg)

def time_eq(self, n_faces):
_ = self.object == self.eq_object

def time_remove_connectivities(self, n_faces):
self.object.remove_connectivities(**self.connectivities_kwarg)

def time_remove_coords(self, n_faces):
self.object.remove_coords(**self.coords_kwarg)


@disable_repeat_between_setup
class MeshLazy(Mesh):
"""Lazy equivalent of :class:`Mesh`."""

def setup(self, n_faces, lazy=True):
super().setup(n_faces, lazy=lazy)


class MeshCoord(UGridCommon):
# Add extra parameter value to match AuxCoord benchmarking.
params = UGridCommon.params + [ARTIFICIAL_DIM_SIZE]

def setup(self, n_faces, lazy=False):
self.mesh = sample_mesh(
n_nodes=n_faces + 2,
n_edges=n_faces * 2,
n_faces=n_faces,
lazy_values=lazy,
)

super().setup(n_faces)

def create(self):
return ugrid.MeshCoord(mesh=self.mesh, location="face", axis="x")

def time_points(self, n_faces):
_ = self.object.points

def time_bounds(self, n_faces):
_ = self.object.bounds


@disable_repeat_between_setup
class MeshCoordLazy(MeshCoord):
"""Lazy equivalent of :class:`MeshCoord`."""

def setup(self, n_faces, lazy=True):
super().setup(n_faces, lazy=lazy)
Loading