Parses per-tile benches and returns the sum as overall skp bench.
Review URL: https://codereview.appspot.com/6940071
git-svn-id: http://skia.googlecode.com/svn/trunk@6884 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/bench/bench_util.py b/bench/bench_util.py
index 82a16f0..39efda4 100644
--- a/bench/bench_util.py
+++ b/bench/bench_util.py
@@ -7,9 +7,15 @@
import re
import math
+# bench representation algorithm constant names
+ALGORITHM_AVERAGE = 'avg'
+ALGORITHM_MEDIAN = 'med'
+ALGORITHM_MINIMUM = 'min'
+ALGORITHM_25TH_PERCENTILE = '25th'
+
class BenchDataPoint:
"""A single data point produced by bench.
-
+
(str, str, str, float, {str:str})"""
def __init__(self, bench, config, time_type, time, settings):
self.bench = bench
@@ -17,7 +23,7 @@
self.time_type = time_type
self.time = time
self.settings = settings
-
+
def __repr__(self):
return "BenchDataPoint(%s, %s, %s, %s, %s)" % (
str(self.bench),
@@ -26,19 +32,19 @@
str(self.time),
str(self.settings),
)
-
+
class _ExtremeType(object):
"""Instances of this class compare greater or less than other objects."""
def __init__(self, cmpr, rep):
object.__init__(self)
self._cmpr = cmpr
self._rep = rep
-
+
def __cmp__(self, other):
if isinstance(other, self.__class__) and other._cmpr == self._cmpr:
return 0
return self._cmpr
-
+
def __repr__(self):
return self._rep
@@ -47,25 +53,24 @@
class _ListAlgorithm(object):
"""Algorithm for selecting the representation value from a given list.
- representation is one of 'avg', 'min', 'med', '25th' (average, minimum,
- median, 25th percentile)"""
+ representation is one of the ALGORITHM_XXX representation types."""
def __init__(self, data, representation=None):
if not representation:
- representation = 'avg' # default algorithm is average
+ representation = ALGORITHM_AVERAGE # default algorithm
self._data = data
self._len = len(data)
- if representation == 'avg':
+ if representation == ALGORITHM_AVERAGE:
self._rep = sum(self._data) / self._len
else:
self._data.sort()
- if representation == 'min':
+ if representation == ALGORITHM_MINIMUM:
self._rep = self._data[0]
else:
# for percentiles, we use the value below which x% of values are
# found, which allows for better detection of quantum behaviors.
- if representation == 'med':
+ if representation == ALGORITHM_MEDIAN:
x = int(round(0.5 * self._len + 0.5))
- elif representation == '25th':
+ elif representation == ALGORITHM_25TH_PERCENTILE:
x = int(round(0.25 * self._len + 0.5))
else:
raise Exception("invalid representation algorithm %s!" %
@@ -75,23 +80,51 @@
def compute(self):
return self._rep
-def parse(settings, lines, representation='avg'):
+def _ParseAndStoreTimes(config_re, time_re, line, bench, dic,
+ representation=None):
+ """Parses given bench time line with regex and adds data to the given dic.
+ config_re: regular expression for parsing the config line.
+ time_re: regular expression for parsing bench time.
+ line: input string line to parse.
+ bench: name of bench for the time values.
+ dic: dictionary to store bench values. See bench_dic in parse() below.
+ representation: should match one of the ALGORITHM_XXX types."""
+
+ for config in re.finditer(config_re, line):
+ current_config = config.group(1)
+ if config_re.startswith(' tile_'): # per-tile bench, add name prefix
+ current_config = 'tile_' + current_config
+ times = config.group(2)
+ for new_time in re.finditer(time_re, times):
+ current_time_type = new_time.group(1)
+ iters = [float(i) for i in
+ new_time.group(2).strip().split(',')]
+ dic.setdefault(bench, {}).setdefault(current_config, {}).setdefault(
+ current_time_type, []).append(_ListAlgorithm(
+ iters, representation).compute())
+
+def parse(settings, lines, representation=None):
"""Parses bench output into a useful data structure.
-
+
({str:str}, __iter__ -> str) -> [BenchDataPoint]
- representation should match one of those defined in class _ListAlgorithm."""
-
+ representation is one of the ALGORITHM_XXX types."""
+
benches = []
current_bench = None
+ bench_dic = {} # [bench][config][time_type] -> [list of bench values]
setting_re = '([^\s=]+)(?:=(\S+))?'
settings_re = 'skia bench:((?:\s+' + setting_re + ')*)'
bench_re = 'running bench (?:\[\d+ \d+\] )?\s*(\S+)'
time_re = '(?:(\w*)msecs = )?\s*((?:\d+\.\d+)(?:,\d+\.\d+)*)'
- config_re = '(\S+): ((?:' + time_re + '\s+)+)'
-
+ # non-per-tile benches have configs that don't end with ']'
+ config_re = '(\S+[^\]]): ((?:' + time_re + '\s+)+)'
+ # per-tile bench lines are in the following format
+ tile_re = (' tile_(\S+): tile \[\d+,\d+\] out of \[\d+,\d+\]: ((?:' +
+ time_re + '\s+)+)')
+
for line in lines:
-
- #see if this line is a settings line
+
+ # see if this line is a settings line
settingsMatch = re.search(settings_re, line)
if (settingsMatch):
settings = dict(settings)
@@ -100,40 +133,41 @@
settings[settingMatch.group(1)] = settingMatch.group(2)
else:
settings[settingMatch.group(1)] = True
-
- #see if this line starts a new bench
+
+ # see if this line starts a new bench
new_bench = re.search(bench_re, line)
if new_bench:
current_bench = new_bench.group(1)
-
- #add configs on this line to the current bench
+
+ # add configs on this line to the bench_dic
if current_bench:
- for new_config in re.finditer(config_re, line):
- current_config = new_config.group(1)
- times = new_config.group(2)
- for new_time in re.finditer(time_re, times):
- current_time_type = new_time.group(1)
- iters = [float(i) for i in
- new_time.group(2).strip().split(',')]
- benches.append(BenchDataPoint(
- current_bench
- , current_config
- , current_time_type
- , _ListAlgorithm(iters, representation).compute()
- , settings))
-
+ for regex in [config_re, tile_re]:
+ _ParseAndStoreTimes(regex, time_re, line, current_bench,
+ bench_dic, representation)
+
+ # append benches to list, use the total time as final bench value.
+ for bench in bench_dic:
+ for config in bench_dic[bench]:
+ for time_type in bench_dic[bench][config]:
+ benches.append(BenchDataPoint(
+ bench,
+ config,
+ time_type,
+ sum(bench_dic[bench][config][time_type]),
+ settings))
+
return benches
-
+
class LinearRegression:
"""Linear regression data based on a set of data points.
-
+
([(Number,Number)])
There must be at least two points for this to make sense."""
def __init__(self, points):
n = len(points)
max_x = Min
min_x = Max
-
+
Sx = 0.0
Sy = 0.0
Sxx = 0.0
@@ -144,20 +178,20 @@
y = point[1]
max_x = max(max_x, x)
min_x = min(min_x, x)
-
+
Sx += x
Sy += y
Sxx += x*x
Sxy += x*y
Syy += y*y
-
+
denom = n*Sxx - Sx*Sx
if (denom != 0.0):
B = (n*Sxy - Sx*Sy) / denom
else:
B = 0.0
a = (1.0/n)*(Sy - B*Sx)
-
+
se2 = 0
sB2 = 0
sa2 = 0
@@ -165,8 +199,8 @@
se2 = (1.0/(n*(n-2)) * (n*Syy - Sy*Sy - B*B*denom))
sB2 = (n*se2) / denom
sa2 = sB2 * (1.0/n) * Sxx
-
-
+
+
self.slope = B
self.intercept = a
self.serror = math.sqrt(max(0, se2))
@@ -174,7 +208,7 @@
self.serror_intercept = math.sqrt(max(0, sa2))
self.max_x = max_x
self.min_x = min_x
-
+
def __repr__(self):
return "LinearRegression(%s, %s, %s, %s, %s)" % (
str(self.slope),
@@ -183,7 +217,7 @@
str(self.serror_slope),
str(self.serror_intercept),
)
-
+
def find_min_slope(self):
"""Finds the minimal slope given one standard deviation."""
slope = self.slope
@@ -192,17 +226,17 @@
regr_start = self.min_x
regr_end = self.max_x
regr_width = regr_end - regr_start
-
+
if slope < 0:
lower_left_y = slope*regr_start + intercept - error
upper_right_y = slope*regr_end + intercept + error
return min(0, (upper_right_y - lower_left_y) / regr_width)
-
+
elif slope > 0:
upper_left_y = slope*regr_start + intercept + error
lower_right_y = slope*regr_end + intercept - error
return max(0, (lower_right_y - upper_left_y) / regr_width)
-
+
return 0
def CreateRevisionLink(revision_number):