diff options
author | Alexandre Rames <alexandre.rames@linaro.org> | 2016-02-17 09:12:55 +0000 |
---|---|---|
committer | Alexandre Rames <alexandre.rames@linaro.org> | 2016-02-17 09:12:55 +0000 |
commit | c04e7c2075df1711cad820ffd4601c511d781b8f (patch) | |
tree | cb360f9037afc14b254114cb54850738161e9dc9 | |
parent | aa0012f9a104c2d87c77b0de8c4c6ffbfdd449f3 (diff) | |
download | art-testing-c04e7c2075df1711cad820ffd4601c511d781b8f.tar.gz |
Revert "Revert "Improve the JSON output of the compilation statistics tools.""
This reverts commit aa0012f9a104c2d87c77b0de8c4c6ffbfdd449f3.
Change-Id: I1170a0bd2e5eaadf2b8f3178c05d7755e54d7518
-rwxr-xr-x | compare.py | 54 | ||||
-rwxr-xr-x | run.py | 8 | ||||
-rwxr-xr-x | test/test.py | 2 | ||||
-rwxr-xr-x | tools/benchmarks/run.py | 4 | ||||
-rwxr-xr-x | tools/compilation_statistics/compare.py | 107 | ||||
-rwxr-xr-x | tools/compilation_statistics/run.py | 138 | ||||
-rw-r--r-- | tools/utils.py | 43 | ||||
-rw-r--r-- | tools/utils_stats.py | 15 |
8 files changed, 136 insertions, 235 deletions
@@ -17,23 +17,52 @@ import argparse import os import pickle -import sys + +from collections import OrderedDict from tools import utils from tools import utils_stats -sys.path.insert(0, os.path.join(utils.dir_tools, 'compilation_statistics')) - -from compare import PrintDiff - def BuildOptions(): parser = argparse.ArgumentParser( - description = "Compare two results of the associated `run.py` script.", + description = "Compare two result sets.", # Print default values. formatter_class = argparse.ArgumentDefaultsHelpFormatter) utils.AddCommonCompareOptions(parser) return parser.parse_args() +def PrintDiff(data_1, data_2, key=None, indentation=''): + indentation_level = ' ' + if data_1 or data_2: + if (isinstance(data_1, OrderedDict) or isinstance(data_1, dict) or data_1 is None) and \ + (isinstance(data_2, OrderedDict) or isinstance(data_2, dict) or data_2 is None): + if key is not None: + print(indentation + key) + entries = [] + list_1 = list(data_1.keys()) if data_1 else [] + list_2 = list(data_2.keys()) if data_2 else [] + for k in utils.MergeLists(list_1, list_2): + value_1 = data_1[k] if data_1 and k in data_1 else None + value_2 = data_2[k] if data_2 and k in data_2 else None + maybe_entry = PrintDiff(value_1, value_2, k, indentation + indentation_level) + if maybe_entry is not None: + entries.append(maybe_entry) + if entries: + utils_stats.PrintTable([''] + utils_stats.stats_diff_headers, + ['s'] + utils_stats.stats_diff_formats, + entries) + print('') + elif (isinstance(data_1, list) or data_1 is None) and \ + (isinstance(data_2, list) or data_2 is None): + list_1 = data_1 if data_1 else [0.0] + list_2 = data_2 if data_2 else [0.0] + m1, M1, ave1, d1, dp1 = utils_stats.ComputeStats(list_1) + m2, M2, ave2, d2, dp2 = utils_stats.ComputeStats(list_2) + return [indentation + key] + \ + [ave1, dp1, ave2, dp2, utils_stats.GetRelativeDiff(ave1, ave2)] + elif type(data_1) != type(data_2): + utils.Error('The data types differ between result sets.') + if __name__ == "__main__": args = BuildOptions() pkl_file_1 = open(args.res_1, 'rb') @@ -42,15 +71,4 @@ if __name__ == "__main__": res_2 = pickle.load(pkl_file_2) pkl_file_1.close() pkl_file_2.close() - results = sorted([result for result in res_1.keys() if result in res_2]) - - for r in results: - if r == 'benchmarks': - f = utils_stats.PrintDiff - elif r == 'compilation_statistics': - f = PrintDiff - else: - continue - - f(res_1[r], res_2[r]) - print('') + PrintDiff(res_1, res_2) @@ -18,6 +18,8 @@ import argparse import os import sys +from collections import OrderedDict + from tools import utils from tools import utils_stats from tools.benchmarks.run import GetBenchmarkStats @@ -44,11 +46,11 @@ if __name__ == "__main__": utils.Warning('Running this script is supported only on Linux.') args = BuildOptions() - result = dict() - result['benchmarks'] = GetBenchmarkStats(args) + result = OrderedDict([(utils.benchmarks_label, GetBenchmarkStats(args))]) if args.target: - result['compilation_statistics'] = GetCompilationStats(args) + result[utils.compilation_statistics_label] = GetCompilationStats(args) + utils.PrintResult(result) utils.OutputObject(result, 'pkl', args.output_pkl) utils.OutputObject(result, 'json', args.output_json) diff --git a/test/test.py b/test/test.py index 2a8a2a0..7ebbe11 100755 --- a/test/test.py +++ b/test/test.py @@ -153,7 +153,7 @@ def TestCompilationStatistics(target=utils.adb_default_target_string): args.append(os.path.join(utils.dir_build, "bench.apk")) run_py = os.path.join(utils.dir_root, "tools", "compilation_statistics", "run.py") - compare_py = os.path.join(utils.dir_root, "tools", "compilation_statistics", "compare.py") + compare_py = os.path.join(utils.dir_root, "compare.py") rc |= TestCommand(["./build.sh", "-t"], _cwd=utils.dir_root) rc |= TestCommand([run_py, "--output-pkl=/tmp/res1"] + args, _cwd=utils.dir_root) rc |= TestCommand([run_py, "--output-pkl=/tmp/res2"] + args, _cwd=utils.dir_root) diff --git a/tools/benchmarks/run.py b/tools/benchmarks/run.py index 59c1523..76c75b5 100755 --- a/tools/benchmarks/run.py +++ b/tools/benchmarks/run.py @@ -256,13 +256,13 @@ def GetBenchmarkStats(args): utils.Error("The benchmarks did *not* run successfully. (rc = %d)" % rc, rc) res = OrderedDict(sorted(result.items())) - utils_stats.PrintStats(res, iterations = args.iterations) - print('') return res if __name__ == "__main__": args = BuildOptions() result = GetBenchmarkStats(args) + utils_stats.PrintStats(result, iterations = args.iterations) + print('') utils.OutputObject(result, 'pkl', args.output_pkl) utils.OutputObject(result, 'json', args.output_json) # Output in CSV format. diff --git a/tools/compilation_statistics/compare.py b/tools/compilation_statistics/compare.py deleted file mode 100755 index 48f7c57..0000000 --- a/tools/compilation_statistics/compare.py +++ /dev/null @@ -1,107 +0,0 @@ -#! /usr/bin/env python3 - -# Copyright (C) 2015 Linaro Limited. All rights received. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import argparse -import os -import pickle -import sys - -dir_compilation_statistics = os.path.dirname(os.path.realpath(__file__)) -dir_tools = os.path.join(dir_compilation_statistics, '..') -sys.path.insert(0, dir_tools) - -import utils -import utils_stats -from run import CompileStats, MemoryUsage, OATSize, memory_stats_fields, time_stats_fields - -def BuildOptions(): - parser = argparse.ArgumentParser( - description = "Compare two results of the associated `run.py` script.", - # Print default values. - formatter_class = argparse.ArgumentDefaultsHelpFormatter) - utils.AddCommonCompareOptions(parser) - return parser.parse_args() - -def GetMemoryEntries(stats_1, stats_2): - res = [['Memory usage', '', '', '']] - - for i in range(len(memory_stats_fields)): - if stats_1.memory_usage[i].si_prefix == stats_2.memory_usage[i].si_prefix: - res.append([' ' + memory_stats_fields[i] + ' (' + stats_1.memory_usage[i].si_prefix + 'B)', - '%d' % stats_1.memory_usage[i].value, '%d' % stats_2.memory_usage[i].value, - '%.3f' % utils_stats.GetRelativeDiff(stats_1.memory_usage[i].value, - stats_2.memory_usage[i].value)]) - - return res - -def GetSizeEntries(stats_1, stats_2): - res = [['OAT size', '', '', ''], [' ' + 'Total (B)', - '%d' % stats_1.oat_size.total_size, - '%d' % stats_2.oat_size.total_size, - '%.3f' % utils_stats.GetRelativeDiff(stats_1.oat_size.total_size, - stats_2.oat_size.total_size)]] - - for s in sorted(set(stats_1.oat_size.section_sizes.keys()) | set(stats_2.oat_size.section_sizes.keys())): - value_1 = stats_1.oat_size.section_sizes[s] if s in stats_1.oat_size.section_sizes else 0 - value_2 = stats_2.oat_size.section_sizes[s] if s in stats_2.oat_size.section_sizes else 0 - res.append([' ' * 2 + s, '%d' % value_1, '%d' % value_2, - '%.3f' % utils_stats.GetRelativeDiff(value_1, value_2)]) - - return res - -def GetTimeEntries(stats_1, stats_2): - res = [['Compile time', '', '', '']] - time_stats_1 = list(utils_stats.ComputeStats(stats_1.compile_times)) - time_stats_2 = list(utils_stats.ComputeStats(stats_2.compile_times)) - si_factor, si_prefix = utils.PrettySIFactor(time_stats_1[0]) - - for i in range(len(time_stats_1)): - field = ' ' + time_stats_fields[i] - value_1 = time_stats_1[i] - value_2 = time_stats_2[i] - diff = '%.3f' % utils_stats.GetRelativeDiff(value_1, value_2) - - if i < len(time_stats_1) - 1: - field += ' (' + si_prefix + 's)' - value_1 = value_1 / si_factor - value_2 = value_2 / si_factor - - res.append([field, '%.3f' % value_1, '%.3f' % value_2, diff]) - - return res - -def PrintStatsDiff(apk, stats_1, stats_2): - utils_stats.PrintTable([apk, 'Value 1', 'Value 2', '(value 2 - value 1) / value 1 * 100'], - ['s', 's', 's', 's'], - GetTimeEntries(stats_1, stats_2) + GetMemoryEntries(stats_1, stats_2) + \ - GetSizeEntries(stats_1, stats_2)) - -def PrintDiff(stats_1, stats_2): - apk_list = [apk for apk in sorted(stats_1.keys()) if apk in stats_2] - - for apk in apk_list: - PrintStatsDiff(apk, stats_1[apk], stats_2[apk]) - print('') - -if __name__ == "__main__": - args = BuildOptions() - pkl_file_1 = open(args.res_1, 'rb') - pkl_file_2 = open(args.res_2, 'rb') - res_1 = pickle.load(pkl_file_1) - res_2 = pickle.load(pkl_file_2) - pkl_file_1.close() - pkl_file_2.close() - PrintDiff(res_1, res_2) diff --git a/tools/compilation_statistics/run.py b/tools/compilation_statistics/run.py index 3588e50..3ce7e4c 100755 --- a/tools/compilation_statistics/run.py +++ b/tools/compilation_statistics/run.py @@ -15,7 +15,6 @@ # limitations under the License. import argparse -import collections import glob import json import os @@ -27,6 +26,8 @@ import sys import tempfile import time +from collections import OrderedDict + dir_compilation_statistics = os.path.dirname(os.path.realpath(__file__)) dir_tools = os.path.join(dir_compilation_statistics, '..') sys.path.insert(0, dir_tools) @@ -35,23 +36,22 @@ import utils import utils_adb import utils_stats -CompileStats = collections.namedtuple('CompileStats', ['compile_times', 'memory_usage', 'oat_size']) -MemoryUsage = collections.namedtuple('MemoryUsage', ['value', 'si_prefix']) -OATSize = collections.namedtuple('OATSize', ['total_size', 'section_sizes']) -memory_stats_fields = ['Arena', 'Java', 'Native', 'Free native'] -sections = set(['.bss', '.rodata', '.text']) -time_stats_fields = ['Min', 'Max', 'Mean', 'Stdev', 'Stdev (% of mean)'] +memory_unit_prefixes = {'' : 1, 'G' : 2 ** 30, 'K' : 2 ** 10, 'M' : 2 ** 20} +sections = set(['.bss', '.rodata', '.text', 'Total']) def BuildOptions(): parser = argparse.ArgumentParser( - description = '''Collect statistics about the APK compilation process on a target adb - device: Compilation time, memory usage by the compiler (arena, Java, - and native allocations, and free native memory), and size of the - generated executable (total, .bss, .rodata, and .text section sizes).''', + description = '''Collect statistics about the APK compilation process on a target + adb device: Compilation time, memory usage by the compiler + (arena, Java, and native allocations, and free native memory), + and size of the generated executable (total, .bss, .rodata, and + .text section sizes).''', # Print default values. formatter_class = argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument('pathnames', nargs = '+', help='''Path containing APK files or a file - name for which compilation statistics should be collected.''') + parser.add_argument('pathnames', + nargs = '+', + help='''Path containing APK files or a file name for which + compilation statistics should be collected.''') utils.AddCommonRunOptions(parser) utils.AddOutputFormatOptions(parser, utils.default_output_formats) @@ -67,63 +67,24 @@ def BuildOptions(): return args -def GetMemoryEntries(stats): - res = [['Memory usage', '']] - - for i in range(len(memory_stats_fields)): - res.append([' ' + memory_stats_fields[i] + ' (' + stats.memory_usage[i].si_prefix + 'B)', - '%d' % stats.memory_usage[i].value]) - - return res - -def GetSizeEntries(stats): - res = [['OAT size', ''], [' ' + 'Total (B)', '%d' % stats.oat_size.total_size]] - - for s in sorted(stats.oat_size.section_sizes.keys()): - res.append([' ' * 2 + s, '%d' % stats.oat_size.section_sizes[s]]) - - return res - -def GetTimeEntries(stats): - res = [['Compile time', '']] - time_stats = list(utils_stats.ComputeStats(stats.compile_times)) - si_factor, si_prefix = utils.PrettySIFactor(time_stats[0]) - - for i in range(len(time_stats)): - field = ' ' + time_stats_fields[i] - value = time_stats[i] - - if i < len(time_stats_fields) - 1: - field += ' (' + si_prefix + 's)' - value = value / si_factor - - res.append([field, '%.3f' % value]) - - return res - -def PrintStatsTable(apk, stats): - utils_stats.PrintTable([apk, ''], ['s', 's'], GetTimeEntries(stats) + GetMemoryEntries(stats) + \ - GetSizeEntries(stats)) - def GetStats(apk, target, isa, compiler_mode, target_copy_path, iterations, - alloc_parser, - size_parser, work_dir): apk_path = os.path.join(target_copy_path, apk) oat = apk_path + '.oat' - # Only the output of the first command is necessary; execute in a subshell to guarantee PID value; - # only one thread is used for compilation to reduce measurement noise. + # Only the output of the first command is necessary; execute in a subshell + # to guarantee PID value; only one thread is used for compilation to reduce + # measurement noise. dex2oat_options = utils.GetDex2oatOptions(compiler_mode) command = '(echo $BASHPID && exec dex2oat -j1 ' + \ ' '.join(dex2oat_options) + \ ' --dex-file=' + apk_path + ' --oat-file=' + oat command += ' --instruction-set=' + isa + ') | head -n1' - compile_times = [] + compilation_times = [] for i in range(iterations): rc, out = utils_adb.shell(command, target) @@ -131,36 +92,30 @@ def GetStats(apk, stats_command = 'logcat -dsv process dex2oat | grep "^I([[:space:]]*' + \ out.rstrip() + ').*took" | tail -n1' rc, out = utils_adb.shell(stats_command, target) - alloc_stats = alloc_parser.match(out) + compile_time = re.match('.*?took (?P<value>.*?)(?P<unit>[mnu]{,1})s.*?\)', out) - if not alloc_stats: + if not compile_time: utils.Error('dex2oat failed; check adb logcat.') - compile_times.append(utils.GetTimeValue(float(alloc_stats.group(1)), alloc_stats.group(2))) - - # The rest of the statistics are deterministic, so there is no need to run several iterations; - # just get the values from the last run. - memory_usage = [] - - for i in range(4): - memory_usage.append(MemoryUsage(int(alloc_stats.group(2 * i + 3)), alloc_stats.group(2 * i + 4))) + value = float(compile_time.group('value')) * \ + utils.si_unit_prefixes[compile_time.group('unit')] + compilation_times.append(value) + # The rest of the statistics are deterministic, so there is no need to run several + # iterations; just get the values from the last run. + out = out[compile_time.end():] + memory_stats = OrderedDict((m[0], [int(m[1]) * memory_unit_prefixes[m[2]]]) for m + in re.findall(' (.*?)=([0-9]+)([GKM]{,1})B', out)) local_oat = os.path.join(utils.dir_root, work_dir, apk + '.oat') utils_adb.pull(oat, local_oat, target) command = ['size', '-A', '-d', local_oat] rc, outerr = utils.Command(command) - section_sizes = dict() - total_size = 0 - - for s in size_parser.findall(outerr): - value = int(s[1]) - - if s[0] in sections: - section_sizes[s[0]] = value - elif s[0] == 'Total': - total_size = value - - return [(apk, CompileStats(compile_times, memory_usage, OATSize(total_size, section_sizes)))] + section_sizes = OrderedDict((s[0], [int(s[1])]) for s + in re.findall('(\S+)\s+([0-9]+).*', outerr) + if s[0] in sections) + return OrderedDict([(utils.compilation_times_label, compilation_times), + (utils.memory_stats_label, memory_stats), + (utils.oat_size_label, section_sizes)]) def GetISA(target, mode): if not mode: @@ -194,12 +149,8 @@ def GetISA(target, mode): def GetCompilationStats(args): utils.CheckDependencies(['adb', 'size']) - alloc_parser = re.compile('.*?took (.*?)([mnu]{,1})s.*?=([0-9]+)([GKM]{,1})B' \ - '.*?=([0-9]+)([GKM]{,1})B.*?=([0-9]+)([GKM]{,1})B' \ - '.*?=([0-9]+)([GKM]{,1})B') - size_parser = re.compile('(\S+)\s+([0-9]+).*') isa = GetISA(args.target, args.mode) - res = dict() + res = OrderedDict() work_dir = tempfile.mkdtemp() apk_list = [] @@ -207,22 +158,18 @@ def GetCompilationStats(args): if os.path.isfile(pathname): apk_list.append(pathname) else: - apk_list[len(apk_list):] = [dentry for dentry in glob.glob(os.path.join(pathname, '*.apk')) - if os.path.isfile(dentry)] + dentries = [dentry for dentry in glob.glob(os.path.join(pathname, '*.apk')) + if os.path.isfile(dentry)] + apk_list[len(apk_list):] = dentries - for apk in apk_list: + for apk in sorted(apk_list): utils_adb.push(apk, args.target_copy_path, args.target) - res.update(GetStats(os.path.basename(apk), args.target, isa, - args.compiler_mode, args.target_copy_path, - args.iterations, alloc_parser, size_parser, work_dir)) + apk_name = os.path.basename(apk) + res[apk_name] = GetStats(apk_name, args.target, isa, + args.compiler_mode, args.target_copy_path, + args.iterations, work_dir) shutil.rmtree(work_dir) - apk_list = sorted(res) - - for apk in apk_list: - PrintStatsTable(apk, res[apk]) - print('') - return res if __name__ == "__main__": @@ -232,5 +179,6 @@ if __name__ == "__main__": args = BuildOptions() stats = GetCompilationStats(args) + utils.PrintResult(stats) utils.OutputObject(stats, 'pkl', args.output_pkl) utils.OutputObject(stats, 'json', args.output_json) diff --git a/tools/utils.py b/tools/utils.py index 39daa7e..f248ac2 100644 --- a/tools/utils.py +++ b/tools/utils.py @@ -23,8 +23,14 @@ import sys import time import traceback +from collections import OrderedDict dir_tools = os.path.dirname(os.path.realpath(__file__)) +sys.path.insert(0, dir_tools) + +import utils_stats + + dir_root = os.path.realpath(os.path.join(dir_tools, '..')) dir_benchmarks = os.path.join(dir_root, 'benchmarks') dir_out = os.path.join(dir_root, 'out') @@ -34,7 +40,12 @@ dir_framework = os.path.join(dir_root, 'framework') # Constant shared values that should not be modified. -si_factors = {'m' : 0.001, 'n' : 0.000000001, 'u' : 0.000001} +si_unit_prefixes = {'' : 1, 'G' : 10 ** 9, 'K' : 10 ** 3, 'M' : 10 ** 6, 'm' : 10 ** -3, 'n' : 10 ** -9, 'u' : 10 ** -6} +benchmarks_label = 'benchmarks' +compilation_statistics_label = 'compilation statistics' +compilation_times_label = 'compilation times' +memory_stats_label = 'memory statistics' +oat_size_label = 'oat size' adb_default_target_string = '<default>' adb_default_target_copy_path = '/data/local/tmp' default_mode = '' @@ -84,18 +95,24 @@ def ensure_dir(path): except: Error('Failed to ensure the directory `%s` exists.' % path) -def GetTimeValue(value, si_prefix): - return value * si_factors[si_prefix] if si_prefix else value +def MergeLists(x, y): + # To simplify things, assume that either y is the same as x, or it contains + # elements that are ordered after those in x. + # TODO: Use topological sorting or a more efficient algorithm. + return x + [e for e in y if e not in x] def PrettySIFactor(value): si_factor = float('inf') si_prefix = '' - for i in si_factors.items(): + for i in si_unit_prefixes.items(): if i[1] < si_factor and value < i[1] * 1000: si_factor = i[1] si_prefix = i[0] + if si_factor == float('inf'): + si_factor = 1 + return si_factor, si_prefix def NameMatchesAnyFilter(name, filters): @@ -226,3 +243,21 @@ def CheckDependencies(dependencies): if rc: Error("Couldn't find `" + d + "`.") + +def PrintResult(data, key=None, indentation=''): + indentation_level = ' ' + if isinstance(data, OrderedDict) or isinstance(data, dict): + if key is not None: + print(indentation + key) + entries = [] + for k in data: + maybe_entry = PrintResult(data[k], k, indentation + indentation_level) + if maybe_entry is not None: + entries.append(maybe_entry) + if entries: + utils_stats.PrintTable([''] + utils_stats.stats_headers, + ['s'] + utils_stats.stats_formats, + entries) + print('') + elif isinstance(data, list): + return [indentation + key] + list(utils_stats.ComputeStats(data)) diff --git a/tools/utils_stats.py b/tools/utils_stats.py index ae91c6c..a3aa168 100644 --- a/tools/utils_stats.py +++ b/tools/utils_stats.py @@ -18,6 +18,12 @@ import math from functools import reduce +stats_diff_headers = ['mean1', 'stdev1 (% of mean1)', 'mean2', + 'stdev2 (% of mean2)', '(mean2 - mean1) / mean1 * 100'] +stats_diff_formats = ['.3f', '.3f', '.3f', '.3f', '.3f'] +stats_headers = ['min', 'max', 'mean', 'stdev', 'stdev (% of mean)'] +stats_formats = ['.3f', '.3f', '.3f', '.3f', '.3f'] + def CalcGeomean(nums): assert len(nums) != 0 return math.exp((sum(map(lambda x: math.log(x), nums))) / len(nums)) @@ -51,7 +57,7 @@ def GetSuiteName(benchmark): return benchmark.split("/", 2)[1] def PrintStats(dict_results, iterations = None): - headers = ['', 'min', 'max', 'mean', 'stdev', 'stdev (% of mean)'] + headers = [''] + stats_headers results = [] stats_dict = {} @@ -67,7 +73,7 @@ def PrintStats(dict_results, iterations = None): results.append([benchmark] + list(data)) - PrintTable(headers, ['.3f'] * len(headers), results) + PrintTable(headers, ['.3f'] + stats_formats, results) # overall and per suite geomeans calculations print("\nGEOMEANS:") @@ -105,8 +111,7 @@ def PrintDiff(res_1, res_2, title = ''): # Only print results for benchmarks present in both sets of results. # Pay attention to maintain the order of the keys. benchmarks = [b for b in res_1.keys() if b in res_2.keys()] - headers = [title, 'mean1', 'stdev1 (% of mean1)', 'mean2', 'stdev2 (% of mean2)', - '(mean2 - mean1) / mean1 * 100'] + headers = [title] + stats_diff_headers results = [] stats_dict = {} # collecting data @@ -125,7 +130,7 @@ def PrintDiff(res_1, res_2, title = ''): diff = GetRelativeDiff(ave1, ave2) results.append([bench, ave1, dp1, ave2, dp2, diff]) - PrintTable(headers, ['.3f'] * len(headers), results) + PrintTable(headers, ['.3f'] + stats_diff_formats, results) # overall and per suite geomeans calculations print("\nGEOMEANS:") |