aboutsummaryrefslogtreecommitdiff
path: root/afdo_redaction/remove_cold_functions.py
diff options
context:
space:
mode:
Diffstat (limited to 'afdo_redaction/remove_cold_functions.py')
-rwxr-xr-xafdo_redaction/remove_cold_functions.py293
1 files changed, 162 insertions, 131 deletions
diff --git a/afdo_redaction/remove_cold_functions.py b/afdo_redaction/remove_cold_functions.py
index 097085db..c6043bc0 100755
--- a/afdo_redaction/remove_cold_functions.py
+++ b/afdo_redaction/remove_cold_functions.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
-# Copyright 2020 The Chromium OS Authors. All rights reserved.
+# Copyright 2020 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
@@ -14,7 +14,7 @@ same sample count, we need to remove all of them in order to meet the
target, so the result profile will always have less than or equal to the
given number of functions.
-The script is intended to be used on production Chrome OS profiles, after
+The script is intended to be used on production ChromeOS profiles, after
other redaction/trimming scripts. It can be used with given textual CWP
and benchmark profiles, in order to analyze how many removed functions are
from which profile (or both), which can be used an indicator of fairness
@@ -24,160 +24,191 @@ This is part of the effort to stablize the impact of AFDO profile on
Chrome binary size. See crbug.com/1062014 for more context.
"""
-from __future__ import division, print_function
import argparse
import collections
import re
import sys
-_function_line_re = re.compile(r'^([\w\$\.@]+):(\d+)(?::\d+)?$')
+
+_function_line_re = re.compile(r"^([\w\$\.@]+):(\d+)(?::\d+)?$")
ProfileRecord = collections.namedtuple(
- 'ProfileRecord', ['function_count', 'function_body', 'function_name'])
+ "ProfileRecord", ["function_count", "function_body", "function_name"]
+)
def _read_sample_count(line):
- m = _function_line_re.match(line)
- assert m, 'Failed to interpret function line %s' % line
- return m.group(1), int(m.group(2))
+ m = _function_line_re.match(line)
+ assert m, "Failed to interpret function line %s" % line
+ return m.group(1), int(m.group(2))
def _read_textual_afdo_profile(stream):
- """Parses an AFDO profile from a line stream into ProfileRecords."""
- # ProfileRecords are actually nested, due to inlining. For the purpose of
- # this script, that doesn't matter.
- lines = (line.rstrip() for line in stream)
- function_line = None
- samples = []
- ret = []
- for line in lines:
- if not line:
- continue
-
- if line[0].isspace():
- assert function_line is not None, 'sample exists outside of a function?'
- samples.append(line)
- continue
-
- if function_line is not None:
- name, count = _read_sample_count(function_line)
- body = [function_line] + samples
- ret.append(
- ProfileRecord(
- function_count=count, function_body=body, function_name=name))
- function_line = line
+ """Parses an AFDO profile from a line stream into ProfileRecords."""
+ # ProfileRecords are actually nested, due to inlining. For the purpose of
+ # this script, that doesn't matter.
+ lines = (line.rstrip() for line in stream)
+ function_line = None
samples = []
+ ret = []
+ for line in lines:
+ if not line:
+ continue
+
+ if line[0].isspace():
+ assert (
+ function_line is not None
+ ), "sample exists outside of a function?"
+ samples.append(line)
+ continue
+
+ if function_line is not None:
+ name, count = _read_sample_count(function_line)
+ body = [function_line] + samples
+ ret.append(
+ ProfileRecord(
+ function_count=count, function_body=body, function_name=name
+ )
+ )
+ function_line = line
+ samples = []
- if function_line is not None:
- name, count = _read_sample_count(function_line)
- body = [function_line] + samples
- ret.append(
- ProfileRecord(
- function_count=count, function_body=body, function_name=name))
- return ret
+ if function_line is not None:
+ name, count = _read_sample_count(function_line)
+ body = [function_line] + samples
+ ret.append(
+ ProfileRecord(
+ function_count=count, function_body=body, function_name=name
+ )
+ )
+ return ret
def write_textual_afdo_profile(stream, records):
- for r in records:
- print('\n'.join(r.function_body), file=stream)
+ for r in records:
+ print("\n".join(r.function_body), file=stream)
def analyze_functions(records, cwp, benchmark):
- cwp_functions = {x.function_name for x in cwp}
- benchmark_functions = {x.function_name for x in benchmark}
- all_functions = {x.function_name for x in records}
- cwp_only_functions = len((all_functions & cwp_functions) -
- benchmark_functions)
- benchmark_only_functions = len((all_functions & benchmark_functions) -
- cwp_functions)
- common_functions = len(all_functions & benchmark_functions & cwp_functions)
- none_functions = len(all_functions - benchmark_functions - cwp_functions)
-
- assert not none_functions
- return cwp_only_functions, benchmark_only_functions, common_functions
+ cwp_functions = {x.function_name for x in cwp}
+ benchmark_functions = {x.function_name for x in benchmark}
+ all_functions = {x.function_name for x in records}
+ cwp_only_functions = len(
+ (all_functions & cwp_functions) - benchmark_functions
+ )
+ benchmark_only_functions = len(
+ (all_functions & benchmark_functions) - cwp_functions
+ )
+ common_functions = len(all_functions & benchmark_functions & cwp_functions)
+ none_functions = len(all_functions - benchmark_functions - cwp_functions)
+
+ assert not none_functions
+ return cwp_only_functions, benchmark_only_functions, common_functions
def run(input_stream, output_stream, goal, cwp=None, benchmark=None):
- records = _read_textual_afdo_profile(input_stream)
- num_functions = len(records)
- if not num_functions:
- return
- assert goal, "It's invalid to remove all functions in the profile"
-
- if cwp and benchmark:
- cwp_records = _read_textual_afdo_profile(cwp)
- benchmark_records = _read_textual_afdo_profile(benchmark)
- cwp_num, benchmark_num, common_num = analyze_functions(
- records, cwp_records, benchmark_records)
-
- records.sort(key=lambda x: (-x.function_count, x.function_name))
- records = records[:goal]
-
- print(
- 'Retained %d/%d (%.1f%%) functions in the profile' %
- (len(records), num_functions, 100.0 * len(records) / num_functions),
- file=sys.stderr)
- write_textual_afdo_profile(output_stream, records)
-
- if cwp and benchmark:
- cwp_num_after, benchmark_num_after, common_num_after = analyze_functions(
- records, cwp_records, benchmark_records)
- print(
- 'Retained %d/%d (%.1f%%) functions only appear in the CWP profile' %
- (cwp_num_after, cwp_num, 100.0 * cwp_num_after / cwp_num),
- file=sys.stderr)
- print(
- 'Retained %d/%d (%.1f%%) functions only appear in the benchmark profile'
- % (benchmark_num_after, benchmark_num,
- 100.0 * benchmark_num_after / benchmark_num),
- file=sys.stderr)
+ records = _read_textual_afdo_profile(input_stream)
+ num_functions = len(records)
+ if not num_functions:
+ return
+ assert goal, "It's invalid to remove all functions in the profile"
+
+ if cwp and benchmark:
+ cwp_records = _read_textual_afdo_profile(cwp)
+ benchmark_records = _read_textual_afdo_profile(benchmark)
+ cwp_num, benchmark_num, common_num = analyze_functions(
+ records, cwp_records, benchmark_records
+ )
+
+ records.sort(key=lambda x: (-x.function_count, x.function_name))
+ records = records[:goal]
+
print(
- 'Retained %d/%d (%.1f%%) functions appear in both CWP and benchmark'
- ' profiles' % (common_num_after, common_num,
- 100.0 * common_num_after / common_num),
- file=sys.stderr)
+ "Retained %d/%d (%.1f%%) functions in the profile"
+ % (len(records), num_functions, 100.0 * len(records) / num_functions),
+ file=sys.stderr,
+ )
+ write_textual_afdo_profile(output_stream, records)
+
+ if cwp and benchmark:
+ (
+ cwp_num_after,
+ benchmark_num_after,
+ common_num_after,
+ ) = analyze_functions(records, cwp_records, benchmark_records)
+ print(
+ "Retained %d/%d (%.1f%%) functions only appear in the CWP profile"
+ % (cwp_num_after, cwp_num, 100.0 * cwp_num_after / cwp_num),
+ file=sys.stderr,
+ )
+ print(
+ "Retained %d/%d (%.1f%%) functions only appear in the benchmark profile"
+ % (
+ benchmark_num_after,
+ benchmark_num,
+ 100.0 * benchmark_num_after / benchmark_num,
+ ),
+ file=sys.stderr,
+ )
+ print(
+ "Retained %d/%d (%.1f%%) functions appear in both CWP and benchmark"
+ " profiles"
+ % (
+ common_num_after,
+ common_num,
+ 100.0 * common_num_after / common_num,
+ ),
+ file=sys.stderr,
+ )
def main():
- parser = argparse.ArgumentParser(
- description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
- parser.add_argument(
- '--input',
- default='/dev/stdin',
- help='File to read from. Defaults to stdin.')
- parser.add_argument(
- '--output',
- default='/dev/stdout',
- help='File to write to. Defaults to stdout.')
- parser.add_argument(
- '--number',
- type=int,
- required=True,
- help='Number of functions to retain in the profile.')
- parser.add_argument(
- '--cwp', help='Textualized CWP profiles, used for further analysis')
- parser.add_argument(
- '--benchmark',
- help='Textualized benchmark profile, used for further analysis')
- args = parser.parse_args()
-
- if not args.number:
- parser.error("It's invalid to remove the number of functions to 0.")
-
- if (args.cwp and not args.benchmark) or (not args.cwp and args.benchmark):
- parser.error('Please specify both --cwp and --benchmark')
-
- with open(args.input) as stdin:
- with open(args.output, 'w') as stdout:
- # When user specify textualized cwp and benchmark profiles, perform
- # the analysis. Otherwise, just trim the cold functions from profile.
- if args.cwp and args.benchmark:
- with open(args.cwp) as cwp:
- with open(args.benchmark) as benchmark:
- run(stdin, stdout, args.number, cwp, benchmark)
- else:
- run(stdin, stdout, args.number)
-
-
-if __name__ == '__main__':
- main()
+ parser = argparse.ArgumentParser(
+ description=__doc__,
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ )
+ parser.add_argument(
+ "--input",
+ default="/dev/stdin",
+ help="File to read from. Defaults to stdin.",
+ )
+ parser.add_argument(
+ "--output",
+ default="/dev/stdout",
+ help="File to write to. Defaults to stdout.",
+ )
+ parser.add_argument(
+ "--number",
+ type=int,
+ required=True,
+ help="Number of functions to retain in the profile.",
+ )
+ parser.add_argument(
+ "--cwp", help="Textualized CWP profiles, used for further analysis"
+ )
+ parser.add_argument(
+ "--benchmark",
+ help="Textualized benchmark profile, used for further analysis",
+ )
+ args = parser.parse_args()
+
+ if not args.number:
+ parser.error("It's invalid to remove the number of functions to 0.")
+
+ if (args.cwp and not args.benchmark) or (not args.cwp and args.benchmark):
+ parser.error("Please specify both --cwp and --benchmark")
+
+ with open(args.input) as stdin:
+ with open(args.output, "w") as stdout:
+ # When user specify textualized cwp and benchmark profiles, perform
+ # the analysis. Otherwise, just trim the cold functions from profile.
+ if args.cwp and args.benchmark:
+ with open(args.cwp) as cwp:
+ with open(args.benchmark) as benchmark:
+ run(stdin, stdout, args.number, cwp, benchmark)
+ else:
+ run(stdin, stdout, args.number)
+
+
+if __name__ == "__main__":
+ main()