@riim/bench 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) Dmitry Vibe <riim@yandex.ru>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
File without changes
@@ -0,0 +1,75 @@
1
+ import { Bench } from '../src';
2
+
3
+ const arr = Array.from({ length: 1000 }, (_, idx) => idx + 1);
4
+ let item: number;
5
+
6
+ const bench = new Bench({ time: 1000 });
7
+
8
+ bench
9
+ .add('by index with l-var', () => {
10
+ for (let i = 0, l = arr.length; i < l; i++) {
11
+ item = arr[i];
12
+ }
13
+ })
14
+ .add('by index without l-var', () => {
15
+ for (let i = 0; i < arr.length; i++) {
16
+ item = arr[i];
17
+ }
18
+ })
19
+ .add('by index without l-var 2', () => {
20
+ for (let i = 0; i != arr.length; i++) {
21
+ item = arr[i];
22
+ }
23
+ })
24
+ .add('by index from end', () => {
25
+ for (let i = arr.length; i != 0; ) {
26
+ item = arr[--i];
27
+ }
28
+ })
29
+ .add('by index from end 2', () => {
30
+ for (let i = arr.length - 1; i >= 0; i--) {
31
+ item = arr[i];
32
+ }
33
+ })
34
+ .add('of operator', () => {
35
+ for (let item_ of arr) {
36
+ item = item_;
37
+ }
38
+ })
39
+ .add('Array#forEach()', () => {
40
+ arr.forEach((item_) => {
41
+ item = item_;
42
+ });
43
+ });
44
+
45
+ (async () => {
46
+ await bench.run();
47
+
48
+ // ┌──────────────────────────┬─────┬──────────┬──────────┐
49
+ // │ (index) │ pos │ mean │ rme │
50
+ // ├──────────────────────────┼─────┼──────────┼──────────┤
51
+ // │ by index with l-var │ 100 │ 0.001055 │ '±0.52%' │
52
+ // │ by index without l-var │ 100 │ 0.001057 │ '±1.22%' │
53
+ // │ Array#forEach() │ 87 │ 0.001217 │ '±1.74%' │
54
+ // │ by index without l-var 2 │ 72 │ 0.001458 │ '±0.4%' │
55
+ // │ by index from end 2 │ 72 │ 0.001461 │ '±0.3%' │
56
+ // │ of operator │ 68 │ 0.001544 │ '±0.37%' │
57
+ // │ by index from end │ 58 │ 0.001833 │ '±0.42%' │
58
+ // └──────────────────────────┴─────┴──────────┴──────────┘
59
+ console.table(bench.table());
60
+
61
+ await bench.run({ memoryUsage: true });
62
+
63
+ // ┌──────────────────────────┬─────┬─────┬──────┬──────┐
64
+ // │ (index) │ pos │ min │ max │ mean │
65
+ // ├──────────────────────────┼─────┼─────┼──────┼──────┤
66
+ // │ by index with l-var │ 100 │ 448 │ 600 │ 448 │
67
+ // │ by index without l-var │ 100 │ 448 │ 3880 │ 448 │
68
+ // │ by index without l-var 2 │ 100 │ 448 │ 3848 │ 448 │
69
+ // │ by index from end │ 100 │ 448 │ 3848 │ 448 │
70
+ // │ by index from end 2 │ 100 │ 448 │ 3848 │ 448 │
71
+ // │ of operator │ 100 │ 448 │ 4080 │ 448 │
72
+ // │ Array#forEach() │ 89 │ 504 │ 4032 │ 504 │
73
+ // └──────────────────────────┴─────┴─────┴──────┴──────┘
74
+ console.table(bench.memoryUsageTable());
75
+ })();
@@ -0,0 +1,63 @@
1
+ export interface ITaskFnParams {
2
+ timeStart: () => void;
3
+ timeEnd: () => void;
4
+ }
5
+ export type TTaskFn = (params: ITaskFnParams) => any;
6
+ export interface ITask {
7
+ name: string;
8
+ fn: TTaskFn;
9
+ completionTimeMeasurements: Array<number>;
10
+ memoryUsageMeasurements: Array<number>;
11
+ }
12
+ export type TReturnedValue = [taskName: string, returnedValue: any];
13
+ export type TCheckReturnedValues = (returnedValues: Array<TReturnedValue>) => void;
14
+ export declare class Bench {
15
+ readonly name: string | undefined;
16
+ readonly warmupTime: number;
17
+ readonly warmupIterations: number;
18
+ readonly time: number;
19
+ readonly iterations: number;
20
+ readonly memoryUsage: boolean;
21
+ readonly checkReturnedValues: TCheckReturnedValues | null;
22
+ protected _tasks: Map<string, ITask>;
23
+ onIterationStart: import("@riim/event").IUnparametrizedEvent<any>;
24
+ onIterationEnd: import("@riim/event").IUnparametrizedEvent<any>;
25
+ constructor({ name, warmupTime, warmupIterations, time, iterations, memoryUsage, checkReturnedValues }?: {
26
+ name?: string;
27
+ warmupTime?: number;
28
+ warmupIterations?: number;
29
+ time?: number;
30
+ iterations?: number;
31
+ memoryUsage?: boolean;
32
+ checkReturnedValues?: TCheckReturnedValues;
33
+ });
34
+ add(name: string, fn: TTaskFn): this;
35
+ remove(name: string): this;
36
+ run(options?: {
37
+ warmupTime?: number;
38
+ warmupIterations?: number;
39
+ time?: number;
40
+ iterations?: number;
41
+ memoryUsage?: boolean;
42
+ checkReturnedValues?: TCheckReturnedValues;
43
+ }): Promise<void>;
44
+ protected _formatMeasurements({ verbose, memoryUsage }?: {
45
+ verbose?: boolean;
46
+ memoryUsage?: boolean;
47
+ }): [taskName: string, taskMeasurements: {
48
+ hz?: number;
49
+ min?: number;
50
+ max?: number;
51
+ mean: number;
52
+ avg?: number;
53
+ p80?: number;
54
+ p95?: number;
55
+ p99?: number;
56
+ p999?: number;
57
+ rme?: number;
58
+ }][];
59
+ table({ verbose }?: {
60
+ verbose?: boolean;
61
+ }): Record<string, Record<string, any>>;
62
+ memoryUsageTable(): Record<string, Record<string, any>>;
63
+ }
package/dist/bench.js ADDED
@@ -0,0 +1,242 @@
1
+ import { event, fireEvent } from "@riim/event";
2
+ import jstat from "jstat";
3
+
4
+ //#region src/utils.ts
5
+ var noop = () => {};
6
+ function round(num, precision, direction = "round") {
7
+ if (precision === null) return num;
8
+ precision = Math.pow(10, precision);
9
+ num *= precision;
10
+ return (direction == "round" ? Math.round(num) : direction == "floor" ? Math.floor(num) : Math.ceil(num)) / precision;
11
+ }
12
+ function commasify(num, comma = ",") {
13
+ return num.toString().replace(/(\d)(?=(?:\d{3})+(\.\d*)?(?:\D|$))/g, `$1${comma}`);
14
+ }
15
+ function mean(sortedMeasurements) {
16
+ var half = sortedMeasurements.length / 2;
17
+ if (sortedMeasurements.length % 2 == 0) return (sortedMeasurements[half - 1] + sortedMeasurements[half]) / 2;
18
+ return sortedMeasurements[Math.trunc(half)];
19
+ }
20
+ function percentile(sortedMeasurements, pct) {
21
+ return sortedMeasurements[Math.round((sortedMeasurements.length - 1) * (pct / 100))];
22
+ }
23
+ function rme(measurements, meanValue) {
24
+ var n = measurements.length;
25
+ var tValue = jstat.studentt.inv(.975, n - 1);
26
+ var variance = measurements.map(val => Math.pow(val - meanValue, 2)).reduce((sum, val) => sum + val, 0) / (n - 1);
27
+ return tValue * (Math.sqrt(variance) / Math.sqrt(n)) / meanValue * 100;
28
+ }
29
+
30
+ //#endregion
31
+ //#region src/Bench.ts
32
+ var Bench = class {
33
+ constructor({
34
+ name,
35
+ warmupTime = Infinity,
36
+ warmupIterations = Infinity,
37
+ time = Infinity,
38
+ iterations = Infinity,
39
+ memoryUsage = false,
40
+ checkReturnedValues
41
+ } = {}) {
42
+ this._tasks = /* @__PURE__ */new Map();
43
+ this.onIterationStart = event();
44
+ this.onIterationEnd = event();
45
+ this.name = name;
46
+ this.warmupTime = warmupTime;
47
+ this.warmupIterations = warmupIterations;
48
+ this.time = time;
49
+ this.iterations = iterations;
50
+ this.memoryUsage = memoryUsage;
51
+ this.checkReturnedValues = checkReturnedValues ?? null;
52
+ }
53
+ add(name, fn) {
54
+ this._tasks.set(name, {
55
+ name,
56
+ fn,
57
+ completionTimeMeasurements: [],
58
+ memoryUsageMeasurements: []
59
+ });
60
+ return this;
61
+ }
62
+ remove(name) {
63
+ this._tasks.delete(name);
64
+ return this;
65
+ }
66
+ async run(options) {
67
+ var {
68
+ warmupTime = this.warmupTime,
69
+ warmupIterations = this.warmupIterations,
70
+ time = this.time,
71
+ iterations = this.iterations,
72
+ memoryUsage = this.memoryUsage,
73
+ checkReturnedValues = this.checkReturnedValues
74
+ } = options ?? this;
75
+ if (warmupTime < 1) throw RangeError("WarmupTime cannot be less than 1");
76
+ if (warmupIterations < 1) throw RangeError("WarmupIterations cannot be less than 1");
77
+ if (time == Infinity && iterations === Infinity) throw TypeError("Time or iterations is required");
78
+ if (time < 1) throw RangeError("Time cannot be less than 1");
79
+ if (iterations < 1) throw RangeError("Iterations cannot be less than 1");
80
+ var tasks = [...this._tasks.values()];
81
+ for (var task of tasks) {
82
+ task.completionTimeMeasurements.length = 0;
83
+ task.memoryUsageMeasurements.length = 0;
84
+ }
85
+ if (warmupTime != Infinity || warmupIterations != Infinity) {
86
+ var _taskFnParams = {
87
+ timeStart: noop,
88
+ timeEnd: noop
89
+ };
90
+ for (var i = 0, startTime = Date.now();; i++) {
91
+ fireEvent(this.onIterationStart);
92
+ for (var j = 0, k = tasks.length; j < k; j++) {
93
+ var returnedValue = tasks[j].fn(_taskFnParams);
94
+ if (returnedValue instanceof Promise) await returnedValue;
95
+ }
96
+ fireEvent(this.onIterationEnd);
97
+ if (Date.now() - startTime >= warmupTime || i >= warmupIterations) break;
98
+ }
99
+ }
100
+ var taskStartTime;
101
+ var taskEndTime;
102
+ var taskFnParams = {
103
+ timeStart: () => {
104
+ taskStartTime = performance.now();
105
+ },
106
+ timeEnd: () => {
107
+ taskEndTime = performance.now();
108
+ }
109
+ };
110
+ for (var _i = 0, _startTime = Date.now();; _i++) {
111
+ tasks.sort(() => Math.random() - .5);
112
+ var returnedValues = checkReturnedValues ? [] : null;
113
+ fireEvent(this.onIterationStart);
114
+ for (var _j = 0, m = tasks.length; _j < m; _j++) {
115
+ var {
116
+ name,
117
+ fn,
118
+ completionTimeMeasurements,
119
+ memoryUsageMeasurements
120
+ } = tasks[_j];
121
+ var heapUsed = void 0;
122
+ if (memoryUsage) {
123
+ gc();
124
+ ({
125
+ heapUsed
126
+ } = process.memoryUsage());
127
+ }
128
+ taskStartTime = performance.now();
129
+ taskEndTime = void 0;
130
+ var _returnedValue = fn(taskFnParams);
131
+ if (_returnedValue instanceof Promise) _returnedValue = await _returnedValue;
132
+ taskEndTime ??= performance.now();
133
+ if (memoryUsage) {
134
+ memoryUsageMeasurements.push(process.memoryUsage().heapUsed - heapUsed);
135
+ gc();
136
+ }
137
+ completionTimeMeasurements.push(taskEndTime - taskStartTime);
138
+ returnedValues?.push([name, _returnedValue]);
139
+ }
140
+ checkReturnedValues?.(returnedValues);
141
+ fireEvent(this.onIterationEnd);
142
+ if (Date.now() - _startTime >= time || _i >= iterations) break;
143
+ }
144
+ }
145
+ _formatMeasurements({
146
+ verbose = false,
147
+ memoryUsage = false
148
+ } = {}) {
149
+ return [...this._tasks.values()].reduce((measurements, task) => {
150
+ var taskMeasurements = memoryUsage ? task.memoryUsageMeasurements : task.completionTimeMeasurements;
151
+ var sortedTaskMeasurements = taskMeasurements.slice().sort((a, b) => a - b);
152
+ var sum = 0;
153
+ for (var i = 0, l = sortedTaskMeasurements.length; i < l; i++) sum += sortedTaskMeasurements[i];
154
+ var meanValue = mean(sortedTaskMeasurements);
155
+ measurements.push([task.name, memoryUsage ? {
156
+ min: sortedTaskMeasurements[0],
157
+ max: sortedTaskMeasurements.at(-1),
158
+ mean: meanValue
159
+ } : verbose ? {
160
+ hz: taskMeasurements.length / sum * 1e3,
161
+ min: sortedTaskMeasurements[0],
162
+ max: sortedTaskMeasurements.at(-1),
163
+ mean: meanValue,
164
+ avg: sum / taskMeasurements.length,
165
+ p80: percentile(sortedTaskMeasurements, 80),
166
+ p95: percentile(sortedTaskMeasurements, 95),
167
+ p99: percentile(sortedTaskMeasurements, 99),
168
+ p999: percentile(sortedTaskMeasurements, 99.9),
169
+ rme: rme(sortedTaskMeasurements, meanValue)
170
+ } : {
171
+ mean: meanValue,
172
+ rme: rme(sortedTaskMeasurements, meanValue)
173
+ }]);
174
+ return measurements;
175
+ }, []).sort(([, {
176
+ mean: a
177
+ }], [, {
178
+ mean: b
179
+ }]) => a - b);
180
+ }
181
+ table({
182
+ verbose = false
183
+ } = {}) {
184
+ var formattedMeasurements = this._formatMeasurements({
185
+ verbose
186
+ });
187
+ var fastestMean = formattedMeasurements[0][1].mean;
188
+ return formattedMeasurements.reduce((tabularData, [name, {
189
+ hz,
190
+ min,
191
+ max,
192
+ mean,
193
+ avg,
194
+ p80,
195
+ p95,
196
+ p99,
197
+ p999,
198
+ rme
199
+ }]) => {
200
+ tabularData[name] = {
201
+ pos: +(fastestMean / mean * 100).toFixed(0),
202
+ ...(verbose ? {
203
+ hz: commasify(Math.round(hz)),
204
+ min: round(min, 4),
205
+ max: round(max, 4),
206
+ mean: round(mean, 4),
207
+ avg: round(avg, 4),
208
+ p80: round(p80, 4),
209
+ p95: round(p95, 4),
210
+ p99: round(p99, 4),
211
+ p999: round(p999, 4)
212
+ } : {
213
+ mean: round(mean, 6)
214
+ }),
215
+ rme: `±${round(rme, 2)}%`
216
+ };
217
+ return tabularData;
218
+ }, {});
219
+ }
220
+ memoryUsageTable() {
221
+ var formattedMeasurements = this._formatMeasurements({
222
+ memoryUsage: true
223
+ });
224
+ var fastestMean = formattedMeasurements[0][1].mean;
225
+ return formattedMeasurements.reduce((tabularData, [name, {
226
+ min,
227
+ max,
228
+ mean
229
+ }]) => {
230
+ tabularData[name] = {
231
+ pos: +(fastestMean / mean * 100).toFixed(0),
232
+ min: round(min, 4),
233
+ max: round(max, 4),
234
+ mean: round(mean, 4)
235
+ };
236
+ return tabularData;
237
+ }, {});
238
+ }
239
+ };
240
+
241
+ //#endregion
242
+ export { Bench };
@@ -0,0 +1 @@
1
+ export { Bench } from './Bench';
@@ -0,0 +1,6 @@
1
+ export declare const noop: () => void;
2
+ export declare function round(num: number, precision: number | null, direction?: 'round' | 'floor' | 'ceil'): number;
3
+ export declare function commasify(num: number | string, comma?: string): string;
4
+ export declare function mean(sortedMeasurements: Array<number>): number;
5
+ export declare function percentile(sortedMeasurements: Array<number>, pct: number): number;
6
+ export declare function rme(measurements: Array<number>, meanValue: number): number;
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@riim/bench",
3
+ "version": "0.1.0",
4
+ "description": "",
5
+ "homepage": "https://github.com/Riim/bench#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/Riim/bench/issues"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/Riim/bench.git"
12
+ },
13
+ "license": "MIT",
14
+ "author": "Dmitry Vibe",
15
+ "type": "commonjs",
16
+ "main": "dist/bench.js",
17
+ "typings": "dist/index.d.ts",
18
+ "scripts": {
19
+ "build": "rm -rf dist && npx tsc && rolldown -c && babel dist --out-dir dist",
20
+ "test": "echo \"Error: no test specified\" && exit 1"
21
+ },
22
+ "devDependencies": {
23
+ "@babel/cli": "7.28.6",
24
+ "@babel/core": "7.29.0",
25
+ "@babel/plugin-transform-block-scoping": "7.28.6",
26
+ "@eslint/js": "9.39.2",
27
+ "@trivago/prettier-plugin-sort-imports": "6.0.2",
28
+ "@types/node": "25.2.3",
29
+ "@typescript-eslint/parser": "8.55.0",
30
+ "eslint": "9.39.2",
31
+ "eslint-plugin-github": "6.0.0",
32
+ "globals": "17.3.0",
33
+ "rolldown": "1.0.0-rc.4",
34
+ "typescript": "5.9.3",
35
+ "typescript-eslint": "8.55.0"
36
+ },
37
+ "dependencies": {
38
+ "@riim/event": "0.1.0",
39
+ "jstat": "1.9.6"
40
+ }
41
+ }