Skip to content

Commit 09d0a98

Browse files
committed
chore: use benchmark package
1 parent 807ab3d commit 09d0a98

File tree

7 files changed

+243
-50
lines changed

7 files changed

+243
-50
lines changed

benchmark/benchmark.js

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,33 @@
11
import { SparseMatrix } from '../src/index.js';
22
import { Matrix } from 'ml-matrix';
33
import { SparseMatrix as SparseMatrixOld } from './class/SparseMatrixOld.js';
4+
// import { SparseMatrix as SparseMatrixOld } from '../src/Elements.js';
45
import fs from 'fs';
56

67
function randomSparseMatrix(rows, cols, density = 0.01) {
7-
const matrix = [];
8-
for (let i = 0; i < rows; i++) {
9-
const row = new Array(cols).fill(0);
10-
for (let j = 0; j < cols; j++) {
11-
if (Math.random() < density) {
12-
row[j] = Math.random() * 10;
13-
}
14-
}
15-
matrix.push(row);
8+
const total = rows * cols;
9+
const cardinality = Math.round(total * density);
10+
const positions = new Set();
11+
12+
// Generate unique random positions
13+
while (positions.size < cardinality) {
14+
positions.add(Math.floor(Math.random() * total));
1615
}
16+
17+
// Build the matrix with zeros
18+
const matrix = Array.from({ length: rows }, () => new Float64Array(cols));
19+
20+
// Assign random values to the selected positions
21+
for (const pos of positions) {
22+
const i = Math.floor(pos / cols);
23+
const j = pos % cols;
24+
matrix[i][j] = Math.random() * 10;
25+
}
26+
1727
return new SparseMatrix(matrix);
1828
}
1929

20-
function benchmark(fn, label, iterations = 5) {
30+
function benchmark(fn, label, iterations = 5, logIt = false) {
2131
const times = [];
2232
for (let i = 0; i < iterations; i++) {
2333
const t0 = performance.now();
@@ -26,7 +36,9 @@ function benchmark(fn, label, iterations = 5) {
2636
times.push(t1 - t0);
2737
}
2838
const avg = times.reduce((a, b) => a + b, 0) / times.length;
29-
console.log(`${label}: avg ${avg.toFixed(2)} ms over ${iterations} runs`);
39+
if (logIt) {
40+
console.log(`${label}: avg ${avg.toFixed(2)} ms over ${iterations} runs`);
41+
}
3042
return avg;
3143
}
3244

@@ -53,9 +65,9 @@ function printWinner(label1, avg1, label2, avg2) {
5365
}
5466

5567
function runBenchmarks() {
56-
const m = 128;
57-
const n = 128;
58-
const p = 128;
68+
const m = 256;
69+
const n = 256;
70+
const p = 256;
5971
const densityA = 0.01;
6072
const densityB = 0.01;
6173

@@ -137,8 +149,9 @@ function runBenchmarks() {
137149
}
138150

139151
function runSizeSweepBenchmark() {
140-
const sizes = [8, 16, 32, 64, 128];
141-
const densities = [0.01, 0.015, 0.02, 0.025, 0.03, 0.35];
152+
const nbIterations = 3;
153+
const sizes = [32, 64, 128, 256];
154+
const densities = [0.01, 0.015, 0.02, 0.025, 0.03];
142155
const results = [];
143156

144157
for (const densityA of densities) {
@@ -165,29 +178,33 @@ function runSizeSweepBenchmark() {
165178
A.mmul(B);
166179
},
167180
'mmulNew',
168-
3,
181+
nbIterations,
169182
);
170183

171184
const mmulAvg = benchmark(
172185
() => {
173186
AOld.mmul(BOld);
174187
},
175188
'mmul',
176-
3,
189+
nbIterations,
177190
);
178191

179-
const denseAvg = benchmark(() => {
180-
denseA.mmul(denseB), 'denseMatrix', 3;
181-
});
192+
const denseAvg = benchmark(
193+
() => {
194+
denseA.mmul(denseB);
195+
},
196+
'denseMatrix',
197+
nbIterations,
198+
);
182199

183200
results.push({
184201
densityA,
185202
densityB,
186203
A_shape: [m, n],
187204
B_shape: [n, p],
188205
dense: denseAvg,
189-
mmulNew: mmulNewAvg,
190-
mmul: mmulAvg,
206+
new: mmulNewAvg,
207+
old: mmulAvg,
191208
});
192209
}
193210
}
@@ -202,6 +219,6 @@ function runSizeSweepBenchmark() {
202219
console.log('Size sweep benchmark results saved to size_sweep_results.json');
203220
}
204221

205-
runBenchmarks();
222+
// runBenchmarks();
206223
// Uncomment to run the size sweep benchmark
207-
// runSizeSweepBenchmark();
224+
runSizeSweepBenchmark();

benchmark/squareMatrixBenchmark.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import Benchmark from 'benchmark';
2+
import { Matrix } from 'ml-matrix';
3+
import { SparseMatrix as SparseMatrixOld } from './class/SparseMatrixOld.js';
4+
import fs from 'fs';
5+
import { randomSparseMatrix } from './utils/randomSparseMatrix.js';
6+
7+
function runSizeSweepBenchmarkCompact() {
8+
const sizes = [32, 64, 128, 256];
9+
const densities = [0.01, 0.015, 0.02, 0.025, 0.03];
10+
const results = [];
11+
12+
for (const densityA of densities) {
13+
for (const m of sizes) {
14+
const A = randomSparseMatrix(m, m, densityA);
15+
const B = randomSparseMatrix(m, m, densityA);
16+
let denseA = A.to2DArray();
17+
let denseB = B.to2DArray();
18+
const AOld = new SparseMatrixOld(denseA);
19+
const BOld = new SparseMatrixOld(denseB);
20+
denseA = new Matrix(denseA);
21+
denseB = new Matrix(denseB);
22+
// Warm up
23+
A.mmul(B);
24+
// Use Benchmark.js for each method
25+
const suite = new Benchmark.Suite();
26+
let mmulNewAvg, mmulAvg, denseAvg;
27+
suite
28+
.add('mmulNew', function () {
29+
A.mmul(B);
30+
})
31+
.add('mmul', function () {
32+
AOld.mmul(BOld);
33+
})
34+
.add('denseMatrix', function () {
35+
denseA.mmul(denseB);
36+
})
37+
.on('cycle', function (event) {
38+
// Optionally log: console.log(String(event.target));
39+
})
40+
.on('complete', function () {
41+
this.forEach((bench) => {
42+
if (bench.name === 'mmulNew') mmulNewAvg = 1000 / bench.hz;
43+
if (bench.name === 'mmul') mmulAvg = 1000 / bench.hz;
44+
if (bench.name === 'denseMatrix') denseAvg = 1000 / bench.hz;
45+
});
46+
})
47+
.run({ async: false });
48+
results.push({
49+
density: densityA,
50+
size: m,
51+
dense: denseAvg,
52+
new: mmulNewAvg,
53+
old: mmulAvg,
54+
});
55+
}
56+
}
57+
fs.writeFileSync(
58+
'./benchmark/size_sweep_results-dense.json',
59+
JSON.stringify(results, null, 2),
60+
);
61+
console.log('Size sweep benchmark results saved to size_sweep_results.json');
62+
}
63+
64+
runSizeSweepBenchmarkCompact();

benchmark/utils/benchmark.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export function benchmark(fn, label, iterations = 5, logIt = true) {
2+
const times = [];
3+
for (let i = 0; i < iterations; i++) {
4+
const t0 = performance.now();
5+
fn();
6+
const t1 = performance.now();
7+
times.push(t1 - t0);
8+
}
9+
const avg = times.reduce((a, b) => a + b, 0) / times.length;
10+
if (logIt) {
11+
console.log(
12+
`${label}: avg ${avg.toFixed(2)} ms over ${iterations} runs \n`,
13+
);
14+
}
15+
return avg;
16+
}

benchmark/utils/printWinner.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export function printWinner(results) {
2+
// results: Array of { label: string, avg: number }
3+
if (!Array.isArray(results) || results.length < 2) {
4+
console.log('Need at least two results to compare.');
5+
return;
6+
}
7+
8+
// Sort by avg ascending (fastest first)
9+
const sorted = [...results].sort((a, b) => a.avg - b.avg);
10+
const winner = sorted[0];
11+
12+
console.log(
13+
` -> ${winner.label} was the fastest (${winner.avg.toFixed(2)} ms avg)\n`,
14+
);
15+
16+
for (let i = 1; i < sorted.length; i++) {
17+
const slower = sorted[i];
18+
const speedup = (slower.avg / winner.avg).toFixed(2);
19+
const percent = (((slower.avg - winner.avg) / slower.avg) * 100).toFixed(1);
20+
console.log(
21+
` ${winner.label} was ${speedup}x faster (${percent}% faster) than ${
22+
slower.label
23+
} (${slower.avg.toFixed(2)} ms avg)`,
24+
);
25+
}
26+
console.log();
27+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { SparseMatrix } from '../../src/index.js';
2+
3+
export function randomSparseMatrix(rows, cols, density = 0.01) {
4+
const total = rows * cols;
5+
const cardinality = Math.round(total * density);
6+
const positions = new Set();
7+
8+
// Generate unique random positions
9+
while (positions.size < cardinality) {
10+
positions.add(Math.floor(Math.random() * total));
11+
}
12+
13+
// Build the matrix with zeros
14+
const matrix = Array.from({ length: rows }, () => new Float64Array(cols));
15+
16+
// Assign random values to the selected positions
17+
for (const pos of positions) {
18+
const i = Math.floor(pos / cols);
19+
const j = pos % cols;
20+
matrix[i][j] = Math.random() * 10;
21+
}
22+
23+
return new SparseMatrix(matrix);
24+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import Benchmark from 'benchmark';
2+
import { Matrix } from 'ml-matrix';
3+
import { SparseMatrix as SparseMatrixOld } from './class/SparseMatrixOld.js';
4+
import fs from 'fs';
5+
import { randomSparseMatrix } from './utils/randomSparseMatrix.js';
6+
7+
function runSizeSweepBenchmark() {
8+
const sizes = [32, 64, 128, 256];
9+
const densities = [0.01, 0.015, 0.02, 0.025, 0.03];
10+
const results = [];
11+
12+
for (const densityA of densities) {
13+
for (const densityB of densities) {
14+
for (const m of sizes) {
15+
for (const n of sizes) {
16+
for (const p of sizes) {
17+
const A = randomSparseMatrix(m, n, densityA);
18+
const B = randomSparseMatrix(n, p, densityB);
19+
let denseA = A.to2DArray();
20+
let denseB = B.to2DArray();
21+
const AOld = new SparseMatrixOld(denseA);
22+
const BOld = new SparseMatrixOld(denseB);
23+
denseA = new Matrix(denseA);
24+
denseB = new Matrix(denseB);
25+
// Use Benchmark.js for each method
26+
const suite = new Benchmark.Suite();
27+
let mmulNewAvg, mmulAvg, denseAvg;
28+
suite
29+
.add('mmulNew', function () {
30+
A.mmul(B);
31+
})
32+
.add('mmul', function () {
33+
AOld.mmul(BOld);
34+
})
35+
.add('denseMatrix', function () {
36+
denseA.mmul(denseB);
37+
})
38+
.on('cycle', function (event) {
39+
// Optionally log: console.log(String(event.target));
40+
})
41+
.on('complete', function () {
42+
this.forEach((bench) => {
43+
if (bench.name === 'mmulNew') mmulNewAvg = 1000 / bench.hz;
44+
if (bench.name === 'mmul') mmulAvg = 1000 / bench.hz;
45+
if (bench.name === 'denseMatrix') denseAvg = 1000 / bench.hz;
46+
});
47+
})
48+
.run({ async: false });
49+
results.push({
50+
densityA,
51+
densityB,
52+
A_shape: [m, n],
53+
B_shape: [n, p],
54+
dense: denseAvg,
55+
new: mmulNewAvg,
56+
old: mmulAvg,
57+
});
58+
}
59+
}
60+
}
61+
}
62+
}
63+
fs.writeFileSync(
64+
'./benchmark/size_sweep_results-dense.json',
65+
JSON.stringify(results, null, 2),
66+
);
67+
console.log('Size sweep benchmark results saved to size_sweep_results.json');
68+
}
69+
70+
runSizeSweepBenchmark();

src/index.js

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1538,28 +1538,3 @@ function matrixCreateEmpty(nbRows, nbColumns) {
15381538
}
15391539
return newMatrix;
15401540
}
1541-
1542-
function useCscCsrFormat(A, B) {
1543-
// Validar dimensiones
1544-
if (!A.rows || !A.columns || !B.rows || !B.columns || A.columns !== B.rows) {
1545-
throw new Error('Dimensiones de matrices inválidas o no compatibles.');
1546-
}
1547-
1548-
const densityA = A.cardinality / (A.rows * A.columns);
1549-
const densityB = B.cardinality / (B.rows * B.columns);
1550-
1551-
const M = A.rows;
1552-
const K = A.columns;
1553-
const N = B.columns;
1554-
1555-
const isHighDensity = densityA >= 0.015 || densityB >= 0.015;
1556-
const isVeryHighDensity = densityB >= 0.03;
1557-
const isLargeMatrix = M >= 64 || K >= 64 || N >= 64;
1558-
const isLargeSquareMatrix = M === K && K === N && M >= 128;
1559-
1560-
return (
1561-
isVeryHighDensity ||
1562-
(isHighDensity && isLargeMatrix) ||
1563-
(isLargeSquareMatrix && densityA >= 0.03)
1564-
);
1565-
}

0 commit comments

Comments
 (0)