From 0755af1b139e52c678dfacf019625c8636b5a980 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Thu, 1 Sep 2016 16:28:09 +0200 Subject: [PATCH 1/9] add new benchmarks that compare the decimal and fractions modules using the same benchmarking code --- performance/benchmarks/__init__.py | 12 +++ performance/benchmarks/bm_telco_fractions.py | 100 +++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 performance/benchmarks/bm_telco_fractions.py diff --git a/performance/benchmarks/__init__.py b/performance/benchmarks/__init__.py index e491044f..fe9ae72e 100644 --- a/performance/benchmarks/__init__.py +++ b/performance/benchmarks/__init__.py @@ -226,6 +226,18 @@ def BM_Telco(python, options): return run_perf_script(python, options, bm_path) +@VersionRange() +def BM_Telco_Fractions(python, options): + bm_path = Relative("bm_telco_fractions.py") + return run_perf_script(python, options, bm_path, extra_args=['fractions']) + + +@VersionRange() +def BM_Telco_Decimal(python, options): + bm_path = Relative("bm_telco_fractions.py") + return run_perf_script(python, options, bm_path, extra_args=['decimal']) + + @VersionRange() def BM_Hexiom2(python, options): bm_path = Relative("bm_hexiom2.py") diff --git a/performance/benchmarks/bm_telco_fractions.py b/performance/benchmarks/bm_telco_fractions.py new file mode 100644 index 00000000..f1e7b66f --- /dev/null +++ b/performance/benchmarks/bm_telco_fractions.py @@ -0,0 +1,100 @@ +#-*- coding: UTF-8 -*- + +""" Telco Benchmark for measuring the performance of Fraction calculations + +http://www2.hursley.ibm.com/decimal/telco.html +http://www2.hursley.ibm.com/decimal/telcoSpec.html + +A call type indicator, c, is set from the bottom (least significant) bit of the duration (hence c is 0 or 1). +A r, r, is determined from the call type. Those calls with c=0 have a low r: 0.0013; the remainder (‘distance calls’) have a ‘premium’ r: 0.00894. (The rates are, very roughly, in Euros or dollarates per second.) +A price, p, for the call is then calculated (p=r*n). +A basic tax, b, is calculated: b=p*0.0675 (6.75%), and the total basic tax variable is then incremented (sumB=sumB+b). +For distance calls: a distance tax, d, is calculated: d=p*0.0341 (3.41%), and then the total distance tax variable is incremented (sumD=sumD+d). +The total price, t, is calculated (t=p+b, and, if a distance call, t=t+d). +The total prices variable is incremented (sumT=sumT+t). +The total price, t, is converted to a string, s. + +""" + +from struct import unpack +import os.path + +import perf + + +def rel_path(*path): + return os.path.join(os.path.dirname(__file__), *path) + +filename = rel_path("data", "telco-bench.b") + + +def run(cls, loops=1): + rates = list(map(cls, ('0.0013', '0.00894'))) + basictax = cls("0.0675") + disttax = cls("0.0341") + + values = [] + with open(filename, "rb") as infil: + for _ in range(20000): + datum = infil.read(8) + if datum == '': break + n, = unpack('>Q', datum) + values.append(n) + + start = perf.perf_counter() + + for _ in range(loops): + sumT = cls() # sum of total prices + sumB = cls() # sum of basic tax + sumD = cls() # sum of 'distance' tax + + for n in values: + calltype = n & 1 + r = rates[calltype] + + p = r * n + b = p * basictax + sumB += b + t = p + b + + if calltype: + d = p * disttax + sumD += d + t += d + + sumT += t + + return perf.perf_counter() - start + + +def run_bench(n, impl): + if impl == 'fractions': + from fractions import Fraction as backend_class + elif impl == 'quicktions': + from quicktions import Fraction as backend_class + elif impl == 'decimal': + from decimal import Decimal as backend_class + else: + raise ValueError("Invalid class name: '%s'" % impl) + + run(backend_class, loops=2) # warmup + return run(backend_class, n) + + +def prepare_subprocess_args(runner, args): + args.append(runner.args.backend) + + +if __name__ == "__main__": + import perf.text_runner + runner = perf.text_runner.TextRunner(name='telco') + runner.metadata['description'] = "Telco fractions benchmark" + runner.prepare_subprocess_args = prepare_subprocess_args + + parser = runner.argparser + backends = ["decimal", "fractions", "quicktions"] + parser.add_argument("backend", choices=backends, nargs='?', default="fractions") + + options = runner.parse_args() + runner.name += "/%s" % options.backend + runner.bench_sample_func(run_bench, options.backend) From 3bf2dc8670b323e5eddcaac784067a4d4b03cbf4 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Thu, 1 Sep 2016 17:22:12 +0200 Subject: [PATCH 2/9] move imports out of the main benchmark code --- performance/benchmarks/bm_telco_fractions.py | 23 ++++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/performance/benchmarks/bm_telco_fractions.py b/performance/benchmarks/bm_telco_fractions.py index f1e7b66f..f2621d11 100644 --- a/performance/benchmarks/bm_telco_fractions.py +++ b/performance/benchmarks/bm_telco_fractions.py @@ -67,18 +67,21 @@ def run(cls, loops=1): return perf.perf_counter() - start -def run_bench(n, impl): - if impl == 'fractions': +def run_bench(n, backend_class): + run(backend_class, loops=2) # warmup + return run(backend_class, n) + + +def find_benchmark_class(impl_name): + if impl_name == 'fractions': from fractions import Fraction as backend_class - elif impl == 'quicktions': + elif impl_name == 'quicktions': from quicktions import Fraction as backend_class - elif impl == 'decimal': + elif impl_name == 'decimal': from decimal import Decimal as backend_class else: - raise ValueError("Invalid class name: '%s'" % impl) - - run(backend_class, loops=2) # warmup - return run(backend_class, n) + raise ValueError("Invalid class name: '%s'" % impl_name) + return backend_class def prepare_subprocess_args(runner, args): @@ -97,4 +100,6 @@ def prepare_subprocess_args(runner, args): options = runner.parse_args() runner.name += "/%s" % options.backend - runner.bench_sample_func(run_bench, options.backend) + backend_class = find_benchmark_class(options.backend) + + runner.bench_sample_func(run_bench, backend_class) From 4324412af02b75d8fa2757486d8b78969ef6391e Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Thu, 1 Sep 2016 17:25:40 +0200 Subject: [PATCH 3/9] remove warmup run from benchmark function --- performance/benchmarks/bm_telco_fractions.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/performance/benchmarks/bm_telco_fractions.py b/performance/benchmarks/bm_telco_fractions.py index f2621d11..97b31134 100644 --- a/performance/benchmarks/bm_telco_fractions.py +++ b/performance/benchmarks/bm_telco_fractions.py @@ -67,9 +67,8 @@ def run(cls, loops=1): return perf.perf_counter() - start -def run_bench(n, backend_class): - run(backend_class, loops=2) # warmup - return run(backend_class, n) +def run_bench(loops, backend_class): + return run(backend_class, loops) def find_benchmark_class(impl_name): From 3380888204bc8c40a0a0d4b87837a7f0f94e5abf Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Thu, 1 Sep 2016 17:27:05 +0200 Subject: [PATCH 4/9] avoid creating large lists in Py2.x --- performance/benchmarks/bm_telco_fractions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/performance/benchmarks/bm_telco_fractions.py b/performance/benchmarks/bm_telco_fractions.py index 97b31134..168e31cb 100644 --- a/performance/benchmarks/bm_telco_fractions.py +++ b/performance/benchmarks/bm_telco_fractions.py @@ -20,6 +20,7 @@ import os.path import perf +from six.moves import xrange def rel_path(*path): @@ -35,15 +36,14 @@ def run(cls, loops=1): values = [] with open(filename, "rb") as infil: - for _ in range(20000): + for _ in xrange(20000): datum = infil.read(8) if datum == '': break n, = unpack('>Q', datum) values.append(n) start = perf.perf_counter() - - for _ in range(loops): + for _ in xrange(loops): sumT = cls() # sum of total prices sumB = cls() # sum of basic tax sumD = cls() # sum of 'distance' tax From 1e4482cf7f099b5a535cda0c1cfc4d01759f557f Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Thu, 1 Sep 2016 17:37:32 +0200 Subject: [PATCH 5/9] obey benchmark description and convert final result to string, then make sure we make use of the final result (and validate the calculations by comparing all results) --- performance/benchmarks/bm_telco_fractions.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/performance/benchmarks/bm_telco_fractions.py b/performance/benchmarks/bm_telco_fractions.py index 168e31cb..e02b155e 100644 --- a/performance/benchmarks/bm_telco_fractions.py +++ b/performance/benchmarks/bm_telco_fractions.py @@ -43,6 +43,7 @@ def run(cls, loops=1): values.append(n) start = perf.perf_counter() + results = set() for _ in xrange(loops): sumT = cls() # sum of total prices sumB = cls() # sum of basic tax @@ -64,7 +65,12 @@ def run(cls, loops=1): sumT += t - return perf.perf_counter() - start + results.add(str(sumT)) + + time = perf.perf_counter() - start + + assert len(results) == 1 + return time def run_bench(loops, backend_class): From 7f1fa779d596848a7d5d383c8a5ee869bd1d4338 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Thu, 1 Sep 2016 17:39:14 +0200 Subject: [PATCH 6/9] replace dead benchmark links --- performance/benchmarks/bm_telco.py | 4 ++-- performance/benchmarks/bm_telco_fractions.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/performance/benchmarks/bm_telco.py b/performance/benchmarks/bm_telco.py index 1cef2aa3..1af510f3 100644 --- a/performance/benchmarks/bm_telco.py +++ b/performance/benchmarks/bm_telco.py @@ -1,8 +1,8 @@ #-*- coding: UTF-8 -*- """ Telco Benchmark for measuring the performance of decimal calculations -http://www2.hursley.ibm.com/decimal/telco.html -http://www2.hursley.ibm.com/decimal/telcoSpec.html +http://speleotrove.com/decimal/telco.html +http://speleotrove.com/decimal/telcoSpec.html A call type indicator, c, is set from the bottom (least significant) bit of the duration (hence c is 0 or 1). A r, r, is determined from the call type. Those calls with c=0 have a low r: 0.0013; the remainder (‘distance calls’) have a ‘premium’ r: 0.00894. (The rates are, very roughly, in Euros or dollarates per second.) diff --git a/performance/benchmarks/bm_telco_fractions.py b/performance/benchmarks/bm_telco_fractions.py index e02b155e..6d70033b 100644 --- a/performance/benchmarks/bm_telco_fractions.py +++ b/performance/benchmarks/bm_telco_fractions.py @@ -2,8 +2,8 @@ """ Telco Benchmark for measuring the performance of Fraction calculations -http://www2.hursley.ibm.com/decimal/telco.html -http://www2.hursley.ibm.com/decimal/telcoSpec.html +http://speleotrove.com/decimal/telco.html +http://speleotrove.com/decimal/telcoSpec.html A call type indicator, c, is set from the bottom (least significant) bit of the duration (hence c is 0 or 1). A r, r, is determined from the call type. Those calls with c=0 have a low r: 0.0013; the remainder (‘distance calls’) have a ‘premium’ r: 0.00894. (The rates are, very roughly, in Euros or dollarates per second.) From 0f7041d1b15643c25bc9e7c3c51ec60a71f8417b Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Fri, 2 Sep 2016 16:09:31 +0200 Subject: [PATCH 7/9] use absolute imports in Py2 --- performance/benchmarks/bm_telco_fractions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/performance/benchmarks/bm_telco_fractions.py b/performance/benchmarks/bm_telco_fractions.py index 6d70033b..244e0b31 100644 --- a/performance/benchmarks/bm_telco_fractions.py +++ b/performance/benchmarks/bm_telco_fractions.py @@ -16,6 +16,8 @@ """ +from __future__ import absolute_import + from struct import unpack import os.path From ff6df486f0d32f4dd030a2a62763e5a284f048b7 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Fri, 2 Sep 2016 16:21:01 +0200 Subject: [PATCH 8/9] use decimal formatting in benchmark, and use more of it --- performance/benchmarks/bm_telco_fractions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/performance/benchmarks/bm_telco_fractions.py b/performance/benchmarks/bm_telco_fractions.py index 244e0b31..a80b6591 100644 --- a/performance/benchmarks/bm_telco_fractions.py +++ b/performance/benchmarks/bm_telco_fractions.py @@ -67,11 +67,11 @@ def run(cls, loops=1): sumT += t - results.add(str(sumT)) + results.add('%.4f' % t) time = perf.perf_counter() - start - assert len(results) == 1 + assert 1 <= len(results) <= len(values) return time From 6ebc4b0156adf8ccc0f529e6e7e34904b95934fe Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Fri, 2 Sep 2016 16:51:56 +0200 Subject: [PATCH 9/9] rename telco based fractions benchmark to "bm_fractions.py" and only run it against the "fractions" module by default --- performance/benchmarks/__init__.py | 10 ++-------- .../{bm_telco_fractions.py => bm_fractions.py} | 4 ++-- 2 files changed, 4 insertions(+), 10 deletions(-) rename performance/benchmarks/{bm_telco_fractions.py => bm_fractions.py} (95%) diff --git a/performance/benchmarks/__init__.py b/performance/benchmarks/__init__.py index fe9ae72e..8af7ed80 100644 --- a/performance/benchmarks/__init__.py +++ b/performance/benchmarks/__init__.py @@ -227,17 +227,11 @@ def BM_Telco(python, options): @VersionRange() -def BM_Telco_Fractions(python, options): - bm_path = Relative("bm_telco_fractions.py") +def BM_Fractions(python, options): + bm_path = Relative("bm_fractions.py") return run_perf_script(python, options, bm_path, extra_args=['fractions']) -@VersionRange() -def BM_Telco_Decimal(python, options): - bm_path = Relative("bm_telco_fractions.py") - return run_perf_script(python, options, bm_path, extra_args=['decimal']) - - @VersionRange() def BM_Hexiom2(python, options): bm_path = Relative("bm_hexiom2.py") diff --git a/performance/benchmarks/bm_telco_fractions.py b/performance/benchmarks/bm_fractions.py similarity index 95% rename from performance/benchmarks/bm_telco_fractions.py rename to performance/benchmarks/bm_fractions.py index a80b6591..5f4b3bc7 100644 --- a/performance/benchmarks/bm_telco_fractions.py +++ b/performance/benchmarks/bm_fractions.py @@ -97,8 +97,8 @@ def prepare_subprocess_args(runner, args): if __name__ == "__main__": import perf.text_runner - runner = perf.text_runner.TextRunner(name='telco') - runner.metadata['description'] = "Telco fractions benchmark" + runner = perf.text_runner.TextRunner(name='fractions') + runner.metadata['description'] = "Fractions benchmark adapted from the telco decimal benchmark" runner.prepare_subprocess_args = prepare_subprocess_args parser = runner.argparser