From 7fbab7473c662a769621104b0cf0952804d9eb2b Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 23 Feb 2011 11:08:09 -0600 Subject: [PATCH] Add Javascript binary byte array test. Compares normal Javascript arrays with Canvas ImageData arrays and Typed Arrays (ArrayBuffers from WebGL). --- tests/arrays.html | 39 +++++ tests/arrays.js | 369 ++++++++++++++++++++++++++++++++++++++++++++ tests/json2graph.py | 199 ++++++++++++++++++++++++ tests/stats.js | 53 +++++++ 4 files changed, 660 insertions(+) create mode 100644 tests/arrays.html create mode 100644 tests/arrays.js create mode 100755 tests/json2graph.py create mode 100644 tests/stats.js diff --git a/tests/arrays.html b/tests/arrays.html new file mode 100644 index 0000000..45a21e4 --- /dev/null +++ b/tests/arrays.html @@ -0,0 +1,39 @@ + + + + Javascript Arrays Performance Test + + + + + + + + +

Javascript Arrays Performance Test

+ Iterations:   + Array Size: *1024  + +   + +

+ Results:
+ +
+ + + + + + diff --git a/tests/arrays.js b/tests/arrays.js new file mode 100644 index 0000000..ba7d303 --- /dev/null +++ b/tests/arrays.js @@ -0,0 +1,369 @@ +var ctx, i, j, randlist, + new_normal, new_imageData, new_arrayBuffer, + browser = Browser.browser + " " + + Browser.version + " on " + + Browser.OS, + do_imageData = false, + do_arrayBuffer = false, + conf = { + 'create_cnt' : 2000, + 'read_cnt' : 5000000, + 'write_cnt' : 5000000, + 'iterations' : 0, + 'order_l1' : [browser], + 'order_l2' : ['normal', + 'imageData', + 'arrayBuffer'], + 'order_l3' : ['create', + 'sequentialRead', + 'randomRead', + 'sequentialWrite'] + }, + stats = {}, + testFunc = {}, + iteration, arraySize; + +var newline = "\n"; +if (Util.Engine.trident) { + var newline = "
\n"; +} +function message(str) { + console.log(str); + cell = $D('messages'); + cell.innerHTML += str + newline; + cell.scrollTop = cell.scrollHeight; +} + +function vmessage(str) { + if (verbose) { + message(str); + } else { + console.log(str); + } +} + +new_normal = function() { + var arr = [], i; + for (i = 0; i < arraySize; i++) { + arr[i] = 0; + } + return arr; +} + +/* Will be overridden with real function */ +new_imageData = function() { + throw("imageData not supported"); +}; + +new_imageData_createImageData = function() { + var imageData = ctx.createImageData(1024/4, arraySize / 1024); + return imageData.data; +}; + +new_imageData_getImageData = function() { + var imageData = ctx.getImageData(0, 0, 1024/4, arraySize / 1024), + arr = imageData.data; + for (i = 0; i < arraySize; i++) { + arr[i] = 0; + } + return arr; +}; + +new_arrayBuffer = function() { + var arr = new ArrayBuffer(arraySize); + return new Uint8Array(arr); +} + +function init_randlist() { + randlist = []; + for (var i=0; i < arraySize; i++) { + randlist[i] = parseInt(Math.random() * 256, 10); + } +} +function copy_randlist(arr) { + for (var i=0; i < arraySize; i++) { + arr[i] = randlist[i]; + } +} + +function begin() { + var i, j; + conf.iterations = parseInt($D('iterations').value, 10); + arraySize = parseInt($D('arraySize').value, 10) * 1024; + + init_randlist(); + + // TODO: randomize test_list + + stats = {}; + for (i = 0; i < conf.order_l2.length; i++) { + stats[conf.order_l2[i]] = {}; + for (j = 0; j < conf.order_l3.length; j++) { + stats[conf.order_l2[i]][conf.order_l3[j]] = []; + } + } + + $D('startButton').value = "Running"; + $D('startButton').disabled = true; + + message("running " + conf.iterations + " test iterations"); + iteration = 1; + setTimeout(run_next_iteration, 250); +} + +function finish() { + var totalTime, arrayType, testType, times; + message("tests finished"); + + for (j = 0; j < conf.order_l3.length; j++) { + testType = conf.order_l3[j]; + message("Test '" + testType + "'"); + for (i = 0; i < conf.order_l2.length; i++) { + arrayType = conf.order_l2[i]; + message(" Array Type '" + arrayType); + times = stats[arrayType][testType]; + message(" Average : " + times.mean() + "ms" + + " (Total: " + times.sum() + "ms)"); + message(" Min/Max : " + times.min() + "ms/" + + times.max() + "ms"); + message(" StdDev : " + times.stdDev() + "ms"); + } + } + + vmessage("array_chart.py JSON data:"); + chart_data = {'conf' : conf, 'stats' : { } }; + chart_data.stats[browser] = stats; + chart_data.stats['next_browser'] = {}; + vmessage(JSON.stringify(chart_data, null, 2)); + + $D('startButton').disabled = false; + $D('startButton').value = "Run Tests"; +} + +function run_next_iteration() { + var arrayType, testType, deltaTime; + + for (i = 0; i < conf.order_l2.length; i++) { + arrayType = conf.order_l2[i]; + if (arrayType === 'imageData' && (!do_imageData)) { + continue; + } + if (arrayType === 'arrayBuffer' && (!do_arrayBuffer)) { + continue; + } + for (j = 0; j < conf.order_l3.length; j++) { + testType = conf.order_l3[j]; + + deltaTime = testFunc[arrayType + "_" + testType](); + + stats[arrayType][testType].push(deltaTime); + vmessage("test " + (arrayType + "_" + testType) + + " time: " + (deltaTime) + "ms"); + } + } + + message("finished test iteration " + iteration); + if (iteration >= conf.iterations) { + setTimeout(finish, 1); + return; + } + iteration++; + setTimeout(run_next_iteration, 1); +} + +/* + * Test functions + */ + +testFunc["normal_create"] = function() { + var cnt, arrNormal, startTime, endTime; + vmessage("create normal array " + conf.create_cnt + "x, initialized to 0"); + + startTime = (new Date()).getTime(); + for (cnt = 0; cnt < conf.create_cnt; cnt++) { + arrNormal = new_normal(); + } + endTime = (new Date()).getTime(); + + return endTime - startTime; +}; + +testFunc["imageData_create"] = function() { + var cnt, arrImage, startTime, endTime; + vmessage("create imageData array " + conf.create_cnt + "x, initialized to 0"); + + startTime = (new Date()).getTime(); + for (cnt = 0; cnt < conf.create_cnt; cnt++) { + arrImage = new_imageData(); + } + endTime = (new Date()).getTime(); + + if (arrImage[103] !== 0) { + message("Initialization failed, arrImage[103] is: " + arrImage[103]); + throw("Initialization failed, arrImage[103] is: " + arrImage[103]); + } + return endTime - startTime; +}; + +testFunc["arrayBuffer_create"] = function() { + var cnt, arrBuffer, startTime, endTime; + vmessage("create arrayBuffer array " + conf.create_cnt + "x, initialized to 0"); + + startTime = (new Date()).getTime(); + for (cnt = 0; cnt < conf.create_cnt; cnt++) { + arrBuffer = new_arrayBuffer(); + } + endTime = (new Date()).getTime(); + + if (arrBuffer[103] !== 0) { + message("Initialization failed, arrBuffer[103] is: " + arrBuffer[103]); + throw("Initialization failed, arrBuffer[103] is: " + arrBuffer[103]); + } + return endTime - startTime; +}; + +function test_sequentialRead(arr) { + var i, j, cnt, startTime, endTime; + /* Initialize the array */ + copy_randlist(arr); + + startTime = (new Date()).getTime(); + i = 0; + j = 0; + for (cnt = 0; cnt < conf.read_cnt; cnt++) { + j = arr[i]; + i++; + if (i >= arraySize) { + i = 0; + } + } + endTime = (new Date()).getTime(); + + return endTime - startTime; +} + +function test_randomRead(arr) { + var i, cnt, startTime, endTime; + /* Initialize the array */ + copy_randlist(arr); // used as jumplist + + startTime = (new Date()).getTime(); + i = 0; + for (cnt = 0; cnt < conf.read_cnt; cnt++) { + i = (arr[i] + cnt) % arraySize; + } + endTime = (new Date()).getTime(); + + return endTime - startTime; +} + +function test_sequentialWrite(arr) { + var i, cnt, startTime, endTime; + /* Initialize the array */ + copy_randlist(arr); + + startTime = (new Date()).getTime(); + i = 0; + for (cnt = 0; cnt < conf.write_cnt; cnt++) { + arr[i] = (cnt % 256); + i++; + if (i >= arraySize) { + i = 0; + } + } + endTime = (new Date()).getTime(); + + return endTime - startTime; +} + +/* Sequential Read Tests */ +testFunc["normal_sequentialRead"] = function() { + vmessage("read normal array " + conf.read_cnt + "x"); + return test_sequentialRead(new_normal()); +}; + +testFunc["imageData_sequentialRead"] = function() { + vmessage("read imageData array " + conf.read_cnt + "x"); + return test_sequentialRead(new_imageData()); +}; + +testFunc["arrayBuffer_sequentialRead"] = function() { + vmessage("read arrayBuffer array " + conf.read_cnt + "x"); + return test_sequentialRead(new_arrayBuffer()); +}; + + +/* Random Read Tests */ +testFunc["normal_randomRead"] = function() { + vmessage("read normal array " + conf.read_cnt + "x"); + return test_randomRead(new_normal()); +}; + +testFunc["imageData_randomRead"] = function() { + vmessage("read imageData array " + conf.read_cnt + "x"); + return test_randomRead(new_imageData()); +}; + +testFunc["arrayBuffer_randomRead"] = function() { + vmessage("read arrayBuffer array " + conf.read_cnt + "x"); + return test_randomRead(new_arrayBuffer()); +}; + + +/* Sequential Write Tests */ +testFunc["normal_sequentialWrite"] = function() { + vmessage("write normal array " + conf.write_cnt + "x"); + return test_sequentialWrite(new_normal()); +}; + +testFunc["imageData_sequentialWrite"] = function() { + vmessage("write imageData array " + conf.write_cnt + "x"); + return test_sequentialWrite(new_imageData()); +}; + +testFunc["arrayBuffer_sequentialWrite"] = function() { + vmessage("write arrayBuffer array " + conf.write_cnt + "x"); + return test_sequentialWrite(new_arrayBuffer()); +}; + +init = function() { + vmessage(">> init"); + + $D('iterations').value = 10; + $D('arraySize').value = 10; + arraySize = parseInt($D('arraySize').value, 10) * 1024; + + message("Browser: " + browser); + + /* Determine browser binary array support */ + try { + ctx = $D('canvas').getContext('2d'); + new_imageData = new_imageData_createImageData; + new_imageData(); + do_imageData = true; + } catch (exc) { + vmessage("createImageData not supported: " + exc); + try { + ctx = $D('canvas').getContext('2d'); + new_imageData = new_imageData_getImageData; + blah = new_imageData(); + do_imageData = true; + } catch (exc) { + vmessage("getImageData not supported: " + exc); + } + } + if (! do_imageData) { + message("imageData arrays not supported"); + } + + try { + new_arrayBuffer(); + do_arrayBuffer = true; + } catch (exc) { + vmessage("Typed Arrays not supported: " + exc); + } + if (! do_arrayBuffer) { + message("Typed Arrays (ArrayBuffers) not suppoted"); + } + vmessage("<< init"); +} diff --git a/tests/json2graph.py b/tests/json2graph.py new file mode 100755 index 0000000..0e9d07b --- /dev/null +++ b/tests/json2graph.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python +# a bar plot with errorbars +import sys, json, pprint +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.font_manager import FontProperties + +def usage(): + print "%s json_file level1 level2 level3\n\n" % sys.argv[0] + print "Description:\n" + print "level1, level2, and level3 are one each of the following:\n"; + print " select=ITEM - select only ITEM at this level"; + print " bar - each item on this level becomes a graph bar"; + print " group - items on this level become groups of bars"; + print "\n"; + print "json_file is a file containing json data in the following format:\n" + print ' {'; + print ' "conf": {'; + print ' "order_l1": ['; + print ' "level1_label1",'; + print ' "level1_label2",'; + print ' ...'; + print ' ],'; + print ' "order_l2": ['; + print ' "level2_label1",'; + print ' "level2_label2",'; + print ' ...'; + print ' ],'; + print ' "order_l3": ['; + print ' "level3_label1",'; + print ' "level3_label2",'; + print ' ...'; + print ' ]'; + print ' },'; + print ' "stats": {'; + print ' "level1_label1": {'; + print ' "level2_label1": {'; + print ' "level3_label1": [val1, val2, val3],'; + print ' "level3_label2": [val1, val2, val3],'; + print ' ...'; + print ' },'; + print ' "level2_label2": {'; + print ' ...'; + print ' },'; + print ' },'; + print ' "level1_label2": {'; + print ' ...'; + print ' },'; + print ' ...'; + print ' },'; + print ' }'; + sys.exit(2) + +def error(msg): + print msg + sys.exit(1) + + +#colors = ['#ff0000', '#0863e9', '#00f200', '#ffa100', +# '#800000', '#805100', '#013075', '#007900'] +colors = ['#ff0000', '#00ff00', '#0000ff', + '#dddd00', '#dd00dd', '#00dddd', + '#dd6622', '#dd2266', '#66dd22', + '#8844dd', '#44dd88', '#4488dd'] + +if len(sys.argv) < 5: + usage() + +filename = sys.argv[1] +L1 = sys.argv[2] +L2 = sys.argv[3] +L3 = sys.argv[4] +if len(sys.argv) > 5: + legendHeight = float(sys.argv[5]) +else: + legendHeight = 0.75 + +# Load the JSON data from the file +data = json.loads(file(filename).read()) +conf = data['conf'] +stats = data['stats'] + +# Sanity check data hierarchy +if len(conf['order_l1']) != len(stats.keys()): + error("conf.order_l1 does not match stats level 1") +for l1 in stats.keys(): + if len(conf['order_l2']) != len(stats[l1].keys()): + error("conf.order_l2 does not match stats level 2 for %s" % l1) + if conf['order_l1'].count(l1) < 1: + error("%s not found in conf.order_l1" % l1) + for l2 in stats[l1].keys(): + if len(conf['order_l3']) != len(stats[l1][l2].keys()): + error("conf.order_l3 does not match stats level 3") + if conf['order_l2'].count(l2) < 1: + error("%s not found in conf.order_l2" % l2) + for l3 in stats[l1][l2].keys(): + if conf['order_l3'].count(l3) < 1: + error("%s not found in conf.order_l3" % l3) + +# +# Generate the data based on the level specifications +# +bar_labels = None +group_labels = None +bar_vals = [] +bar_sdvs = [] +if L3.startswith("select="): + select_label = l3 = L3.split("=")[1] + bar_labels = conf['order_l1'] + group_labels = conf['order_l2'] + bar_vals = [[0]*len(group_labels) for i in bar_labels] + bar_sdvs = [[0]*len(group_labels) for i in bar_labels] + for b in range(len(bar_labels)): + l1 = bar_labels[b] + for g in range(len(group_labels)): + l2 = group_labels[g] + bar_vals[b][g] = np.mean(stats[l1][l2][l3]) + bar_sdvs[b][g] = np.std(stats[l1][l2][l3]) +elif L2.startswith("select="): + select_label = l2 = L2.split("=")[1] + bar_labels = conf['order_l1'] + group_labels = conf['order_l3'] + bar_vals = [[0]*len(group_labels) for i in bar_labels] + bar_sdvs = [[0]*len(group_labels) for i in bar_labels] + for b in range(len(bar_labels)): + l1 = bar_labels[b] + for g in range(len(group_labels)): + l3 = group_labels[g] + bar_vals[b][g] = np.mean(stats[l1][l2][l3]) + bar_sdvs[b][g] = np.std(stats[l1][l2][l3]) +elif L1.startswith("select="): + select_label = l1 = L1.split("=")[1] + bar_labels = conf['order_l2'] + group_labels = conf['order_l3'] + bar_vals = [[0]*len(group_labels) for i in bar_labels] + bar_sdvs = [[0]*len(group_labels) for i in bar_labels] + for b in range(len(bar_labels)): + l2 = bar_labels[b] + for g in range(len(group_labels)): + l3 = group_labels[g] + bar_vals[b][g] = np.mean(stats[l1][l2][l3]) + bar_sdvs[b][g] = np.std(stats[l1][l2][l3]) +else: + usage() + +# If group is before bar then flip (zip) the data +if [L1, L2, L3].index("group") < [L1, L2, L3].index("bar"): + bar_labels, group_labels = group_labels, bar_labels + bar_vals = zip(*bar_vals) + bar_sdvs = zip(*bar_sdvs) + +print "bar_vals:", bar_vals + +# +# Now render the bar graph +# +ind = np.arange(len(group_labels)) # the x locations for the groups +width = 0.8 * (1.0/len(bar_labels)) # the width of the bars + +fig = plt.figure(figsize=(10,6), dpi=80) +plot = fig.add_subplot(1, 1, 1) + +rects = [] +for i in range(len(bar_vals)): + rects.append(plot.bar(ind+width*i, bar_vals[i], width, color=colors[i], + yerr=bar_sdvs[i], align='center')) + +# add some +plot.set_ylabel('Milliseconds (less is better)') +plot.set_title("Javascript array test: %s" % select_label) +plot.set_xticks(ind+width) +plot.set_xticklabels( group_labels ) + +fontP = FontProperties() +fontP.set_size('small') +plot.legend( [r[0] for r in rects], bar_labels, prop=fontP, + loc = 'center right', bbox_to_anchor = (1.0, legendHeight)) + +def autolabel(rects): + # attach some text labels + for rect in rects: + height = rect.get_height() + if np.isnan(height): + height = 0.0 + plot.text(rect.get_x()+rect.get_width()/2., height+20, '%d'%int(height), + ha='center', va='bottom', size='7') + +for rect in rects: + autolabel(rect) + +# Adjust axis sizes +axis = list(plot.axis()) +axis[0] = -width # Make sure left side has enough for bar +#axis[1] = axis[1] * 1.20 # Add 20% to the right to make sure it fits +axis[2] = 0 # Make y-axis start at 0 +axis[3] = axis[3] * 1.10 # Add 10% to the top +plot.axis(axis) + +plt.show() diff --git a/tests/stats.js b/tests/stats.js new file mode 100644 index 0000000..cd3011c --- /dev/null +++ b/tests/stats.js @@ -0,0 +1,53 @@ +/* + * Define some useful statistical functions on arrays of numbers + */ + +Array.prototype.sum = function() { + var i, sum = 0; + for (i = 0; i < this.length; i++) { + sum += this[i]; + } + return sum; +} + +Array.prototype.max = function() { + return Math.max.apply(null, this); +} + +Array.prototype.min = function() { + return Math.min.apply(null, this); +} + +Array.prototype.mean = function() { + return this.sum() / this.length; +} +Array.prototype.average = Array.prototype.mean; + +Array.prototype.median = function() { + var sorted = this.sort( function(a,b) { return a-b; }), + len = sorted.length; + if (len % 2) { + return sorted[Math.floor(len / 2)]; // Odd + } else { + return (sorted[len/2 - 1] + sorted[len/2]) / 2; // Even + } +} + +Array.prototype.stdDev = function(sample) { + var i, sumSqr = 0, mean = this.mean(), N; + + if (sample) { + // Population correction if this is a sample + N = this.length - 1; + } else { + // Standard deviation of just the array + N = this.length; + } + + for (i = 0; i < this.length; i++) { + sumSqr += Math.pow(this[i] - mean, 2); + } + + return Math.sqrt(sumSqr / N); +} +