diff options
Diffstat (limited to 'gnuradio-examples')
3 files changed, 398 insertions, 0 deletions
diff --git a/gnuradio-examples/python/volk_benchmark/volk_math.py b/gnuradio-examples/python/volk_benchmark/volk_math.py new file mode 100755 index 000000000..ec85ce0ad --- /dev/null +++ b/gnuradio-examples/python/volk_benchmark/volk_math.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python + +from gnuradio import gr +import argparse +from volk_test_funcs import * + +def multiply_const_cc(N): + k = 3.3 + op = gr.multiply_const_cc(k) + tb = helper(N, op, gr.sizeof_gr_complex, gr.sizeof_gr_complex, 1, 1) + return tb + +###################################################################### + +def multiply_const_ff(N): + k = 3.3 + op = gr.multiply_const_ff(k) + tb = helper(N, op, gr.sizeof_float, gr.sizeof_float, 1, 1) + return tb + +###################################################################### + +def multiply_cc(N): + op = gr.multiply_cc() + tb = helper(N, op, gr.sizeof_gr_complex, gr.sizeof_gr_complex, 2, 1) + return tb + +###################################################################### + +def multiply_ff(N): + op = gr.multiply_ff() + tb = helper(N, op, gr.sizeof_float, gr.sizeof_float, 2, 1) + return tb + +###################################################################### + +def add_ff(N): + op = gr.add_ff() + tb = helper(N, op, gr.sizeof_float, gr.sizeof_float, 2, 1) + return tb + +###################################################################### + +def conjugate_cc(N): + op = gr.conjugate_cc() + tb = helper(N, op, gr.sizeof_gr_complex, gr.sizeof_gr_complex, 1, 1) + return tb + +###################################################################### + +def multiply_conjugate_cc_volk(N): + op = gr.multiply_conjugate_cc() + tb = helper(N, op, gr.sizeof_gr_complex, gr.sizeof_gr_complex, 2, 1) + return tb + +def multiply_conjugate_cc_nonvolk(N): + class s(gr.hier_block2): + def __init__(self): + gr.hier_block2.__init__(self, "s", + gr.io_signature(2, 2, gr.sizeof_gr_complex), + gr.io_signature(1, 1, gr.sizeof_gr_complex)) + conj = gr.conjugate_cc() + mult = gr.multiply_cc() + self.connect((self,0), (mult,0)) + self.connect((self,1), conj, (mult,1)) + self.connect(mult, self) + + op = s() + tb = helper(N, op, gr.sizeof_gr_complex, gr.sizeof_gr_complex, 2, 1) + return tb + +#multiply_conjugate_cc = multiply_conjugate_cc_volk +multiply_conjugate_cc = multiply_conjugate_cc_nonvolk + +###################################################################### + +def run_tests(func, N, iters): + print("Running Test: {0}".format(func.__name__)) + tb = func(N) + t = timeit(tb, iters) + res = format_results(func.__name__, t) + return res + +def main(): + avail_tests = [multiply_const_cc, + multiply_const_ff, + multiply_cc, + multiply_ff, + add_ff, + conjugate_cc, + multiply_conjugate_cc] + + desc='Time an operation to compare with other implementations. \ + This program runs a simple GNU Radio flowgraph to test a \ + particular math function, mostly to compare the \ + Volk-optimized implementation versus a regular \ + implementation. The results are stored to an SQLite database \ + that can then be read by volk_plot.py to plot the differences.' + parser = argparse.ArgumentParser(description=desc) + parser.add_argument('label', type=str, + default=None, + help='Label of database table [default: %(default)s]') + parser.add_argument('-D', '--database', type=str, + default="volk_results.db", + help='Database file to store data in [default: %(default)s]') + parser.add_argument('-N', '--nitems', type=float, + default=1e9, + help='Number of items per iterations [default: %(default)s]') + parser.add_argument('-I', '--iterations', type=int, + default=20, + help='Number of iterations [default: %(default)s]') + parser.add_argument('--test', type=int, + choices=xrange(len(avail_tests)), + help='Test to run') + parser.add_argument('--list', action='store_true', + help='List the available tests') + parser.add_argument('--all', action='store_true', + help='Run all tests') + args = parser.parse_args() + + if(args.list): + print "Available Tests to Run:" + print "\n".join(["\t{0}: {1}".format(i,f.__name__) for i,f in enumerate(avail_tests)]) + sys.exit(0) + + N = int(args.nitems) + iters = args.iterations + label = args.label + + conn = create_connection(args.database) + new_table(conn, label) + + if not args.all: + func = avail_tests[args.test] + res = run_tests(func, N, iters) + replace_results(conn, label, N, iters, res) + else: + for f in avail_tests: + res = run_tests(f, N, iters) + replace_results(conn, label, N, iters, res) + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + pass diff --git a/gnuradio-examples/python/volk_benchmark/volk_plot.py b/gnuradio-examples/python/volk_benchmark/volk_plot.py new file mode 100755 index 000000000..665df5e14 --- /dev/null +++ b/gnuradio-examples/python/volk_benchmark/volk_plot.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python + +import sys +import argparse +from volk_test_funcs import * + +try: + import matplotlib + import matplotlib.pyplot as plt +except ImportError: + sys.stderr.write("Could not import Matplotlib (http://matplotlib.sourceforge.net/)\n") + sys.exit(1) + +def main(): + desc='Plot Volk performance results from a SQLite database. ' + \ + 'Run one of the volk tests first (e.g, volk_math.py)' + parser = argparse.ArgumentParser(description=desc) + parser.add_argument('-D', '--database', type=str, + default="volk_results.db", + help='Database file to read data from [default: %(default)s]') + args = parser.parse_args() + + # Set up global plotting properties + matplotlib.rcParams['figure.subplot.bottom'] = 0.2 + matplotlib.rcParams['figure.subplot.top'] = 0.95 + matplotlib.rcParams['ytick.labelsize'] = 16 + matplotlib.rcParams['xtick.labelsize'] = 16 + matplotlib.rcParams['legend.fontsize'] = 18 + + # Get list of tables to compare + conn = create_connection(args.database) + tables = list_tables(conn) + M = len(tables) + + # width of bars depends on number of comparisons + wdth = 0.80/M + + colors = ['b', 'r', 'g', 'm', 'k'] + + # Set up figure for plotting + f0 = plt.figure(0, facecolor='w', figsize=(14,10)) + s0 = f0.add_subplot(1,1,1) + + for i,table in enumerate(tables): + # Get results from the next table + res = get_results(conn, table[0]) + + xlabels = [] + averages = [] + variances = [] + maxes = [] + mins = [] + for r in res: + xlabels.append(r['kernel']) + averages.append(r['avg']) + variances.append(r['var']) + maxes.append(r['max']) + mins.append(r['min']) + + # makes x values for this data set placement + x0 = xrange(len(res)) + x1 = [x + i*wdth for x in x0] + + s0.bar(x1, averages, width=wdth, + #yerr=variances, + color=colors[i%M], label=table[0], + edgecolor='k', linewidth=2) + + s0.legend() + s0.set_ylabel("Processing time (sec) [{0:G} items]".format(res[0]['nitems']), + fontsize=22, fontweight='bold') + s0.set_xticks(x0) + s0.set_xticklabels(xlabels) + for label in s0.xaxis.get_ticklabels(): + label.set_rotation(45) + label.set_fontsize(16) + + plt.show() + +if __name__ == "__main__": + main() diff --git a/gnuradio-examples/python/volk_benchmark/volk_test_funcs.py b/gnuradio-examples/python/volk_benchmark/volk_test_funcs.py new file mode 100644 index 000000000..4f4e4afd3 --- /dev/null +++ b/gnuradio-examples/python/volk_benchmark/volk_test_funcs.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python + +from gnuradio import gr +import math, sys, os, time + +try: + import scipy +except ImportError: + sys.stderr.write("Unable to import Scipy (www.scipy.org)\n") + sys.exit(1) + +try: + import sqlite3 +except ImportError: + sys.stderr.write("Unable to import sqlite3: requires Python 2.5\n") + sys.exit(1) + +def execute(conn, cmd): + ''' + Executes the command cmd to the database opened in connection conn. + ''' + c = conn.cursor() + c.execute(cmd) + conn.commit() + c.close() + +def create_connection(database): + ''' + Returns a connection object to the SQLite database. + ''' + return sqlite3.connect(database) + +def new_table(conn, tablename): + ''' + Create a new table for results. + All results are in the form: [kernel | nitems | iters | avg. time | variance | max time | min time ] + Each table is meant as a different setting (e.g., volk_aligned, volk_unaligned, etc.) + ''' + cols = "kernel text, nitems int, iters int, avg real, var real, max real, min real" + cmd = "create table if not exists {0} ({1})".format( + tablename, cols) + execute(conn, cmd) + +def replace_results(conn, tablename, nitems, iters, res): + ''' + Inserts or replaces the results 'res' dictionary values into the table. + This deletes all old entries of the kernel in this table. + ''' + cmd = "DELETE FROM {0} where kernel='{1}'".format(tablename, res["kernel"]) + execute(conn, cmd) + insert_results(conn, tablename, nitems, iters, res) + +def insert_results(conn, tablename, nitems, iters, res): + ''' + Inserts the results dictionary values into the table. + ''' + cols = "kernel, nitems, iters, avg, var, max, min" + cmd = "INSERT INTO {0} ({1}) VALUES ('{2}', {3}, {4}, {5}, {6}, {7}, {8})".format( + tablename, cols, res["kernel"], nitems, iters, + res["avg"], res["var"], res["max"], res["min"]) + execute(conn, cmd) + +def list_tables(conn): + ''' + Returns a list of all tables in the database. + ''' + cmd = "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name" + c = conn.cursor() + c.execute(cmd) + t = c.fetchall() + c.close() + + return t + +def get_results(conn, tablename): + ''' + Gets all results in tablename. + ''' + cmd = "SELECT * FROM {0}".format(tablename) + c = conn.cursor() + c.execute(cmd) + fetched = c.fetchall() + c.close() + + res = list() + for f in fetched: + r = dict() + r['kernel'] = f[0] + r['nitems'] = f[1] + r['iters'] = f[2] + r['avg'] = f[3] + r['var'] = f[4] + r['min'] = f[5] + r['max'] = f[6] + res.append(r) + + return res + + +class helper(gr.top_block): + ''' + Helper function to run the tests. The parameters are: + N: number of items to process (int) + op: The GR block/hier_block to test + isizeof: the sizeof the input type + osizeof: the sizeof the output type + nsrcs: number of inputs to the op + nsnks: number of outputs of the op + + This function can only handle blocks where all inputs are the same + datatype and all outputs are the same data type + ''' + def __init__(self, N, op, + isizeof=gr.sizeof_gr_complex, + osizeof=gr.sizeof_gr_complex, + nsrcs=1, nsnks=1): + gr.top_block.__init__(self, "helper") + + self.op = op + self.srcs = [] + self.snks = [] + self.head = gr.head(isizeof, N) + + for n in xrange(nsrcs): + self.srcs.append(gr.null_source(isizeof)) + + for n in xrange(nsnks): + self.snks.append(gr.null_sink(osizeof)) + + self.connect(self.srcs[0], self.head, (self.op,0)) + + for n in xrange(1, nsrcs): + self.connect(self.srcs[n], (self.op,n)) + + for n in xrange(nsnks): + self.connect((self.op,n), self.snks[n]) + +def timeit(tb, iterations): + ''' + Given a top block, this function times it for a number of + iterations and stores the time in a list that is returned. + ''' + r = gr.enable_realtime_scheduling() + if r != gr.RT_OK: + print "Warning: failed to enable realtime scheduling" + + times = [] + for i in xrange(iterations): + start_time = time.time() + tb.run() + end_time = time.time() + tb.head.reset() + + times.append(end_time - start_time) + + return times + +def format_results(kernel, times): + ''' + Convinience function to convert the results of the timeit function + into a dictionary. + ''' + res = dict() + res["kernel"] = kernel + res["avg"] = scipy.mean(times) + res["var"] = scipy.var(times) + res["max"] = max(times) + res["min"] = min(times) + return res + + |