Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
70 changes: 70 additions & 0 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# This is a basic workflow to help you get started with Actions

name: benchmark-check

# Controls when the workflow will run
on:
# Triggers the workflow on push or pull request events but only for the master branch
pull_request:

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest

# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
with:
fetch-depth: 0

# This is too complicated to get working for now.
#
# # fetch the target branch from origin too (just the head)
# # and check it out into a local branch
# - name: Checkout PR and target branch
# run: |
# git fetch --no-tags --depth=1 origin ${{ github.base_ref }}
# git fetch --no-tags --depth=1 origin ${{ github.head_ref }}

- name: Setup asv
run: |
pip install asv
cd benchmarks
asv machine --yes --machine gh-actions

- name: Run benchmarks on source and target
run: |
cd benchmarks
export BASE=origin/${{ github.base_ref }}
export HEAD=origin/${{ github.head_ref }}
# run asv benchmarks on the target
# -s 1 ensures only one "step" (the tip of the branch) is run
asv run $BASE -s 1
# run asv on the head of this PR
asv run $HEAD -s 1
asv compare -s $BASE $HEAD > .asv/compare.txt

- name: Archive asv results
uses: actions/upload-artifact@v2
with:
name: asv-report
path: |
benchmarks/.asv/results
benchmarks/.asv/compare.txt

- name: Fail if performance degraded
run: |
cd benchmarks
cat .asv/compare.txt
if grep -q "Benchmarks that have got worse" .asv/compare.txt; then
echo "::error::Performance degradation. See action artifact for full results."
exit 1
fi

37 changes: 0 additions & 37 deletions asv.conf.json

This file was deleted.

17 changes: 17 additions & 0 deletions benchmarks/asv.conf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"version": 1,
"project": "scitools-iris",
"project_url": "https://github.com/SciTools/iris",
"repo": "..",
"environment_type": "conda-lock",
"show_commit_url": "http://github.com/scitools/iris/commit/",

"benchmark_dir": "./benchmarks",
"env_dir": ".asv/env",
"results_dir": ".asv/results",
"html_dir": ".asv/html",
"plugins": [".conda_lock_plugin"],
// this is not an asv standard config entry, just for our plugin
// path to lockfile, relative to project base
"conda_lockfile": "requirements/ci/nox.lock/py38-linux-64.lock"
}
40 changes: 40 additions & 0 deletions benchmarks/benchmarks/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""Common code for benchmarks."""

import os
from pathlib import Path

# Environment variable names
_ASVDIR_VARNAME = 'ASV_DIR' # As set in nightly script "asv_nightly/asv.sh"
_DATADIR_VARNAME = 'BENCHMARK_DATA' # For local runs

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

# Work out where the benchmark data dir is.
asv_dir = os.environ.get('ASV_DIR', None)
if asv_dir:
# For an overnight run, this comes from the 'ASV_DIR' setting.
benchmark_data_dir = Path(asv_dir) / 'data'
else:
# For a local run, you set 'BENCHMARK_DATA'.
benchmark_data_dir = os.environ.get(_DATADIR_VARNAME, None)
if benchmark_data_dir is not None:
benchmark_data_dir = Path(benchmark_data_dir)


def testdata_path(*path_names):
"""
Return the path of a benchmark test data file.

These are based from a test-data location dir, which is either
${}/data (for overnight tests), or ${} for local testing.

If neither of these were set, an error is raised.

""".format(_ASVDIR_VARNAME, _DATADIR_VARNAME)
if benchmark_data_dir is None:
msg = ('Benchmark data dir is not defined : '
'Either "${}" or "${}" must be set.')
raise(ValueError(msg.format(_ASVDIR_VARNAME, _DATADIR_VARNAME)))
path = benchmark_data_dir.joinpath(*path_names)
path = str(path) # Because Iris doesn't understand Path objects yet.
return path
53 changes: 53 additions & 0 deletions benchmarks/benchmarks/aux_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""
AuxFactory benchmark tests.

"""

import numpy as np

from benchmarks import ARTIFICIAL_DIM_SIZE
from iris import aux_factory, coords


class FactoryCommon:
# TODO: once https://github.com/airspeed-velocity/asv/pull/828 is released:
# * make class an ABC
# * remove NotImplementedError
# * combine setup_common into setup

"""
A base class running a generalised suite of benchmarks for any factory.
Factory to be specified in a subclass.

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

Should only be instantiated within subclasses, but cannot enforce this
since ASV cannot handle classes that include abstract methods.
"""
def setup(self):
"""Prevent ASV instantiating (must therefore override setup() in any subclasses.)"""
raise NotImplementedError

def setup_common(self):
"""Shared setup code that can be called by subclasses."""
self.factory = self.create()

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

def time_return(self):
"""Return an instance of the benchmarked factory."""
self.factory


class HybridHeightFactory(FactoryCommon):
def setup(self):
data_1d = np.zeros(ARTIFICIAL_DIM_SIZE)
self.coord = coords.AuxCoord(points=data_1d, units="m")

self.setup_common()

def create(self):
return aux_factory.HybridHeightFactory(delta=self.coord)
113 changes: 113 additions & 0 deletions benchmarks/benchmarks/coords.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
"""
Coord benchmark tests.

"""

import numpy as np

from benchmarks import ARTIFICIAL_DIM_SIZE
from iris import coords


def setup():
"""General variables needed by multiple benchmark classes."""
global data_1d

data_1d = np.zeros(ARTIFICIAL_DIM_SIZE)


class CoordCommon:
# TODO: once https://github.com/airspeed-velocity/asv/pull/828 is released:
# * make class an ABC
# * remove NotImplementedError
# * combine setup_common into setup
"""

A base class running a generalised suite of benchmarks for any coord.
Coord to be specified in a subclass.

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

Should only be instantiated within subclasses, but cannot enforce this
since ASV cannot handle classes that include abstract methods.
"""
def setup(self):
"""Prevent ASV instantiating (must therefore override setup() in any subclasses.)"""
raise NotImplementedError

def setup_common(self):
"""Shared setup code that can be called by subclasses."""
self.component = self.create()

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

def time_return(self):
"""Return an instance of the benchmarked coord."""
self.component


class DimCoord(CoordCommon):
def setup(self):
point_values = np.arange(ARTIFICIAL_DIM_SIZE)
bounds = np.array(
[point_values - 1, point_values + 1]).transpose()

self.create_kwargs = {
"points": point_values,
"bounds": bounds,
"units": "days since 1970-01-01",
"climatological": True
}

self.setup_common()

def create(self):
return coords.DimCoord(**self.create_kwargs)

def time_regular(self):
coords.DimCoord.from_regular(0, 1, 1000)


class AuxCoord(CoordCommon):
def setup(self):
bounds = np.array(
[data_1d - 1, data_1d + 1]).transpose()

self.create_kwargs = {
"points": data_1d,
"bounds": bounds,
"units": "days since 1970-01-01",
"climatological": True
}

self.setup_common()

def create(self):
return coords.AuxCoord(**self.create_kwargs)


class CellMeasure(CoordCommon):
def setup(self):
self.setup_common()

def create(self):
return coords.CellMeasure(data_1d)


class CellMethod(CoordCommon):
def setup(self):
self.setup_common()

def create(self):
return coords.CellMethod("test")


class AncillaryVariable(CoordCommon):
def setup(self):
self.setup_common()

def create(self):
return coords.AncillaryVariable(data_1d)
Loading