blob: 41498a1202f2f3b325f61fc104b3d91d1cf1c1ac [file] [log] [blame]
#!/usr/bin/python2.7
# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import itertools
import subprocess
import sys
import numpy
class Error(Exception):
"""Module error class."""
def LinearRegression(x, y):
"""Perform a linear regression using numpy.
@param x: Array of x-coordinates of the samples
@param y: Array of y-coordinates of the samples
@return: ((slope, intercept), r-squared)
"""
# p(x) = p[0]*x**1 + p[1]
p, (residual,) = numpy.polyfit(x, y, 1, full=True)[:2]
# Calculate the coefficient of determination (R-squared) from the
# "residual sum of squares"
# Reference:
# http://en.wikipedia.org/wiki/Coefficient_of_determination
r2 = 1 - (residual / (y.size*y.var()))
# Alternate calculation for R-squared:
#
# Calculate the coefficient of determination (R-squared) as the
# square of the sample correlation coefficient,
# which can be calculated from the variances and covariances.
# Reference:
# http://en.wikipedia.org/wiki/Correlation#Pearson.27s_product-moment_coefficient
#V = numpy.cov(x, y, ddof=0)
#r2 = (V[0,1]*V[1,0]) / (V[0,0]*V[1,1])
return p, r2
def GatherPerfStats(noploop, events, progress_func=lambda i, j: None):
"""Run perf stat with the given events and noploop program.
@param noploop: path to noploop binary. It should take one argument (number
of loop iterations) and produce no output.
@param events: value to pass to '-e' arg of perf stat.
@param progress_func: function that tracks progress of running the
benchmark. takes two arguments for the outer and inner iteration
numbers.
@returns: List of dicts.
"""
facts = []
for i, j in itertools.product(xrange(10), xrange(5)):
progress_func(i, j)
loops = (i+1) * 10000000 # (i+1) * 10 million
out = subprocess.check_output(
('perf', 'stat', '-x', ',',
'-e', events,
noploop, '%d' % loops),
stderr=subprocess.STDOUT)
unsupported_events = []
f = {'loops': loops}
for line in out.splitlines():
fields = line.split(',')
count, unit, event = None, None, None
if len(fields) == 2:
count, event = fields
elif len(fields) == 3:
count, unit, event = fields
else:
raise Error('Unable to parse perf stat output')
if count == '<not supported>':
unsupported_events.append(event)
else:
f[event] = int(count)
if unsupported_events:
raise Error('These events are not supported: %s'
% unsupported_events)
facts.append(f)
progress_func(-1, -1) # Finished
return facts
def FactsToNumpyArray(facts, dtype):
"""Convert "facts" (list of dicts) to a numpy array.
@param facts: A list of dicts. Each dict must have keys matching the field
names in dtype.
@param dtype: A numpy.dtype used to fill the array from facts. The dtype
must be a "structured array". ie:
numpy.dtype([('loops', numpy.int), ('cycles', numpy.int)])
@returns: A numpy.ndarray with dtype=dtype filled with facts.
"""
a = numpy.zeros(len(facts), dtype=dtype)
for i, f in enumerate(facts):
a[i] = tuple(f[n] for n in dtype.names)
return a
def main():
"""This can be run stand-alone."""
def _Progress(i, j):
if i == -1 and j == -1: # Finished
print
return
if j == 0:
if i != 0:
print
print i, ':',
print j,
sys.stdout.flush()
events = ('cycles', 'instructions')
facts = GatherPerfStats('src/noploop', ','.join(events),
progress_func=_Progress)
dt = numpy.dtype([('loops', numpy.int)] +
[(e, numpy.int) for e in events])
a = FactsToNumpyArray(facts, dt)
for y_var in events:
print y_var
(slope, intercept), r2 = LinearRegression(a['loops'], a[y_var])
print "slope:", slope
print "intercept:", intercept
print "r-squared:", r2
if __name__ == '__main__':
main()