1import numpy as np
2import bottleneck as bn
3from .autotimeit import autotimeit
4
5__all__ = ["bench"]
6
7
8def bench(
9 shapes=[(100,), (1000, 1000), (1000, 1000), (1000, 1000), (1000, 1000)],
10 axes=[0, 0, 0, 1, 1],
11 nans=[False, False, True, False, True],
12 dtype="float64",
13 order="C",
14 functions=None,
15):
16 """
17 Bottleneck benchmark.
18
19 Parameters
20 ----------
21 shapes : list, optional
22 A list of tuple shapes of input arrays to use in the benchmark.
23 axes : list, optional
24 List of axes along which to perform the calculations that are being
25 benchmarked.
26 nans : list, optional
27 A list of the bools (True or False), one for each tuple in the
28 `shapes` list, that tells whether the input arrays should be randomly
29 filled with one-fifth NaNs.
30 dtype : str, optional
31 Data type string such as 'float64', which is the default.
32 order : {'C', 'F'}, optional
33 Whether to store multidimensional data in C- or Fortran-contiguous
34 (row- or column-wise) order in memory.
35 functions : {list, None}, optional
36 A list of strings specifying which functions to include in the
37 benchmark. By default (None) all functions are included in the
38 benchmark.
39
40 Returns
41 -------
42 A benchmark report is printed to stdout.
43
44 """
45
46 if len(shapes) != len(nans):
47 raise ValueError("`shapes` and `nans` must have the same length")
48 if len(shapes) != len(axes):
49 raise ValueError("`shapes` and `axes` must have the same length")
50
51 # Header
52 print("Bottleneck performance benchmark")
53 print(" Bottleneck %s; Numpy %s" % (bn.__version__, np.__version__))
54 print(" Speed is NumPy time divided by Bottleneck time")
55 print(" NaN means approx one-fifth NaNs; %s used" % str(dtype))
56
57 print("")
58 header = [" " * 11]
59 for nan in nans:
60 if nan:
61 header.append("NaN".center(11))
62 else:
63 header.append("no NaN".center(11))
64 print("".join(header))
65 header = ["".join(str(shape).split(" ")).center(11) for shape in shapes]
66 header = [" " * 12] + header
67 print("".join(header))
68 header = ["".join(("axis=" + str(axis)).split(" ")).center(11) for axis in axes]
69 header = [" " * 12] + header
70 print("".join(header))
71
72 suite = benchsuite(shapes, dtype, nans, axes, order, functions)
73 for test in suite:
74 name = test["name"].ljust(12)
75 fmt = name + "%7.1f" + "%11.1f" * (len(shapes) - 1)
76 speed = timer(test["statements"], test["setups"])
77 print(fmt % tuple(speed))
78
79
80def timer(statements, setups):
81 speed = []
82 if len(statements) != 2:
83 raise ValueError("Two statements needed.")
84 for setup in setups:
85 with np.errstate(invalid="ignore"):
86 t0 = autotimeit(statements[0], setup)
87 t1 = autotimeit(statements[1], setup)
88 speed.append(t1 / t0)
89 return speed
90
91
92def getarray(shape, dtype, nans, order):
93 a = np.arange(np.prod(shape), dtype=dtype)
94 if nans and issubclass(a.dtype.type, np.inexact):
95 a[::5] = np.nan
96 rs = np.random.RandomState(shape)
97 rs.shuffle(a)
98 return np.array(a.reshape(*shape), order=order)
99
100
101def benchsuite(shapes, dtype, nans, axes, order, functions):
102
103 suite = []
104
105 def getsetups(setup, shapes, nans, axes, dtype, order):
106 template = """
107 from bottleneck.benchmark.bench import getarray
108 a = getarray(%s, '%s', %s, '%s')
109 axis=%s
110 %s"""
111 setups = []
112 for shape, axis, nan in zip(shapes, axes, nans):
113 s = template % (
114 str(shape),
115 str(dtype),
116 str(nan),
117 str(order),
118 str(axis),
119 setup,
120 )
121 s = "\n".join([line.strip() for line in s.split("\n")])
122 setups.append(s)
123 return setups
124
125 # non-moving window functions
126 funcs = bn.get_functions("reduce", as_string=True)
127 funcs += ["rankdata", "nanrankdata"]
128 for func in funcs:
129 if functions is not None and func not in functions:
130 continue
131 run = {}
132 run["name"] = func
133 run["statements"] = ["bn_func(a, axis)", "sl_func(a, axis)"]
134 setup = """
135 from bottleneck import %s as bn_func
136 try: from numpy import %s as sl_func
137 except ImportError: from bottleneck.slow import %s as sl_func
138 if "%s" == "median": from bottleneck.slow import median as sl_func
139 """ % (
140 func,
141 func,
142 func,
143 func,
144 )
145 run["setups"] = getsetups(setup, shapes, nans, axes, dtype, order)
146 suite.append(run)
147
148 # partition, argpartition
149 funcs = ["partition", "argpartition"]
150 for func in funcs:
151 if functions is not None and func not in functions:
152 continue
153 run = {}
154 run["name"] = func
155 run["statements"] = ["bn_func(a, n, axis)", "sl_func(a, n, axis)"]
156 setup = """
157 from bottleneck import %s as bn_func
158 from bottleneck.slow import %s as sl_func
159 if axis is None: n = a.size
160 else: n = a.shape[axis] - 1
161 n = max(n // 2, 0)
162 """ % (
163 func,
164 func,
165 )
166 run["setups"] = getsetups(setup, shapes, nans, axes, dtype, order)
167 suite.append(run)
168
169 # replace, push
170 funcs = ["replace", "push"]
171 for func in funcs:
172 if functions is not None and func not in functions:
173 continue
174 run = {}
175 run["name"] = func
176 if func == "replace":
177 run["statements"] = ["bn_func(a, nan, 0)", "slow_func(a, nan, 0)"]
178 elif func == "push":
179 run["statements"] = ["bn_func(a, 5, axis)", "slow_func(a, 5, axis)"]
180 else:
181 raise ValueError("Unknow function name")
182 setup = """
183 from numpy import nan
184 from bottleneck import %s as bn_func
185 from bottleneck.slow import %s as slow_func
186 """ % (
187 func,
188 func,
189 )
190 run["setups"] = getsetups(setup, shapes, nans, axes, dtype, order)
191 suite.append(run)
192
193 # moving window functions
194 funcs = bn.get_functions("move", as_string=True)
195 for func in funcs:
196 if functions is not None and func not in functions:
197 continue
198 run = {}
199 run["name"] = func
200 run["statements"] = ["bn_func(a, w, 1, axis)", "sw_func(a, w, 1, axis)"]
201 setup = """
202 from bottleneck.slow.move import %s as sw_func
203 from bottleneck import %s as bn_func
204 w = a.shape[axis] // 5
205 """ % (
206 func,
207 func,
208 )
209 run["setups"] = getsetups(setup, shapes, nans, axes, dtype, order)
210 suite.append(run)
211
212 return suite