diff --git a/GruntFile.js b/GruntFile.js index a591d1de..7c8878db 100644 --- a/GruntFile.js +++ b/GruntFile.js @@ -33,23 +33,41 @@ module.exports = function(grunt) { src: ['**/*.js'], dest: 'out/tmp/traceur/test/' }] + }, + 'translate-test-perf': { + options: { + experimental: true, + moduleNaming: { + stripPrefix: 'out/tmp/traceur' + } + }, + files: [{ + expand: true, + cwd: 'test_perf/', + src: ['**/*.js'], + dest: 'out/tmp/traceur/test_perf/' + }] } }, karma: { unit: { - configFile: 'karma.conf.js' + configFile: 'karma.test.conf.js' }, 'unit-chrome': { - configFile: 'karma.conf.js', + configFile: 'karma.test.conf.js', browsers: ['Chrome'] }, 'unit-firefox': { - configFile: 'karma.conf.js', + configFile: 'karma.test.conf.js', browsers: ['Firefox'] }, 'unit-travis': { - configFile: 'karma.conf.js', + configFile: 'karma.test.conf.js', browsers: ['Firefox'] + }, + 'perf-chrome': { + configFile: 'karma.test_perf.conf.js', + browsers: ['Chrome'] } }, concat: { @@ -76,6 +94,19 @@ module.exports = function(grunt) { 'out/tmp/traceur/bootstrap_post_test/**/*.js' ], dest: 'out/test.js' + }, + 'concat-traceur-test-perf': { + options: { + separator: ';' + }, + src: [ + 'out/tmp/traceur/bootstrap_pre_src/**/*.js', + 'out/tmp/traceur/bootstrap_pre_test/**/*.js', + 'out/tmp/traceur/src/**/*.js', + 'out/tmp/traceur/test_perf/**/*.js', + 'out/tmp/traceur/bootstrap_post_test/**/*.js' + ], + dest: 'out/test_perf.js' } }, uglify: { @@ -165,7 +196,16 @@ module.exports = function(grunt) { 'concat:concat-traceur-test', 'clean:clean-tmp' ]); + grunt.registerTask('build-test-perf', [ + 'clean:clean-tmp', + 'traceur:translate-src', + 'traceur:translate-test-perf', + 'bootstrap-get-packages:test_perf/**/*.perf.js:out/tmp/traceur/bootstrap_post_test/run_tests.js', + 'concat:concat-traceur-test-perf', + 'clean:clean-tmp' + ]); + grunt.registerTask('test-perf-chrome', ['build-test-perf', 'karma:perf-chrome']); grunt.registerTask('test-chrome', ['build-test', 'karma:unit-chrome']); grunt.registerTask('test-firefox', ['build-test', 'karma:unit-firefox']); grunt.registerTask('test-travis', ['build-test', 'karma:unit-travis']); diff --git a/karma.conf.js b/karma.test.conf.js similarity index 100% rename from karma.conf.js rename to karma.test.conf.js diff --git a/karma.test_perf.conf.js b/karma.test_perf.conf.js new file mode 100644 index 00000000..5bd786a8 --- /dev/null +++ b/karma.test_perf.conf.js @@ -0,0 +1,17 @@ +module.exports = function(config) { + config.set({ + basePath: '', + frameworks: [], + files: ['out/test_perf.js'], + exclude: [], + preprocessors: {}, + reporters: ['dots'], + port: 19876, + colors: true, + browserNoActivityTimeout: 30000, + logLevel: config.LOG_INFO, + autoWatch: false, + browsers: ['Chrome', 'Firefox'], + singleRun: true + }); +}; diff --git a/src/circuit/Gate.js b/src/circuit/Gate.js index 78182e2f..219a461a 100644 --- a/src/circuit/Gate.js +++ b/src/circuit/Gate.js @@ -124,7 +124,7 @@ class Gate { * @param {!string} blurb * @returns {!Gate} */ - static fromKnownMatrix(symbol, matrix, name, blurb) { + static fromKnownMatrix(symbol, matrix, name='', blurb='') { if (!(matrix instanceof Matrix)) { throw new DetailedError("Bad matrix.", {symbol, matrix, name, blurb}); } diff --git a/src/gates/ReverseBitsGate.js b/src/gates/ReverseBitsGate.js index 5075ebf4..5ce7626d 100644 --- a/src/gates/ReverseBitsGate.js +++ b/src/gates/ReverseBitsGate.js @@ -29,7 +29,7 @@ let _generateReverseShaderForSize = span => span < 2 ? undefined : ketShaderPerm `, span); -let reverseShaders = Seq.range(Config.MAX_WIRE_COUNT).map(_generateReverseShaderForSize).toArray(); +let reverseShaders = Seq.range(Config.MAX_WIRE_COUNT + 1).map(_generateReverseShaderForSize).toArray(); /** * @param {!int} span diff --git a/test_perf/KarmaTestRunner.perf.js b/test_perf/KarmaTestRunner.perf.js new file mode 100644 index 00000000..a144c2c2 --- /dev/null +++ b/test_perf/KarmaTestRunner.perf.js @@ -0,0 +1,43 @@ +import {getKnownPerfTests} from "test_perf/TestPerfUtil.js"; + +let execIntoPromise = method => { + try { + return Promise.resolve(method()); + } catch (ex) { + return Promise.reject(ex); + } +}; + +let promiseRunPerfTest = ({name, method}) => { + let result = { + description: name, + suite: ['(Perf Tests)'], + success: false, + log: [], + time: undefined + }; + + let t0 = performance.now(); + return execIntoPromise(method).then( + passed => { + result.success = passed; + }, + ex => { + result.log.push(String(ex)); + if (ex.details !== undefined) { + result.log.push(ex.details); + } + if (ex.stack !== undefined) { + result.log.push(ex.stack); + } + }).then(() => { + result.time = performance.now() - t0; + __karma__.result(result); + }); +}; + +__karma__.start = () => { + let known = getKnownPerfTests(); + __karma__.info({ total: known.length }); + Promise.all(known.map(promiseRunPerfTest)).then(() => __karma__.complete()); +}; diff --git a/test_perf/TestPerfUtil.js b/test_perf/TestPerfUtil.js new file mode 100644 index 00000000..f1f07ae0 --- /dev/null +++ b/test_perf/TestPerfUtil.js @@ -0,0 +1,75 @@ +function nanos(nanoseconds) { + return { + duration_nanos: nanoseconds, + description: nanoseconds + " ns" + }; +} + +function micros(microseconds) { + return { + duration_nanos: microseconds * 1.0e3, + description: microseconds + " us" + }; +} + +function millis(milliseconds) { + return { + duration_nanos: milliseconds * 1.0e6, + description: milliseconds + " ms" + }; +} + +let _knownPerfTests = []; +function getKnownPerfTests() { + return _knownPerfTests; +} + +/** + * @param {!string} name + * @param {!{duration_nanos: !int, description: !string}} targetDuration + * @param {!function(*):*} method + * @param {undefined|*=undefined} arg + */ +function perfGoal(name, targetDuration, method, arg=undefined) { + //noinspection JSUnusedGlobalSymbols + _knownPerfTests.push({name, method: () => { + let dt = _measureDuration(method, arg, targetDuration.duration_nanos); + let p = dt.duration_nanos / targetDuration.duration_nanos; + let logger = (p > 1 ? console.warn : console.log); + logger(`${_proportionDesc(p)} of target (${_pad(targetDuration.description, 6)}) for ${name}`); + return dt.duration_nanos <= targetDuration.duration_nanos + }}); +} + +function _pad(obj, length) { + let v = `${obj}`; + return ' '.repeat(Math.max(0, length - v.length)) + v; +} + +function _proportionDesc(proportion) { + return `[${_proportionBar(proportion)}] ${_pad(Math.round(proportion*100), 2)}%`; +} + +function _proportionBar(proportion, length=20) { + if (proportion > 1) { + return '!'.repeat(length); + } + let n = Math.round(proportion * length); + return '#'.repeat(n) + ' '.repeat(length - n); +} + +function _measureDuration(method, arg, expected_nanos_hint) { + let ms = 1.0e6; + let repeats = expected_nanos_hint < 30 * ms ? 50 : 10; + // Dry run to get any one-time initialization done. + method(arg); + + let t0 = window.performance.now(); + for (let i = 0; i < repeats; i++) { + method(arg); + } + let t1 = window.performance.now(); + return {duration_nanos: (t1 - t0) / repeats * ms}; +} + +export {getKnownPerfTests, perfGoal, nanos, micros, millis} diff --git a/test_perf/circuit.perf.js b/test_perf/circuit.perf.js new file mode 100644 index 00000000..b5cd36a7 --- /dev/null +++ b/test_perf/circuit.perf.js @@ -0,0 +1,115 @@ +import {perfGoal, millis} from "test_perf/TestPerfUtil.js" +import {CircuitDefinition} from "src/circuit/CircuitDefinition.js" +import {CircuitStats} from "src/circuit/CircuitStats.js" +import {Gate} from "src/circuit/Gate.js" +import {Gates} from "src/gates/AllGates.js" +import {Matrix} from "src/math/Matrix.js" + +perfGoal( + "2-Qubit QFT gate with manual de-QFT", + millis(15), + circuit => CircuitStats.fromCircuitAtTime(circuit, 0), + CircuitDefinition.fromTextDiagram( + new Map([ + ['•', Gates.Controls.Control], + ['H', Gates.HalfTurns.H], + ['1', Gates.QuarterTurns.SqrtZForward], + ['-', undefined], + ['/', undefined], + ['Q', Gates.FourierTransformGates.InverseFourierTransformFamily.ofSize(2)] + ]), + `-Q-H-1--- + -/---•-H-`)); + +perfGoal( + "4-Qubit QFT gate with manual de-QFT", + millis(15), + circuit => CircuitStats.fromCircuitAtTime(circuit, 0), + CircuitDefinition.fromTextDiagram( + new Map([ + ['•', Gates.Controls.Control], + ['H', Gates.HalfTurns.H], + ['1', Gates.QuarterTurns.SqrtZForward], + ['2', Gates.OtherZ.Z4], + ['3', Gates.OtherZ.Z8], + ['-', undefined], + ['/', undefined], + ['Q', Gates.FourierTransformGates.InverseFourierTransformFamily.ofSize(4)] + ]), + `-Q-H-1---2---3--- + -/---•-H-1---2--- + -/-------•-H-1--- + -/-----------•-H-`)); + +perfGoal( + "8-Qubit QFT gate with manual de-QFT", + millis(25), + circuit => CircuitStats.fromCircuitAtTime(circuit, 0), + CircuitDefinition.fromTextDiagram( + new Map([ + ['•', Gates.Controls.Control], + ['H', Gates.HalfTurns.H], + ['1', Gates.QuarterTurns.SqrtZForward], + ['2', Gates.OtherZ.Z4], + ['3', Gates.OtherZ.Z8], + ['4', Gates.OtherZ.Z16], + ['5', Gates.OtherZ.Z32], + ['6', Gates.OtherZ.Z64], + ['7', Gates.OtherZ.Z128], + ['-', undefined], + ['/', undefined], + ['Q', Gates.FourierTransformGates.InverseFourierTransformFamily.ofSize(8)] + ]), + `-Q-H-1---2---3---4---5---6---7--- + -/---•-H-1---2---3---4---5---6--- + -/-------•-H-1---2---3---4---5--- + -/-----------•-H-1---2---3---4--- + -/---------------•-H-1---2---3--- + -/-------------------•-H-1---2--- + -/-----------------------•-H-1--- + -/---------------------------•-H-`)); + + +perfGoal( + "16-Qubit QFT gate with manual de-QFT", + millis(200), + circuit => CircuitStats.fromCircuitAtTime(circuit, 0), + CircuitDefinition.fromTextDiagram( + new Map([ + ['•', Gates.Controls.Control], + ['H', Gates.HalfTurns.H], + ['1', Gates.QuarterTurns.SqrtZForward], + ['2', Gates.OtherZ.Z4], + ['3', Gates.OtherZ.Z8], + ['4', Gates.OtherZ.Z16], + ['5', Gates.OtherZ.Z32], + ['6', Gates.OtherZ.Z64], + ['7', Gates.OtherZ.Z128], + ['8', Gate.fromKnownMatrix("8", Matrix.fromPauliRotation(0, 0, 1/(1<<9)))], + ['9', Gate.fromKnownMatrix("9", Matrix.fromPauliRotation(0, 0, 1/(1<<10)))], + ['A', Gate.fromKnownMatrix("A", Matrix.fromPauliRotation(0, 0, 1/(1<<11)))], + ['B', Gate.fromKnownMatrix("B", Matrix.fromPauliRotation(0, 0, 1/(1<<12)))], + ['C', Gate.fromKnownMatrix("C", Matrix.fromPauliRotation(0, 0, 1/(1<<13)))], + ['D', Gate.fromKnownMatrix("D", Matrix.fromPauliRotation(0, 0, 1/(1<<14)))], + ['E', Gate.fromKnownMatrix("E", Matrix.fromPauliRotation(0, 0, 1/(1<<15)))], + ['F', Gate.fromKnownMatrix("F", Matrix.fromPauliRotation(0, 0, 1/(1<<16)))], + ['-', undefined], + ['/', undefined], + ['Q', Gates.FourierTransformGates.InverseFourierTransformFamily.ofSize(16)] + ]), + `-Q-H-1---2---3---4---5---6---7---8---9---A---B---C---D---E---F--- + -/---•-H-1---2---3---4---5---6---7---8---9---A---B---C---D---E--- + -/-------•-H-1---2---3---4---5---6---7---8---9---A---B---C---D--- + -/-----------•-H-1---2---3---4---5---6---7---8---9---A---B---C--- + -/---------------•-H-1---2---3---4---5---6---7---8---9---A---B--- + -/-------------------•-H-1---2---3---4---5---6---7---8---9---A--- + -/-----------------------•-H-1---2---3---4---5---6---7---8---9--- + -/---------------------------•-H-1---2---3---4---5---6---7---8--- + -/-------------------------------•-H-1---2---3---4---5---6---7--- + -/-----------------------------------•-H-1---2---3---4---5---6--- + -/---------------------------------------•-H-1---2---3---4---5--- + -/-------------------------------------------•-H-1---2---3---4--- + -/-----------------------------------------------•-H-1---2---3--- + -/---------------------------------------------------•-H-1---2--- + -/-------------------------------------------------------•-H-1--- + -/-----------------------------------------------------------•-H-`));