First off apologies for the epic post.
As per a previous post I have been writing a number of optimized utility functions that I can use for a bit of data wrangling.
In addition as part of the exercise I have also written a timing module, which currently logs results out to the console.
Loosely following other testing modules, my module is invoked like this
// Setup
const randomArray = Array.from({length: 1000000}, () => Math.floor(Math.random() * 100));
const sum = (a, b) => a + b;
//Tests
const reduceArrays = (
timeTests('reduce performance tests with an (Array) of one million random values')
.add(
'Vanilla JS reduce',
() => {
randomArray.reduce(sum);
}
)
.add(
'_.loDash reduce',
() => {
_.reduce(randomArray, sum);
}
)
.add(
'Custom reduce',
() => {
reduce(randomArray, sum);
}
)
);
reduceArrays.run(100); // 1000 iterations
It currently outputs a log like this.
Starting reduce performance tests with an (Array) of one million random values...
1. Vanilla JS reduce: 88.7 ops/sec (100 runs)
2. _.loDash reduce: 500.6 ops/sec (100 runs)
3. Custom reduce: 456.8 ops/sec (100 runs)
Results compared:
1st. _.loDash reduce: 464% faster
2nd. Custom reduce: 414.7% faster
3rd. Vanilla JS reduce: slowest
Note: 1., 2. and 3. are logged one after the other as each test completes and Results compared is outputted on completion of the tests,
I am amending my module to make it a little less tightly coupled. The console logging code was intertwined with my timing methods and instead I want to be able to pass in a custom logging class/instance.
The idea being that I could have a logger that outputs to the DOM instead of the console if I so wish.
This is the main issue I could do with input on:
With regards OOP, I really am making this up as I go along, so would appreciate some feedback. Pleass go easy on me, I know I donât deserve it
/**
* timeIt - A function that measures time taken to execute a function over a number of iterations.
* @param {Function} fn - The function to measure.
* @returns {Function} A wrapped function that returns the start time, stop time, and number of iterations.
*/
function timeIt(fn) {
// Updates the wrapper function with the passed functions name and toString method.
const wrapper = wraps(fn);
return wrapper (
/**
* wrapper
* @param {number} iterations - The number of times to run the function.
* @param {*} args - The arguments to pass to the function.
* @returns {object} The delta and iterations.
*/
function(iterations, ...args) {
const start = performance.now();
for (let i = 0; i < iterations; i++) fn(...args);
const stop = performance.now();
return { delta: stop - start, iterations };
}
);
}
/**
* @interface
* @property {Function} log - Logs a single time entry.
* @property {Function} logResult - Logs the results of all time entries.
*/
class ConsoleLogger {
log(time) {
console.log(time);
}
logResult(times) {
console.table(times);
}
}
/**
* @class
* @param {ConsoleLogger} logger - An instance of a logger class.
*/
class Times {
constructor(logger) {
this.times = [];
this.log = logger.log.bind(logger);
this.logResult = logger.logResult.bind(logger);
}
add(time) {
this.times.push(time);
this.log(time);
}
}
class TimeTest {
constructor(id, description, fn) {
this.id = id;
this.description = description;
this.fn = timeIt(fn);
}
run(iterations) {
return {
id: this.id,
description: this.description,
...this.fn(iterations)
};
}
}
class TimeTests {
#id = 1;
constructor(title, logger) {
this.title = title;
this.tests = new Set();
this.times = new Times(logger);
}
add(description, fn) {
this.tests.add(new TimeTest(this.#id++, description, fn));
return this;
}
remove(id) {
const test = find(this.tests, (test) => test.id === id);
if (test)
this.tests.delete(test);
return this;
}
run(iterations = 1000) {
for (const test of this.tests)
this.times.add(test.run(iterations));
this.logTimes();
}
clear() {
this.#id = 1;
this.tests.clear();
}
toString() {
return Serialize.toString(this.tests);
}
}
/**
* Creates a new instance of TimeTests with the given title and logger.
* @param {string} title - The title for the time tests.
* @param {ConsoleLogger} [logger] - The logger to use for logging the test results.
* Defaults to an instance of ConsoleLogger.
* @returns {TimeTests} A new instance of TimeTests.
*/
export default function timeTests(title, logger) {
if (!logger) {
logger = new ConsoleLogger();
}
return new TimeTests(title, logger);
I hope the code is clear enough to reason with, thanks.