Skip to content

Commit ac26a10

Browse files
committed
Merge branch 'improve-performance-mmul' of github.com:jobo322/sparse-matrix into improve-performance-mmul
2 parents 0f1cdb9 + c050090 commit ac26a10

File tree

8 files changed

+318
-239
lines changed

8 files changed

+318
-239
lines changed

benchmark/benchmark.js

Lines changed: 94 additions & 179 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,18 @@
1-
import fs from 'node:fs';
2-
1+
/* eslint-disable no-invalid-this */
2+
import Benchmark from 'benchmark';
33
import { Matrix } from 'ml-matrix';
44

5-
import { SparseMatrix } from '../src/index.js';
6-
75
import { SparseMatrix as SparseMatrixOld } from './class/SparseMatrixOld.js';
8-
9-
function randomSparseMatrix(rows, cols, density = 0.01) {
10-
const matrix = [];
11-
for (let i = 0; i < rows; i++) {
12-
const row = new Array(cols).fill(0);
13-
for (let j = 0; j < cols; j++) {
14-
if (Math.random() < density) {
15-
row[j] = Math.random() * 10;
16-
}
17-
}
18-
matrix.push(row);
19-
}
20-
return new SparseMatrix(matrix);
21-
}
22-
23-
function benchmark(fn, label, iterations = 5) {
24-
const times = [];
25-
for (let i = 0; i < iterations; i++) {
26-
const t0 = performance.now();
27-
fn();
28-
const t1 = performance.now();
29-
times.push(t1 - t0);
30-
}
31-
const avg = times.reduce((a, b) => a + b, 0) / times.length;
32-
console.log(`${label}: avg ${avg.toFixed(2)} ms over ${iterations} runs`);
33-
return avg;
34-
}
35-
36-
function printWinner(label1, avg1, label2, avg2) {
37-
let winner, loser, win, lose;
38-
if (avg1 < avg2) {
39-
winner = label1;
40-
win = avg1;
41-
loser = label2;
42-
lose = avg2;
43-
} else {
44-
winner = label2;
45-
win = avg2;
46-
loser = label1;
47-
lose = avg1;
48-
}
49-
50-
const percent = ((lose - win) / lose) * 100;
51-
console.log(
52-
` -> ${winner} was ${(lose / win).toFixed(2)}x faster (${percent.toFixed(
53-
1,
54-
)}% faster) than ${loser}\n`,
55-
);
56-
}
6+
import { printWinner } from './utils/printWinner.js';
7+
import { randomSparseMatrix } from './utils/randomSparseMatrix.js';
578

