"""
Benchmarks for ufuncs.
"""
from __future__ import absolute_import
from ..license import license as _license, copyright as _copyright, version as _version
import sys as _sys
from .utils.misc import try_import_for_setup as _try_import_for_setup
from .core import Bench as _Bench
__author__ = "Shane J. Latham"
__license__ = _license()
__copyright__ = _copyright()
__version__ = _version()
[docs]class UfuncBench(_Bench):
"""
Base class for array ufunc benchmarks.
Run benchmarks for calls of the form::
numpy.exp(self.a_ary, out=self.c_ary)
numpy.add(self.a_ary, self.b_ary, out=self.c_ary)
where the :attr:`a_ary`, :attr:`b_ary` and :attr:`c_ary` arrays
are initialised (with uniform random scalars) during :meth:`setup`.
"""
[docs] def __init__(self, ufunc_name=None):
"""
Initialise.
:type ufunc_name: :obj:`str`
:param ufunc_name: Name of the ufunc, should be the name of an attribute of :attr:`module`.
"""
_Bench.__init__(self)
self._ufunc_name = ufunc_name
self._ufunc = None
self._a_ary = None
self._b_ary = None
self._c_ary = None
self._b_scalar = None
#: Number of repetitions to run each benchmark
repeat = 8
#: The set of array-shape parameters.
params = [[((1024**2) // 8,), ((1000, 100, 100,)), ((1024 // 8, 1024, 1024,))], ]
#: The name of the array-shape parameters.
param_names = ["shape"]
# Goal time (seconds) for a single repeat.
goal_time = 1.0
# Execute benchmark for this time (seconds) as a *warm-up* prior to real timing.
warmup_time = 1.0
@property
def ufunc(self):
"""
The :samp:`numpy.ufunc` for this benchmark.
"""
return self._ufunc
@ufunc.setter
def ufunc(self, value):
self._ufunc = value
@property
def a_ary(self):
"""
First input array.
"""
return self._a_ary
@a_ary.setter
def a_ary(self, value):
self._a_ary = value
@property
def a_ary_range(self):
"""
A :samp:`(low, high)` tuple indicating the uniform random range of scalars for
the :attr:`a_ary` array elements.
"""
return (0.66, 1.66)
@property
def b_ary(self):
"""
Second input array.
"""
return self._b_ary
@b_ary.setter
def b_ary(self, value):
self._b_ary = value
@property
def b_ary_range(self):
"""
A :samp:`(low, high)` tuple inficating the uniform random range of scalars for
the :attr:`b_ary` array elements.
"""
return (0.7, 1.3)
@property
def c_ary(self):
"""
Output array.
"""
return self._c_ary
@c_ary.setter
def c_ary(self, value):
self._c_ary = value
@property
def b_scalar(self):
"""
Second scalar input.
"""
return self._b_scalar
@b_scalar.setter
def b_scalar(self, value):
self._b_scalar = value
@property
def random_state(self):
"""
A :obj:`numpy.random.RandomState` instance, used to generate random
values in arrays.
"""
_np = self.try_import_for_setup("numpy")
_mpi = self.try_import_for_setup("mpi4py.MPI")
seed_str = str(2 ** 31)[1:]
rank_str = str(_mpi.COMM_WORLD.rank + 1)
seed_str = rank_str + seed_str[len(rank_str):]
seed_str = seed_str[0:-len(rank_str)] + rank_str[::-1]
random_state = _np.random.RandomState(seed=int(seed_str))
return random_state
[docs] def initialise_arrays(self, shape):
"""
Initialise arrays/scalars passed to ufunc instances.
:type shape: sequence of :obj:`int`
:param shape: Shape of the arrays to be passed to ufuncs.
"""
shape = self.get_globale_shape(shape)
random_state = self.random_state
self.a_ary = \
random_state.uniform(
low=self.a_ary_range[0],
high=self.a_ary_range[1],
size=shape
).astype(self.dtype)
self.b_ary = \
random_state.uniform(
low=self.b_ary_range[0],
high=self.b_ary_range[1],
size=shape
).astype(self.dtype)
self.c_ary = self.module.empty(shape, dtype=self.dtype)
self.b_scalar = \
self.dtype.type(random_state.uniform(low=self.b_ary_range[0], high=self.b_ary_range[1]))
[docs] def initialise(self, shape, dtype, module_name):
"""
Sets the :attr:`module`, :attr:`dtype` and :attr:`ufunc` attributes
and initialises the :attr:`a_ary`, :attr:`b_ary`, :attr:`b_scalar` and :attr:`c_ary`
arrays.
"""
self.module = _try_import_for_setup(module_name)
self.dtype = self.module.dtype(dtype)
self.ufunc = getattr(self.module, self._ufunc_name)
self.initialise_arrays(shape)
[docs] def setup(self, shape, dtype="float64"):
"""
Should be over-ridden in sub-classes.
"""
pass
[docs] def teardown(self, shape):
"""
Free arrays allocated during :meth:`setup`.
"""
self.free(self.a_ary)
self.a_ary = None
self.free(self.b_ary)
self.b_ary = None
self.free(self.c_ary)
self.c_ary = None
self.b_scalar = None
[docs] def free(self, a):
"""
Clean up array resources, over-ridden in sub-classes.
"""
pass
[docs]class NumpyUfuncBench(UfuncBench):
"""
Comparison benchmarks for :obj:`numpy.ufunc` instances.
"""
[docs] def initialise_arrays(self, shape):
"""
Initialise arrays/scalars passed to ufunc instances.
:type shape: sequence of :obj:`int`
:param shape: Shape of the arrays to be passed to ufuncs.
"""
shape = self.get_globale_shape(shape)
random_state = self.random_state
self.a_ary = \
random_state.uniform(
low=self.a_ary_range[0],
high=self.a_ary_range[1],
size=shape
).astype(self.dtype)
self.b_ary = \
random_state.uniform(
low=self.b_ary_range[0],
high=self.b_ary_range[1],
size=shape
).astype(self.dtype)
self.c_ary = self.module.empty(shape, dtype=self.dtype)
self.b_scalar = \
self.dtype.type(random_state.uniform(low=self.b_ary_range[0], high=self.b_ary_range[1]))
[docs] def setup(self, shape, dtype="float64"):
"""
Import :mod:`numpy` module and assign to :attr:`module`.
"""
mpi = _try_import_for_setup("mpi4py.MPI")
if mpi.COMM_WORLD.size > 1:
raise NotImplementedError("only runs for single process")
self.initialise(shape=shape, dtype=dtype, module_name="numpy")
[docs]class MpiArrayUfuncBench(UfuncBench):
"""
Benchmarks for :mod:`mpi_array` ufuncs..
"""
[docs] def initialise_arrays(self, shape):
"""
Initialise arrays/scalars passed to ufunc instances.
:type shape: sequence of :obj:`int`
:param shape: Shape of the arrays to be passed to ufuncs.
"""
self.root_logger.debug("%s.setup: locale array shape=%s", self.__class__.__name__, shape)
shape = self.get_globale_shape(shape)
self.root_logger.debug("%s.setup: globale array shape=%s", self.__class__.__name__, shape)
random_state = self.random_state
self.a_ary = self.module.empty(shape, dtype=self.dtype)
self.b_ary = self.module.empty(shape, dtype=self.dtype)
self.c_ary = self.module.empty(shape, dtype=self.dtype)
self.a_ary.rank_view_n[...] = \
random_state.uniform(
low=self.a_ary_range[0],
high=self.a_ary_range[1],
size=self.a_ary.rank_view_n.shape
).astype(self.dtype)
self.b_ary.rank_view_n[...] = \
random_state.uniform(
low=self.b_ary_range[0],
high=self.b_ary_range[1],
size=self.b_ary.rank_view_n.shape
).astype(self.dtype)
self.b_scalar = \
self.dtype.type(random_state.uniform(low=self.b_ary_range[0], high=self.b_ary_range[1]))
self.root_logger.debug(
"%s.setup: a_ary.shape=%s, a_ary.view_n.shape=%s, a_ary.rank_view_n.shape=%s,"
+
" a_ary.view_n.dtype=%s",
self.__class__.__name__,
self.a_ary.shape,
self.a_ary.view_n.shape,
self.a_ary.rank_view_n.shape,
self.a_ary.view_n.dtype
)
self.a_ary.locale_comms.intra_locale_comm.barrier()
[docs] def setup(self, shape, dtype="float64"):
"""
Import :mod:`mpi_array` module and assign to :samp:`self.module`.
"""
self.initialise(shape=shape, dtype=dtype, module_name="mpi_array")
[docs] def free(self, a):
"""
See :meth:`free_mpi_array_obj`.
"""
self.free_mpi_array_obj(a)
[docs]class NumpyUfuncBench_invsqrt(NumpyUfuncBench):
"""
Benchmarks for :samp:`numpy.power(ary, -0.5)` ufunc for :obj:`numpy.ndarray` input.
"""
[docs] def __init__(self):
NumpyUfuncBench.__init__(self, ufunc_name="power")
[docs] def time_array_op(self, shape, dtype="float64"):
"""
Timing for single argument ufuncs.
"""
self._ufunc(self._a_ary, -0.5, out=self._c_ary)
[docs]class MpiArrayUfuncBench_invsqrt(MpiArrayUfuncBench):
"""
Benchmarks for :samp:`mpi_array.power(ary, -0.5)` ufunc for :obj:`mpi_array.globale.gndarray`
input.
"""
[docs] def __init__(self):
MpiArrayUfuncBench.__init__(self, ufunc_name="power")
[docs] def time_array_op(self, shape, dtype="float64"):
"""
Timing for single argument ufuncs.
"""
self._ufunc(self._a_ary, -0.5, out=self._c_ary)
_module_name_to_sphinx_doc_array_type = {
"mpi_array": "mpi_array.globale.gndarray",
"numpy": "numpy.ndarray"
}
_module_name_to_bench_type = {"mpi_array": MpiArrayUfuncBench, "numpy": NumpyUfuncBench}
[docs]def create_ufunc_bench(module_name, ufunc_name, method_dict):
"""
Creates a new benchmark type for the ufunc :samp:`getattr({module_name}, {ufunc_name})`.
"""
try:
bench_type = _module_name_to_bench_type[module_name]
ufunc_bench_type = None
def __init__(self):
bench_type.__init__(self, ufunc_name=ufunc_name)
modyule = _try_import_for_setup(module_name)
ufunc = getattr(modyule, ufunc_name)
ufunc_bench_method_dict = {"__init__": __init__}
if ufunc.nin == 1:
ufunc_bench_method_dict["time_array_op"] = \
method_dict["time_array_op"]
if ufunc.nin == 2:
ufunc_bench_method_dict["time_array_array_op"] = \
method_dict["time_array_array_op"]
ufunc_bench_method_dict["time_array_scalar_op"] = \
method_dict["time_array_scalar_op"]
ufunc_bench_type = \
type(
bench_type.__name__ + "_" + ufunc_name,
(bench_type,),
ufunc_bench_method_dict
)
if (_sys.version_info[0] >= 3) and (_sys.version_info[1] >= 4):
setattr(
ufunc_bench_type,
"__doc__",
("Benchmark for :obj:`numpy." + ufunc_name + "` with :obj:`%s` array inputs.")
%
(_module_name_to_sphinx_doc_array_type[module_name],)
)
except (AttributeError, ImportError, NotImplementedError):
pass
return ufunc_bench_type
[docs]def create_ufunc_benchmarks(ufunc_names, module_names, module=None):
"""
Creates a new benchmark type for each ufunc name in :samp:`ufunc_names`
and for each module name in :samp:`{module_names}`. Sets created types
as attributes of the module :samp:`{module}`.
"""
def time_array_op(self, shape, dtype="float64"):
"""
Timing for single input ufuncs (single array input).
"""
self._ufunc(self._a_ary, out=self._c_ary)
def time_array_array_op(self, shape, dtype="float64"):
"""
Timing for two input ufuncs (two array inputs).
"""
self._ufunc(self._a_ary, self._b_ary, out=self._c_ary)
def time_array_scalar_op(self, shape, dtype="float64"):
"""
Timing for two input ufuncs (one array input, one scalar input).
"""
self._ufunc(self._a_ary, self._b_scalar, out=self._c_ary)
method_dict = \
{
"time_array_op": time_array_op,
"time_array_array_op": time_array_array_op,
"time_array_scalar_op": time_array_scalar_op
}
for module_name in module_names:
for ufunc_name in ufunc_names:
bench_type = create_ufunc_bench(module_name, ufunc_name, method_dict)
if (module is not None) and (bench_type is not None):
setattr(module, bench_type.__name__, bench_type)
[docs]def find_scipy_ufuncs():
"""
Imports the :func:`scipy.special.erf` ufunc and sets it as an attribute
of the :mod:`numpy` and :mod:`mpi_array` modules.
"""
try:
import scipy.special as special
import numpy as np
import mpi_array as mpia
for m in (np, mpia):
setattr(m, "erf", special.erf)
except Exception:
pass
find_scipy_ufuncs()
UFUNC_NAMES = ["add", "subtract", "multiply", "true_divide", "log", "log10", "erf"]
create_ufunc_benchmarks(
module_names=("mpi_array", "numpy"),
ufunc_names=UFUNC_NAMES,
module=_sys.modules[__name__]
)
__all__ = [s for s in dir() if not s.startswith('_')]