Skip to content

Commit d647066

Browse files
committed
feat: add benchmark for sparse matrix operations and performance comparison
1 parent 1bacd59 commit d647066

File tree

2 files changed

+1590
-0
lines changed

2 files changed

+1590
-0
lines changed

benchmark/benchmark.js

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
import { SparseMatrix } from '../src/index.js';
2+
import { Matrix } from 'ml-matrix';
3+
import { SparseMatrix as SparseMatrixOld } from './class/SparseMatrixOld.js';
4+
import fs from 'fs';
5+
6+
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);
16+
}
17+
return new SparseMatrix(matrix);
18+
}
19+
20+
function benchmark(fn, label, iterations = 5) {
21+
const times = [];
22+
for (let i = 0; i < iterations; i++) {
23+
const t0 = performance.now();
24+
fn();
25+
const t1 = performance.now();
26+
times.push(t1 - t0);
27+
}
28+
const avg = times.reduce((a, b) => a + b, 0) / times.length;
29+
console.log(`${label}: avg ${avg.toFixed(2)} ms over ${iterations} runs`);
30+
return avg;
31+
}
32+
33+
function printWinner(label1, avg1, label2, avg2) {
34+
let winner, loser, win, lose;
35+
if (avg1 < avg2) {
36+
winner = label1;
37+
win = avg1;
38+
loser = label2;
39+
lose = avg2;
40+
} else {
41+
winner = label2;
42+
win = avg2;
43+
loser = label1;
44+
lose = avg1;
45+
}
46+
47+
const percent = ((lose - win) / lose) * 100;
48+
console.log(
49+
` -> ${winner} was ${(lose / win).toFixed(2)}x faster (${percent.toFixed(
50+
1,
51+
)}% faster) than ${loser}\n`,
52+
);
53+
}
54+
55+
function runBenchmarks() {
56+
const m = 128;
57+
const n = 64;
58+
const p = 128;
59+
const densityA = 0.03;
60+
const densityB = 0.03;
61+
const A = randomSparseMatrix(m, n, densityA);
62+
const B = randomSparseMatrix(n, p, densityB);
63+
64+
let denseA = A.to2DArray();
65+
let denseB = B.to2DArray();
66+
67+
const AOld = new SparseMatrixOld(denseA);
68+
const BOld = new SparseMatrixOld(denseB);
69+
A.cardinality;
70+
denseA = new Matrix(denseA);
71+
denseB = new Matrix(denseB);
72+
73+
// 1. add vs addNew
74+
// const addAvg = benchmark(() => {
75+
// const a = AOld.clone();
76+
// a.add(BOld);
77+
// }, 'add');
78+
79+
// const addNewAvg = benchmark(() => {
80+
// const a = A.clone();
81+
// a.add(B);
82+
// }, 'addNew');
83+
84+
// printWinner('add', addAvg, 'addNew', addNewAvg);
85+
86+
// 2. mmul vs mmulNew
87+
88+
const mmulNewAvg = benchmark(
89+
() => {
90+
A.mmul(B);
91+
},
92+
'mmulNew',
93+
3,
94+
);
95+
96+
const mmulAvg = benchmark(
97+
() => {
98+
AOld.mmul(BOld);
99+
},
100+
'mmul',
101+
3,
102+
);
103+
104+
const denseAvg = benchmark(
105+
() => {
106+
denseA.mmul(denseB);
107+
},
108+
'denseMatrix',
109+
3,
110+
);
111+
112+
printWinner('mmul', mmulAvg, 'mmulNew', mmulNewAvg);
113+
114+
// 3. kroneckerProduct vs kroneckerProductNew
115+
// const kronNewAvg = benchmark(() => {
116+
// A.kroneckerProduct(B);
117+
// }, 'kroneckerProductNew');
118+
// const kronAvg = benchmark(() => {
119+
// AOld.kroneckerProduct(BOld);
120+
// }, 'kroneckerProduct');
121+
122+
// printWinner('kroneckerProduct', kronAvg, 'kroneckerProductNew', kronNewAvg);
123+
124+
// 4. matrix multiplication
125+
// const mulAvg = benchmark(() => {
126+
// A.mul(5);
127+
// }, 'mul');
128+
129+
// const mulNewAvg = benchmark(() => {
130+
// AOld.mul(5);
131+
// }, 'mulNew');
132+
133+
// printWinner('mul', mulAvg, 'mulNew', mulNewAvg);
134+
}
135+
136+
function runSizeSweepBenchmark() {
137+
const sizes = [8, 16, 32, 64, 128];
138+
const densities = [0.01, 0.015, 0.02, 0.025, 0.03, 0.35];
139+
const results = [];
140+
141+
for (const densityA of densities) {
142+
for (const densityB of densities) {
143+
for (const m of sizes) {
144+
for (const n of sizes) {
145+
for (const p of sizes) {
146+
// A: m x n, B: n x p
147+
148+
const A = randomSparseMatrix(m, n, densityA);
149+
const B = randomSparseMatrix(n, p, densityB);
150+
151+
let denseA = A.to2DArray();
152+
let denseB = B.to2DArray();
153+
154+
const AOld = new SparseMatrixOld(denseA);
155+
const BOld = new SparseMatrixOld(denseB);
156+
157+
denseA = new Matrix(denseA);
158+
denseB = new Matrix(denseB);
159+
160+
const mmulNewAvg = benchmark(
161+
() => {
162+
A.mmul(B);
163+
},
164+
'mmulNew',
165+
3,
166+
);
167+
168+
const mmulAvg = benchmark(
169+
() => {
170+
AOld.mmul(BOld);
171+
},
172+
'mmul',
173+
3,
174+
);
175+
176+
const denseAvg = benchmark(() => {
177+
denseA.mmul(denseB), 'denseMatrix', 3;
178+
});
179+
180+
results.push({
181+
densityA,
182+
densityB,
183+
A_shape: [m, n],
184+
B_shape: [n, p],
185+
dense: denseAvg,
186+
mmulNew: mmulNewAvg,
187+
mmul: mmulAvg,
188+
});
189+
}
190+
}
191+
}
192+
}
193+
}
194+
195+
fs.writeFileSync(
196+
'./benchmark/size_sweep_results-dense.json',
197+
JSON.stringify(results, null, 2),
198+
);
199+
console.log('Size sweep benchmark results saved to size_sweep_results.json');
200+
}
201+
202+
runBenchmarks();
203+
// Uncomment to run the size sweep benchmark
204+
// runSizeSweepBenchmark();

0 commit comments

Comments
 (0)