diff options
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | CHANGES | 2 | ||||
-rw-r--r-- | asv.conf.json | 160 | ||||
-rw-r--r-- | bench/bench.py | 146 | ||||
-rw-r--r-- | bench/bench_base.yaml | 42 | ||||
-rw-r--r-- | bench/bench_numpy.yaml | 23 | ||||
-rw-r--r-- | benchmarks/00_common.py | 2 | ||||
-rw-r--r-- | benchmarks/01_registry_creation.py | 10 | ||||
-rw-r--r-- | benchmarks/10_registry.py | 101 | ||||
-rw-r--r-- | benchmarks/20_quantity.py | 56 | ||||
-rw-r--r-- | benchmarks/30_numpy.py | 97 | ||||
-rw-r--r-- | benchmarks/__init__.py | 0 | ||||
-rw-r--r-- | benchmarks/hashes.txt | 32 | ||||
-rw-r--r-- | benchmarks/util.py | 38 |
14 files changed, 500 insertions, 212 deletions
@@ -31,3 +31,6 @@ notebooks/pandas_test.csv # dask stuff dask-worker-space + +# airspeed velocity bechmark +.asv/ @@ -4,7 +4,7 @@ Pint Changelog 0.17 (unreleased) ----------------- -- Nothing changed yet. +- Implemented benchmarks based on airspeed velocity. 0.16.1 (2020-09-22) diff --git a/asv.conf.json b/asv.conf.json new file mode 100644 index 0000000..c8c82b5 --- /dev/null +++ b/asv.conf.json @@ -0,0 +1,160 @@ +{ + // The version of the config file format. Do not change, unless + // you know what you are doing. + "version": 1, + + // The name of the project being benchmarked + "project": "pint", + + // The project's homepage + "project_url": "https://github.com/hgrecco/pint", + + // The URL or local path of the source code repository for the + // project being benchmarked + "repo": ".", + + // The Python project's subdirectory in your repo. If missing or + // the empty string, the project is assumed to be located at the root + // of the repository. + // "repo_subdir": "", + + // Customizable commands for building, installing, and + // uninstalling the project. See asv.conf.json documentation. + // + // "install_command": ["in-dir={env_dir} python -mpip install {wheel_file}"], + // "uninstall_command": ["return-code=any python -mpip uninstall -y {project}"], + // "build_command": [ + // "python setup.py build", + // "PIP_NO_BUILD_ISOLATION=false python -mpip wheel --no-deps --no-index -w {build_cache_dir} {build_dir}" + // ], + + // List of branches to benchmark. If not provided, defaults to "master" + // (for git) or "default" (for mercurial). + // "branches": ["master"], // for git + // "branches": ["default"], // for mercurial + + // The DVCS being used. If not set, it will be automatically + // determined from "repo" by looking at the protocol in the URL + // (if remote), or by looking for special directories, such as + // ".git" (if local). + // "dvcs": "git", + + // The tool to use to create environments. May be "conda", + // "virtualenv" or other value depending on the plugins in use. + // If missing or the empty string, the tool will be automatically + // determined by looking for tools on the PATH environment + // variable. + "environment_type": "conda", + + // timeout in seconds for installing any dependencies in environment + // defaults to 10 min + //"install_timeout": 600, + + // the base URL to show a commit for the project. + "show_commit_url": "http://github.com/hgrecco/pint/commit/", + + // The Pythons you'd like to test against. If not provided, defaults + // to the current version of Python used to run `asv`. + "pythons": ["3.7"], + + // The list of conda channel names to be searched for benchmark + // dependency packages in the specified order + // "conda_channels": ["conda-forge", "defaults"], + + // The matrix of dependencies to test. Each key is the name of a + // package (in PyPI) and the values are version numbers. An empty + // list or empty string indicates to just test against the default + // (latest) version. null indicates that the package is to not be + // installed. If the package to be tested is only available from + // PyPi, and the 'environment_type' is conda, then you can preface + // the package name by 'pip+', and the package will be installed via + // pip (with all the conda available packages installed first, + // followed by the pip installed packages). + + "matrix": { + "numpy": ["1.19"], + // "six": ["", null], // test with and without six installed + // "pip+emcee": [""], // emcee is only available for install with pip. + }, + + // Combinations of libraries/python versions can be excluded/included + // from the set to test. Each entry is a dictionary containing additional + // key-value pairs to include/exclude. + // + // An exclude entry excludes entries where all values match. The + // values are regexps that should match the whole string. + // + // An include entry adds an environment. Only the packages listed + // are installed. The 'python' key is required. The exclude rules + // do not apply to includes. + // + // In addition to package names, the following keys are available: + // + // - python + // Python version, as in the *pythons* variable above. + // - environment_type + // Environment type, as above. + // - sys_platform + // Platform, as in sys.platform. Possible values for the common + // cases: 'linux2', 'win32', 'cygwin', 'darwin'. + // + // "exclude": [ + // {"python": "3.2", "sys_platform": "win32"}, // skip py3.2 on windows + // {"environment_type": "conda", "six": null}, // don't run without six on conda + // ], + // + // "include": [ + // // additional env for python2.7 + // {"python": "2.7", "numpy": "1.8"}, + // // additional env if run on windows+conda + // {"platform": "win32", "environment_type": "conda", "python": "2.7", "libpython": ""}, + // ], + + // The directory (relative to the current directory) that benchmarks are + // stored in. If not provided, defaults to "benchmarks" + // "benchmark_dir": "benchmarks", + + // The directory (relative to the current directory) to cache the Python + // environments in. If not provided, defaults to "env" + "env_dir": ".asv/env", + + // The directory (relative to the current directory) that raw benchmark + // results are stored in. If not provided, defaults to "results". + "results_dir": ".asv/results", + + // The directory (relative to the current directory) that the html tree + // should be written to. If not provided, defaults to "html". + "html_dir": ".asv/html", + + // The number of characters to retain in the commit hashes. + // "hash_length": 8, + + // `asv` will cache results of the recent builds in each + // environment, making them faster to install next time. This is + // the number of builds to keep, per environment. + // "build_cache_size": 2, + + // The commits after which the regression search in `asv publish` + // should start looking for regressions. Dictionary whose keys are + // regexps matching to benchmark names, and values corresponding to + // the commit (exclusive) after which to start looking for + // regressions. The default is to start from the first commit + // with results. If the commit is `null`, regression detection is + // skipped for the matching benchmark. + // + // "regressions_first_commits": { + // "some_benchmark": "352cdf", // Consider regressions only after this commit + // "another_benchmark": null, // Skip regression detection altogether + // }, + + // The thresholds for relative change in results, after which `asv + // publish` starts reporting regressions. Dictionary of the same + // form as in ``regressions_first_commits``, with values + // indicating the thresholds. If multiple entries match, the + // maximum is taken. If no entry matches, the default is 5%. + // + // "regressions_thresholds": { + // "some_benchmark": 0.01, // Threshold of 1% + // "another_benchmark": 0.5, // Threshold of 50% + // }, +} diff --git a/bench/bench.py b/bench/bench.py deleted file mode 100644 index c3c9323..0000000 --- a/bench/bench.py +++ /dev/null @@ -1,146 +0,0 @@ -import copy -import fnmatch -import os -from timeit import Timer - -import yaml - - -def time_stmt(stmt="pass", setup="pass", number=0, repeat=3): - """Timer function with the same behaviour as running `python -m timeit ` - in the command line. - - Parameters - ---------- - stmt : str - (Default value = "pass") - setup : str - (Default value = "pass") - number : int - (Default value = 0) - repeat : int - (Default value = 3) - - Returns - ------- - float - elapsed time in seconds or NaN if the command failed. - - """ - - t = Timer(stmt, setup) - - if not number: - # determine number so that 0.2 <= total time < 2.0 - for i in range(1, 10): - number = 10 ** i - - try: - x = t.timeit(number) - except Exception: - print(t.print_exc()) - return float("NaN") - - if x >= 0.2: - break - - try: - r = t.repeat(repeat, number) - except Exception: - print(t.print_exc()) - return float("NaN") - - best = min(r) - - return best / number - - -def build_task(task, name="", setup="", number=0, repeat=3): - nt = copy.copy(task) - - nt["name"] = (name + " " + task.get("name", "")).strip() - nt["setup"] = (setup + "\n" + task.get("setup", "")).strip("\n") - nt["stmt"] = task.get("stmt", "") - nt["number"] = task.get("number", number) - nt["repeat"] = task.get("repeat", repeat) - - return nt - - -def time_task(name, stmt="pass", setup="pass", number=0, repeat=3, stmts="", base=""): - - if base: - nvalue = time_stmt(stmt=base, setup=setup, number=number, repeat=repeat) - yield name + " (base)", nvalue - suffix = " (normalized)" - else: - nvalue = 1.0 - suffix = "" - - if stmt: - value = time_stmt(stmt=stmt, setup=setup, number=number, repeat=repeat) - yield name, value / nvalue - - for task in stmts: - new_task = build_task(task, name, setup, number, repeat) - for task_name, value in time_task(**new_task): - yield task_name + suffix, value / nvalue - - -def time_file(filename, name="", setup="", number=0, repeat=3): - """Open a yaml benchmark file an time each statement, - - yields a tuple with filename, task name, time in seconds. - - Parameters - ---------- - filename : - - name : - (Default value = "") - setup : - (Default value = "") - number : - (Default value = 0) - repeat : - (Default value = 3) - - Returns - ------- - - """ - with open(filename, "r") as fp: - tasks = yaml.load(fp) - - for task in tasks: - new_task = build_task(task, name, setup, number, repeat) - for task_name, value in time_task(**new_task): - yield task_name, value - - -def recursive_glob(rootdir=".", pattern="*"): - return [ - os.path.join(looproot, filename) - for looproot, _, filenames in os.walk(rootdir) - for filename in filenames - if fnmatch.fnmatch(filename, pattern) - ] - - -def main(filenames=None): - if not filenames: - filenames = recursive_glob(".", "bench_*.yaml") - elif isinstance(filenames, str): - filenames = [filenames] - - for filename in filenames: - print(filename) - print("-" * len(filename)) - print() - for task_name, value in time_file(filename): - print(f"{value:.2e} {task_name}") - print() - - -if __name__ == "__main__": - main() diff --git a/bench/bench_base.yaml b/bench/bench_base.yaml deleted file mode 100644 index f767a04..0000000 --- a/bench/bench_base.yaml +++ /dev/null @@ -1,42 +0,0 @@ - - -- name: importing - stmt: import pint - -- name: empty registry - setup: import pint - stmt: ureg = pint.UnitRegistry(None) - -- name: default registry - setup: import pint - stmt: ureg = pint.UnitRegistry() - -- name: finding meter - setup: | - import pint - ureg = pint.UnitRegistry() - stmts: - - name: (attr) - stmt: q = ureg.meter - - name: (item) - stmt: q = ureg['meter'] - -- name: base units - setup: | - import pint - ureg = pint.UnitRegistry() - stmts: - - name: meter - stmt: ureg.get_base_units('meter') - - name: yard - stmt: ureg.get_base_units('yard') - - name: meter / second - stmt: ureg.get_base_units('meter / second') - - name: yard / minute - stmt: ureg.get_base_units('yard / minute') - -- name: build cache - setup: | - import pint - ureg = pint.UnitRegistry() - stmt: ureg._build_cache() diff --git a/bench/bench_numpy.yaml b/bench/bench_numpy.yaml deleted file mode 100644 index 8609bc0..0000000 --- a/bench/bench_numpy.yaml +++ /dev/null @@ -1,23 +0,0 @@ - - -- name: NumPy - setup: | - import numpy as np - import pint - ureg = pint.UnitRegistry() - stmts: - - name: cosine - setup: | - d = np.arange(0, 90, 10) - r = np.deg2rad(d) - base: np.cos(r) - stmts: - - name: radian - setup: x = r * ureg.radian - stmt: np.cos(x) - - name: dimensionless - setup: x = r * ureg.dimensionless - stmt: np.cos(x) - - name: degree - setup: x = d * ureg.degree - stmt: np.cos(x) diff --git a/benchmarks/00_common.py b/benchmarks/00_common.py new file mode 100644 index 0000000..4444d5d --- /dev/null +++ b/benchmarks/00_common.py @@ -0,0 +1,2 @@ +def time_import(): + import pint diff --git a/benchmarks/01_registry_creation.py b/benchmarks/01_registry_creation.py new file mode 100644 index 0000000..e555585 --- /dev/null +++ b/benchmarks/01_registry_creation.py @@ -0,0 +1,10 @@ +import pint + +from . import util + + +def time_create_registry(args): + pint.UnitRegistry(*args) + + +time_create_registry.params = [[(None,), tuple(), (util.get_tiny_def(),)]] diff --git a/benchmarks/10_registry.py b/benchmarks/10_registry.py new file mode 100644 index 0000000..d95a53f --- /dev/null +++ b/benchmarks/10_registry.py @@ -0,0 +1,101 @@ +import pint + +from . import util + +units = ("meter", "kilometer", "second", "minute", "angstrom") + +other_units = ("meter", "angstrom", "kilometer/second", "angstrom/minute") + +all_values = ("int", "float", "complex") + +ureg = None +data = {} + + +def setup(*args): + + global ureg, data + + data["int"] = 1 + data["float"] = 1.0 + data["complex"] = complex(1, 2) + + ureg = pint.UnitRegistry(util.get_tiny_def()) + + +def my_setup(*args): + global data + setup(*args) + for unit in units + other_units: + data["uc_%s" % unit] = pint.registry.to_units_container(unit, ureg) + + +def time_build_cache(): + ureg._build_cache() + + +def time_getattr(key): + getattr(ureg, key) + + +time_getattr.params = units + + +def time_getitem(key): + ureg[key] + + +time_getitem.params = units + + +def time_parse_unit_name(key): + ureg.parse_unit_name(key) + + +time_parse_unit_name.params = units + + +def time_parse_units(key): + ureg.parse_units(key) + + +time_parse_units.params = units + + +def time_parse_expression(key): + ureg.parse_expression("1.0 " + key) + + +time_parse_expression.params = units + + +def time_base_units(unit): + ureg.get_base_units(unit) + + +time_base_units.params = other_units + + +def time_to_units_container_registry(unit): + pint.registry.to_units_container(unit, ureg) + + +time_to_units_container_registry.params = other_units + + +def time_to_units_container_detached(unit): + pint.registry.to_units_container(unit, ureg) + + +time_to_units_container_detached.params = other_units + + +def time_convert_from_uc(key): + src, dst = key + ureg._convert(1.0, data[src], data[dst]) + + +time_convert_from_uc.setup = my_setup +time_convert_from_uc.params = [ + (("uc_meter", "uc_kilometer"), ("uc_kilometer/second", "uc_angstrom/minute")) +] diff --git a/benchmarks/20_quantity.py b/benchmarks/20_quantity.py new file mode 100644 index 0000000..5f6dd41 --- /dev/null +++ b/benchmarks/20_quantity.py @@ -0,0 +1,56 @@ +import itertools as it +import operator + +import pint + +from . import util + +units = ("meter", "kilometer", "second", "minute", "angstrom") +all_values = ("int", "float", "complex") +all_values_q = tuple( + "%s_%s" % (a, b) for a, b in it.product(all_values, ("meter", "kilometer")) +) + +op1 = (operator.neg, operator.truth) +op2_cmp = (operator.eq,) # operator.lt) +op2_math = (operator.add, operator.sub, operator.mul, operator.truediv) + +ureg = None +data = {} + + +def setup(*args): + + global ureg, data + + data["int"] = 1 + data["float"] = 1.0 + data["complex"] = complex(1, 2) + + ureg = pint.UnitRegistry(util.get_tiny_def()) + + for key in all_values: + data[key + "_meter"] = data[key] * ureg.meter + data[key + "_kilometer"] = data[key] * ureg.kilometer + + +def time_build_by_mul(key): + data[key] * ureg.meter + + +time_build_by_mul.params = all_values + + +def time_op1(key, op): + op(data[key]) + + +time_op1.params = [all_values_q, op1] + + +def time_op2(keys, op): + key1, key2 = keys + op(data[key1], data[key2]) + + +time_op2.params = [tuple(it.product(all_values_q, all_values_q)), op2_math + op2_cmp] diff --git a/benchmarks/30_numpy.py b/benchmarks/30_numpy.py new file mode 100644 index 0000000..ec83833 --- /dev/null +++ b/benchmarks/30_numpy.py @@ -0,0 +1,97 @@ +import itertools as it +import operator + +import numpy as np + +import pint + +from . import util + +lengths = ("short", "mid") +all_values = tuple( + "%s_%s" % (a, b) for a, b in it.product(lengths, ("list", "tuple", "array")) +) +all_arrays = ("short_array", "mid_array") +units = ("meter", "kilometer") +all_arrays_q = tuple("%s_%s" % (a, b) for a, b in it.product(all_arrays, units)) + +ureg = None +data = {} +op1 = (operator.neg,) # operator.truth, +op2_cmp = (operator.eq, operator.lt) +op2_math = (operator.add, operator.sub, operator.mul, operator.truediv) +numpy_op2_cmp = (np.equal, np.less) +numpy_op2_math = (np.add, np.subtract, np.multiply, np.true_divide) + + +def float_range(n): + return (float(x) for x in range(1, n + 1)) + + +def setup(*args): + + global ureg, data + short = list(float_range(3)) + mid = list(float_range(1_000)) + + data["short_list"] = short + data["short_tuple"] = tuple(short) + data["short_array"] = np.asarray(short) + data["mid_list"] = mid + data["mid_tuple"] = tuple(mid) + data["mid_array"] = np.asarray(mid) + + ureg = pint.UnitRegistry(util.get_tiny_def()) + + for key in all_arrays: + data[key + "_meter"] = data[key] * ureg.meter + data[key + "_kilometer"] = data[key] * ureg.kilometer + + +def time_finding_meter_getattr(): + ureg.meter + + +def time_finding_meter_getitem(): + ureg["meter"] + + +def time_base_units(unit): + ureg.get_base_units(unit) + + +time_base_units.params = ["meter", "angstrom", "meter/second", "angstrom/minute"] + + +def time_build_by_mul(key): + data[key] * ureg.meter + + +time_build_by_mul.params = all_arrays + + +def time_op1(key, op): + op(data[key]) + + +time_op1.params = [all_arrays_q, op1 + (np.sqrt, np.square)] + + +def time_op2(keys, op): + key1, key2 = keys + op(data[key1], data[key2]) + + +time_op2.params = [ + ( + ("short_array_meter", "short_array_meter"), + ("short_array_meter", "short_array_kilometer"), + ("short_array_kilometer", "short_array_meter"), + ("short_array_kilometer", "short_array_kilometer"), + ("mid_array_meter", "mid_array_meter"), + ("mid_array_meter", "mid_array_kilometer"), + ("mid_array_kilometer", "mid_array_meter"), + ("mid_array_kilometer", "mid_array_kilometer"), + ), + op2_math + op2_cmp + numpy_op2_math + numpy_op2_cmp, +] diff --git a/benchmarks/__init__.py b/benchmarks/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/benchmarks/__init__.py diff --git a/benchmarks/hashes.txt b/benchmarks/hashes.txt new file mode 100644 index 0000000..473130b --- /dev/null +++ b/benchmarks/hashes.txt @@ -0,0 +1,32 @@ +04468ad216555cab2b50dbae62b57f56bf694cc3 +a964aebfb904d5ac4cb2645478f38189b27ad97a +7f77dabbfcd1be8b5943cbb6ee3b049c66d337f6 +d78cc94a9538aeb91b30494bdcde1348cab0e2f6 +040b01283470b95d23a244eb1a791dfd189502c6 +5ca84aca6002ec1550417bf791b3f8cc9e551818 +2dc133ef61baf3f6b2b54814cbeb3299282ee794 +1d3f26bfcabab7751bd324d1fd48825667c2e949 +126e00f691007ff71e6a2c327b19f92d8a478adb +2c0c4ee67f732b0d1b7530e8363b15c57b936b97 +619b95ee13f82560c3cd596095bcd606ba0277ae +6e18639b3db4121322e0fc7009df463b8dc7ee9f +fe0a64b270dbde0946fdcb0819aad178d42f77e6 +6aea7d4aaf424e2a514c004f99b271ecfb446237 +c48c441ec6b8919f104edd6affb57b69aa275a38 +7d9837ead056af2e36009a0b0ed8d1a9e9bf3947 +e7e7de5ca2e1c19963be8a918369fb19186f9a73 +f65bebb6930fdd5ec891a905c30489a52add05f6 +a171cdfbab64903ae67d7461b0904f350191ee29 +7196ae6794a5f7b3ab0a48e880f1797a218e59de +5ee3ff950709ab356d7349d6b56818b0c5fe624b +697834d17d6fb5bb5d0adf945a5d5f5d187506a1 +2b296e79f0260b8d547af8d078178f079d90a3d9 +a91762071e3cecc8d995c993a3175f84ed9ec804 +82b2c2f97b3f929568a07850de4f75b0d16f3c56 +3d713cf920c1f164c1c3c2fcfab96956467b0d64 +eed85601c8bdf0829ed256cdf2af9982bc9ed5a2 +4e6dbff740292ae57230686ad4417e46d63e171c +222a1c89e9ce5c24debc749558cf5eb3b231f89a +85603e644e9ba004188ef79e499b9a40557b446c +9a05e600275c592451bda9287205fe0e99a87bb3 +6b0b531d5311ab0f61aefd3e76a8dec9144d6b81
\ No newline at end of file diff --git a/benchmarks/util.py b/benchmarks/util.py new file mode 100644 index 0000000..4e72048 --- /dev/null +++ b/benchmarks/util.py @@ -0,0 +1,38 @@ +import io + +SMALL_VEC_LEN = 3 +MID_VEC_LEN = 1_000 +LARGE_VEC_LEN = 1_000_000 + +TINY_DEF = """ +yocto- = 1e-24 = y- +zepto- = 1e-21 = z- +atto- = 1e-18 = a- +femto- = 1e-15 = f- +pico- = 1e-12 = p- +nano- = 1e-9 = n- +micro- = 1e-6 = µ- = u- +milli- = 1e-3 = m- +centi- = 1e-2 = c- +deci- = 1e-1 = d- +deca- = 1e+1 = da- = deka- +hecto- = 1e2 = h- +kilo- = 1e3 = k- +mega- = 1e6 = M- +giga- = 1e9 = G- +tera- = 1e12 = T- +peta- = 1e15 = P- +exa- = 1e18 = E- +zetta- = 1e21 = Z- +yotta- = 1e24 = Y- + +meter = [length] = m = metre +second = [time] = s = sec + +angstrom = 1e-10 * meter = Å = ångström = Å +minute = 60 * second = min +""" + + +def get_tiny_def(): + return io.StringIO(TINY_DEF) |