Skip to content

Commit 2430bf8

Browse files
authored
Support reporting geometric mean by benchmark tags (#132)
This reports geometric mean organized by the tag(s) assigned to each benchmark. This will allow us to include benchmarks in the pyperformance suite that we don't necessarily want to include in "one big overall number" to represent progress. Addresses python/pyperformance#208
1 parent 968f247 commit 2430bf8

File tree

6 files changed

+3276
-18
lines changed

6 files changed

+3276
-18
lines changed

doc/api.rst

+2
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,7 @@ Benchmark:
676676
* ``inner_loops`` (``int >= 1``): number of inner-loops of the benchmark (``int``)
677677
* ``timer``: Implementation of ``time.perf_counter()``, and also resolution if
678678
available
679+
* ``tags``: (list of str, optional): A list of tags associated with the benchmark. If provided, the results output will be aggreggated by each tag.
679680

680681
Python metadata:
681682

@@ -831,6 +832,7 @@ Example of JSON, ``...`` is used in the example for readability::
831832
"loops": 8,
832833
"name": "telco",
833834
"perf_version": "0.8.2",
835+
"tags": ["numeric"],
834836
...
835837
},
836838
"version": "1.0"

pyperf/_compare.py

+39-18
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ def format_geometric_mean(norm_means):
4949
return format_normalized_mean(geo_mean)
5050

5151

52+
def get_tags_for_result(result):
53+
return result.ref.benchmark.get_metadata().get("tags", [])
54+
55+
5256
class CompareResult(object):
5357
def __init__(self, ref, changed, min_speed=None):
5458
# CompareData object
@@ -242,6 +246,12 @@ def __init__(self, benchmarks, args):
242246

243247
self.show_name = (len(grouped_by_name) > 1)
244248

249+
self.tags = set()
250+
for results in self.all_results:
251+
for result in results:
252+
self.tags.update(get_tags_for_result(result))
253+
self.tags = sorted(list(self.tags))
254+
245255
def compare_benchmarks(self, name, benchmarks):
246256
min_speed = self.min_speed
247257

@@ -258,11 +268,11 @@ def compare_benchmarks(self, name, benchmarks):
258268
return results
259269

260270
@staticmethod
261-
def display_not_signiticant(not_significant):
271+
def display_not_significant(not_significant):
262272
print("Benchmark hidden because not significant (%s): %s"
263273
% (len(not_significant), ', '.join(not_significant)))
264274

265-
def compare_suites_table(self):
275+
def compare_suites_table(self, all_results):
266276
if self.group_by_speed:
267277
def sort_key(results):
268278
result = results[0]
@@ -280,7 +290,7 @@ def sort_key(results):
280290

281291
rows = []
282292
not_significant = []
283-
for results in self.all_results:
293+
for results in all_results:
284294
row = [results.name]
285295

286296
ref_bench = results[0].ref.benchmark
@@ -324,14 +334,14 @@ def sort_key(results):
324334
if not_significant:
325335
if rows:
326336
print()
327-
self.display_not_signiticant(not_significant)
337+
self.display_not_significant(not_significant)
328338

329-
def compare_suites_by_speed(self):
339+
def compare_suites_by_speed(self, all_results):
330340
not_significant = []
331341
slower = []
332342
faster = []
333343
same = []
334-
for results in self.all_results:
344+
for results in all_results:
335345
result = results[0]
336346
if not result.significant:
337347
not_significant.append(results.name)
@@ -372,14 +382,14 @@ def sort_key(item):
372382
if not self.quiet and not_significant:
373383
if empty_line:
374384
print()
375-
self.display_not_signiticant(not_significant)
385+
self.display_not_significant(not_significant)
376386

377-
def compare_suites_list(self):
387+
def compare_suites_list(self, all_results):
378388
not_significant = []
379389
empty_line = False
380390
last_index = (len(self.all_results) - 1)
381391

382-
for index, results in enumerate(self.all_results):
392+
for index, results in enumerate(all_results):
383393
significant = any(result.significant for result in results)
384394
lines = []
385395
for result in results:
@@ -406,7 +416,7 @@ def compare_suites_list(self):
406416
if not self.quiet and not_significant:
407417
if empty_line:
408418
print()
409-
self.display_not_signiticant(not_significant)
419+
self.display_not_significant(not_significant)
410420

411421
def list_ignored(self):
412422
for suite, hidden in self.benchmarks.group_by_name_ignored():
@@ -416,9 +426,7 @@ def list_ignored(self):
416426
print("Ignored benchmarks (%s) of %s: %s"
417427
% (len(hidden), suite.filename, ', '.join(sorted(hidden_names))))
418428

419-
def compare_geometric_mean(self):
420-
all_results = self.all_results
421-
429+
def compare_geometric_mean(self, all_results):
422430
# use a list since two filenames can be identical,
423431
# even if results are different
424432
all_norm_means = []
@@ -443,16 +451,29 @@ def compare_geometric_mean(self):
443451
geo_mean = format_geometric_mean(all_norm_means[0][1])
444452
print(f'Geometric mean: {geo_mean}')
445453

446-
def compare(self):
454+
def compare_suites(self, results):
447455
if self.table:
448-
self.compare_suites_table()
456+
self.compare_suites_table(results)
449457
else:
450458
if self.group_by_speed:
451-
self.compare_suites_by_speed()
459+
self.compare_suites_by_speed(results)
452460
else:
453-
self.compare_suites_list()
461+
self.compare_suites_list(results)
454462

455-
self.compare_geometric_mean()
463+
self.compare_geometric_mean(results)
464+
465+
def compare(self):
466+
if len(self.tags):
467+
for tag in self.tags:
468+
display_title(f"Benchmarks with tag '{tag}':")
469+
all_results = [
470+
results for results in self.all_results
471+
if tag is None or tag in get_tags_for_result(results[0])
472+
]
473+
self.compare_suites(all_results)
474+
print()
475+
display_title(f"All benchmarks:")
476+
self.compare_suites(self.all_results)
456477

457478
if not self.quiet:
458479
self.list_ignored()

pyperf/_metadata.py

+8
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ def is_positive(value):
4242
return (value >= 0)
4343

4444

45+
def is_tags(value):
46+
if not isinstance(value, list):
47+
return False
48+
return all(isinstance(x, str) and x not in ('all', '') for x in value)
49+
50+
4551
def parse_load_avg(value):
4652
if isinstance(value, NUMBER_TYPES):
4753
return value
@@ -62,6 +68,7 @@ def format_noop(value):
6268
LOOPS = _MetadataInfo(format_number, (int,), is_strictly_positive, 'integer')
6369
WARMUPS = _MetadataInfo(format_number, (int,), is_positive, 'integer')
6470
SECONDS = _MetadataInfo(format_seconds, NUMBER_TYPES, is_positive, 'second')
71+
TAGS = _MetadataInfo(format_generic, (list,), is_tags, 'tag')
6572

6673
# Registry of metadata keys
6774
METADATA = {
@@ -84,6 +91,7 @@ def format_noop(value):
8491
'recalibrate_loops': LOOPS,
8592
'calibrate_warmups': WARMUPS,
8693
'recalibrate_warmups': WARMUPS,
94+
'tags': TAGS,
8795
}
8896

8997
DEFAULT_METADATA_INFO = _MetadataInfo(format_generic, METADATA_VALUE_TYPES, None, None)

0 commit comments

Comments
 (0)