589
function runBenchmarks() {
59-
const m = 128;
60-
const n = 128;
61-
const p = 128;
10+
const m = 256;
11+
const n = 256;
12+
const p = 256;
6213
const densityA = 0.01;
6314
const densityB = 0.01;
6415

65-
const nbIterations = 3;
6616
const A = randomSparseMatrix(m, n, densityA);
6717
const B = randomSparseMatrix(n, p, densityB);
6818

@@ -75,138 +25,103 @@ function runBenchmarks() {
7525
denseA = new Matrix(denseA);
7626
denseB = new Matrix(denseB);
7727

78-
denseA.mmul(denseB);
79-
// 1. add vs addNew
80-
// const addAvg = benchmark(() => {
81-
// const a = AOld.clone();
82-
// a.add(BOld);
83-
// }, 'add');
84-
85-
// const addNewAvg = benchmark(() => {
86-
// const a = A.clone();
87-
// a.add(B);
88-
// }, 'addNew');
28+
const results = [];
8929

90-
// printWinner('add', addAvg, 'addNew', addNewAvg);
30+
// 1. add vs addNew
31+
const addSuite = new Benchmark.Suite('add');
32+
addSuite
33+
.add('add', () => {
34+
const a = AOld.clone();
35+
a.add(BOld);
36+
})
37+
.add('addNew', () => {
38+
const a = A.clone();
39+
a.add(B);
40+
})
41+
.add('addDense', () => {
42+
const a = denseA.clone();
43+
a.add(denseB);
44+
})
45+
.on('cycle', (event) => {
46+
console.log(String(event.target));
47+
})
48+
.on('complete', function onComplete() {
49+
const fastest = this.filter('fastest').map('name');
50+
this.forEach((bench) => {
51+
results.push({ label: bench.name, avg: 1000 / bench.hz });
52+
});
53+
printWinner(results);
54+
})
55+
.run({ async: false });
9156

9257
// 2. mmul vs mmulNew
93-
94-
const mmulNewAvg = benchmark(
95-
() => {
58+
const mmulSuite = new Benchmark.Suite('mmul');
59+
mmulSuite
60+
.add('mmulNew', () => {
9661
A.mmul(B);
97-
},
98-
'mmulNew',
99-
nbIterations,
100-
);
101-
102-
const mmulAvg = benchmark(
103-
() => {
62+
})
63+
.add('mmul', () => {
10464
AOld.mmul(BOld);
105-
},
106-
'mmul',
107-
nbIterations,
108-
);
109-
110-
const denseAvg = benchmark(
111-
() => {
65+
})
66+
.add('denseMatrix', () => {
11267
denseA.mmul(denseB);
113-
},
114-
'denseMatrix',
115-
nbIterations,
116-
);
117-
118-
printWinner('mmul', mmulAvg, 'mmulNew', mmulNewAvg);
68+
})
69+
.on('cycle', (event) => {
70+
console.log(String(event.target));
71+
})
72+
.on('complete', function onComplete() {
73+
const mmulResults = [];
74+
this.forEach((bench) => {
75+
mmulResults.push({ label: bench.name, avg: 1000 / bench.hz });
76+
});
77+
printWinner(mmulResults);
78+
})
79+
.run({ async: false });
11980

12081
// 3. kroneckerProduct vs kroneckerProductNew
121-
// const kronNewAvg = benchmark(() => {
122-
// A.kroneckerProduct(B);
123-
// }, 'kroneckerProductNew');
124-
// const kronAvg = benchmark(() => {
125-
// AOld.kroneckerProduct(BOld);
126-
// }, 'kroneckerProduct');
127-
128-
// printWinner('kroneckerProduct', kronAvg, 'kroneckerProductNew', kronNewAvg);
82+
const kronSuite = new Benchmark.Suite('kroneckerProduct');
83+
kronSuite
84+
.add('kroneckerProductNew', () => {
85+
A.kroneckerProduct(B);
86+
})
87+
.add('kroneckerProduct', () => {
88+
AOld.kroneckerProduct(BOld);
89+
})
90+
.add('kroneckerProductDense', () => {
91+
denseA.kroneckerProduct(denseB);
92+
})
93+
.on('cycle', (event) => {
94+
console.log(String(event.target));
95+
})
96+
.on('complete', function onComplete() {
97+
const kronResults = [];
98+
this.forEach((bench) => {
99+
kronResults.push({ label: bench.name, avg: 1000 / bench.hz });
100+
});
101+
printWinner(kronResults);
102+
})
103+
.run({ async: false });
129104

130105
// 4. matrix multiplication
131-
// const mulAvg = benchmark(() => {
132-
// A.mul(5);
133-
// }, 'mul');
134-
135-
// const mulNewAvg = benchmark(() => {
136-
// AOld.mul(5);
137-
// }, 'mulNew');
138-
139-
// printWinner('mul', mulAvg, 'mulNew', mulNewAvg);
140-
}
141-
142-
function runSizeSweepBenchmark() {
143-
const sizes = [8, 16, 32, 64, 128];
144-
const densities = [0.01, 0.015, 0.02, 0.025, 0.03, 0.35];
145-
const results = [];
146-
147-
for (const densityA of densities) {
148-
for (const densityB of densities) {
149-
for (const m of sizes) {
150-
for (const n of sizes) {
151-
for (const p of sizes) {
152-
// A: m x n, B: n x p
153-
154-
const A = randomSparseMatrix(m, n, densityA);
155-
const B = randomSparseMatrix(n, p, densityB);
156-
157-
let denseA = A.to2DArray();
158-
let denseB = B.to2DArray();
159-
160-
const AOld = new SparseMatrixOld(denseA);
161-
const BOld = new SparseMatrixOld(denseB);
162-
163-
denseA = new Matrix(denseA);
164-
denseB = new Matrix(denseB);
165-
166-
const mmulNewAvg = benchmark(
167-
() => {
168-
A.mmul(B);
169-
},
170-
'mmulNew',
171-
3,
172-
);
173-
174-
const mmulAvg = benchmark(
175-
() => {
176-
AOld.mmul(BOld);
177-
},
178-
'mmul',
179-
3,
180-
);
181-
182-
const denseAvg = benchmark(
183-
() => denseA.mmul(denseB),
184-
'denseMatrix',
185-
3,
186-
);
187-
188-
results.push({
189-
densityA,
190-
densityB,
191-
aShape: [m, n],
192-
bShape: [n, p],
193-
dense: denseAvg,
194-
mmulNew: mmulNewAvg,
195-
mmul: mmulAvg,
196-
});
197-
}
198-
}
199-
}
200-
}
201-
}
202-
203-
fs.writeFileSync(
204-
'./benchmark/size_sweep_results-dense.json',
205-
JSON.stringify(results, null, 2),
206-
);
207-
console.log('Size sweep benchmark results saved to size_sweep_results.json');
106+
const mulSuite = new Benchmark.Suite('mul');
107+
mulSuite
108+
.add('mul', () => {
109+
A.mul(5);
110+
})
111+
.add('mulNew', () => {
112+
AOld.mul(5);
113+
})
114+
.on('cycle', (event) => {
115+
console.log(String(event.target));
116+
})
117+
.on('complete', function onComplete() {
118+
const mulResults = [];
119+
this.forEach((bench) => {
120+
mulResults.push({ label: bench.name, avg: 1000 / bench.hz });
121+
});
122+
printWinner(mulResults);
123+
})
124+
.run({ async: false });
208125
}
209126

210127
runBenchmarks();
211-
// Uncomment to run the size sweep benchmark
212-
// runSizeSweepBenchmark();

benchmark/squareMatrixBenchmark.js

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

0 commit comments

Comments
 (0)