Skip to content
Permalink
Browse files

Merge pull request #3 from marcomontalbano/matrix-multiplication

Add 'matrix multiplication' algorithm
  • Loading branch information...
marcomontalbano committed Feb 26, 2019
2 parents 8da3840 + 73b2719 commit 8f202021e13c9e9ed0183d697a05213bd4019cbb
Showing with 282 additions and 49 deletions.
  1. +10 −6 README.md
  2. +1 −1 package.json
  3. +13 −23 public/index.html
  4. +55 −0 public/main.css
  5. +41 −0 src-js/libs/matrix.js
  6. +85 −0 src-js/libs/matrix.test.js
  7. +1 −0 src-js/libs/mod.js
  8. +11 −4 src-js/main.js
  9. +6 −3 src-js/web/chart.js
  10. +3 −3 src-js/web/utility.js
  11. +47 −0 src/libs/matrix.rs
  12. +1 −0 src/libs/mod.rs
  13. +6 −8 src/main.rs
  14. +2 −1 webpack.config.js
@@ -86,16 +86,16 @@ $ cargo build --release
Now that we have built our code, we can run it:

```sh
$ ./target/release/wa-vs-js-benchmark get_primes 11
$ ./target/release/wa-vs-js-benchmark primes-get_primes 11
```

We can also use `cargo run` to compile and then run it, all in one step:

```sh
$ cargo run get_primes 11
$ cargo run primes-get_primes 11
# compile and run our project with optimizations
$ cargo run --release get_primes 11
$ cargo run --release primes-get_primes 11
```

Last but not least, we'll compile our project to `.wasm`:
@@ -147,13 +147,17 @@ These benchmarks are recorded on a MacBook Pro (15-inch, 2016) having these spec
- 16 GB 2133 MHz LPDDR3

```sh
cargo build --release
hyperfine --warmup 3 './target/release/wa-vs-js-benchmark get_primes 100000'
$ cargo build --release
$ hyperfine --warmup 3 --export-markdown BENCHMARK.md \
'./target/release/wa-vs-js-benchmark primes-get_primes 100000' \
'./target/release/wa-vs-js-benchmark matrix-multiply 500 500'
```

| Command | Mean [s] | Min…Max [s] |
|:---|---:|---:|
| `./target/release/wa-vs-js-benchmark get_primes 100000` | 1.204 ± 0.012 | 1.193…1.235 |
| `./target/release/wa-vs-js-benchmark primes-get_primes 100000` | 1.198 ± 0.006 | 1.191…1.212 |
| `./target/release/wa-vs-js-benchmark matrix-multiply 500 500` | 0.420 ± 0.006 | 0.411…0.432 |


### WebAssembly vs Javascript
@@ -18,7 +18,7 @@
"build:rs": "cargo build --release",
"build:js": "webpack --mode=production --config webpack.config.js",
"build": "npm run build:wasm && npm run build:js",
"benchmark": "npm run build:rs && hyperfine --export-markdown BENCHMARK.md --warmup 3 './target/release/wa-vs-js-benchmark get_primes 100000'",
"benchmark": "npm run build:rs && hyperfine --export-markdown BENCHMARK.md --warmup 3 './target/release/wa-vs-js-benchmark primes-get_primes 100000'",
"publish": "gh-pages -m 'Publish website.' -b 'gh-pages' -d 'dist' -r \"https://$GH_TOKEN@github.com/marcomontalbano/wa-vs-js-benchmark\""
},
"dependencies": {
@@ -1,32 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
margin: 0;
}
#root {
position: absolute;
width: 100%; height: 100%;
top: 0; left: 0;
}
.chart-container {
margin: 0 auto;
max-width: 90vw;
width: 180vh;
height: 100vh;
}
.chart-container canvas {
width: 400px;
height: 200px;
position: relative;
top: 50%;
transform: translateY(-50%);
}
</style>
<title>WebAssembly vs Javascript</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="https://fonts.googleapis.com/css?family=Roboto:300" rel="stylesheet">
<link rel="stylesheet" href="./main.css" />
</head>
<body>
<div id="root"></div>
<script src="./bootstrap.js"></script>
<footer>
<a target="_blank" href="https://marcomontalbano.com">Marco Montalbano</a>
<a target="_blank" href="https://github.com/marcomontalbano/wa-vs-js-benchmark">
<svg height="14" width="14" viewBox="0 0 16 16" version="1.1" aria-hidden="true">
<path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z">
</path>
</svg> GitHub
</a>
</footer>
</body>
</html>
@@ -0,0 +1,55 @@
:root {
--color-link: #2196F3;
--color-link-hover: #1565C0;
}

body {
margin: 0;
font-size: 14px;
font-family: 'Roboto', sans-serif;
font-weight: 300;
letter-spacing: .1em;
}
a, a:link, a:hover, a:visited {
text-decoration: none;
}
#root {
position: absolute;
width: 100%; height: 100%;
top: 0; left: 0;
}
.chart-container {
display: block;
margin: 0 auto;
max-width: 90vw;
width: 180vh;
height: 100vh;
}
.chart-container canvas {
width: 400px;
height: 200px;
position: relative;
top: 50%;
transform: translateY(-50%);
}
footer {
position: fixed;
bottom: 15px;
right: 20px;
z-index: 1;
}
footer a {
margin: 0 10px;
color: var(--color-link);
transition: color .3s;
}
footer a svg {
fill: var(--color-link);
transition: fill .3s;
}
footer a:hover {
color: var(--color-link-hover);
}
footer a:hover svg {
fill: var(--color-link-hover);
}
@@ -0,0 +1,41 @@
export const _multiply = (a, b) => {
let c = [];
for (let i = 0; i < a.length; i++) {
for (let j = 0; j < b[0].length; j++) {
for (let k = 0; k < b.length; k++) {
c[i] = c[i] || [];
c[i][j] = c[i][j] || 0;
c[i][j] += a[i][k] * b[k][j];
}
}
}
return c;
};

export const multiply = (a, b) => {
return _multiply(
Array(a).fill(Array(a).fill(1)),
Array(b).fill(Array(b).fill(1)),
)[0][0];
};

export const _multiply_slower = (a, b) => {
let c = [];
for (let k = 0; k < b.length; k++) {
for (let j = 0; j < b[0].length; j++) {
for (let i = 0; i < a.length; i++) {
c[i] = c[i] || [];
c[i][j] = c[i][j] || 0;
c[i][j] += a[i][k] * b[k][j];
}
}
}
return c;
};

export const multiply_slower = (a, b) => {
return _multiply_slower(
Array(a).fill(Array(a).fill(1)),
Array(b).fill(Array(b).fill(1)),
)[0][0];
};
@@ -0,0 +1,85 @@
import assert from 'assert';

import * as matrix from './matrix';

const createTestFor = method => {
describe(`.${method}()`, () => {
it('test 1', () => {
const expected = [
[7, 10],
[15, 22],
];

assert.deepEqual(matrix[method](
[
[1, 2],
[3, 4]
],
[
[1, 2],
[3, 4]
]
), expected);
});

it('test 2', () => {
const expected = [
[58, 64],
[139, 154],
];

assert.deepEqual(matrix[method](
[
[1, 2, 3],
[4, 5, 6]
],
[
[7, 8],
[9, 10],
[11, 12]
]
), expected);
});

it('test 3', () => {
const expected = [[83, 63, 37, 75]];

assert.deepEqual(matrix[method](
[[3, 4, 2]],
[
[13, 9, 7, 15],
[8, 7, 4, 6],
[6, 4, 0, 3]
]
), expected);
});

it('test 4', () => {
const expected = [[4, 4], [10, 8]];

assert.deepEqual(matrix[method](
[
[1, 2],
[3, 4],
],
[
[2, 0],
[1, 2],
]
), expected);
});
});
}

describe('matrix', () => {
createTestFor('_multiply');
createTestFor('_multiply_slower');

it('.multiply()', () => {
assert.deepEqual(matrix.multiply(10, 10), 10);
});

it('.multiply_slower()', () => {
assert.deepEqual(matrix.multiply_slower(10, 10), 10);
});
});
@@ -1 +1,2 @@
export * from './primes';
export * from './matrix';
@@ -1,6 +1,13 @@
import { createBenchmarkChart } from './web/chart';
import { promiseSequential } from './web/utility';

createBenchmarkChart({
method: 'get_primes',
args: [100000]
});
promiseSequential([
createBenchmarkChart({
method: 'get_primes',
args: [100000]
}),
createBenchmarkChart({
method: 'multiply',
args: [500, 500]
})
]);
@@ -91,14 +91,17 @@ const _runBenchmark = (payload, chart, times = 5) => {

export const createBenchmarkChart = payload => {

let element_chartContainer = document.createElement('div');
let element_chartContainer = document.createElement('a');
element_chartContainer.id = `method--${payload.method}`;
element_chartContainer.classList.add('chart-container');

let element_chartCanvas = document.createElement('canvas');
let chart = _createChart(element_chartCanvas, `${payload.method}(${payload.args})`);
let chart = _createChart(element_chartCanvas, `${payload.method}(${payload.args.join(', ')})`);

element_chartContainer.appendChild(element_chartCanvas);
document.getElementById('root').appendChild(element_chartContainer);

_runBenchmark(payload, chart);
return () => {
return _runBenchmark(payload, chart);
};
}
@@ -36,13 +36,13 @@ const createPromiseWorker = Worker => {
const rsWorker = createPromiseWorker(RSWorker);
const jsWorker = createPromiseWorker(JSWorker);

const _cloneArrayElements = (arr, times) => {
export const cloneArrayElements = (arr, times) => {
return arr.reduce((accumulator, currentValue) => {
return accumulator.concat(Array(times).fill(currentValue))
}, [])
}

const _promiseSequential = fns => {
export const promiseSequential = fns => {
return fns.reduce((promise, fn) => {
return promise.then(results => {
return fn().then([].concat.bind(results));
@@ -51,7 +51,7 @@ const _promiseSequential = fns => {
};

export const runBenchmark = (payload, times = 5, eachTime = value => value) => {
return _promiseSequential(_cloneArrayElements([
return promiseSequential(cloneArrayElements([
() => rsWorker.postMessage(payload).then(eachTime),
() => jsWorker.postMessage(payload).then(eachTime),
], times));
@@ -0,0 +1,47 @@
extern crate wasm_bindgen;

use wasm_bindgen::prelude::*;

#[allow(dead_code)]
fn _multiply(a: Vec<Vec<i32>>, b: Vec<Vec<i32>>) -> Vec<Vec<i32>> {

let mut c: Vec<Vec<i32>> = Vec::new();

for i in 0..a.len() {
c.push(vec![0; b.len()]);
for j in 0..b[0].len() {
for k in 0..b.len() {
c[i][j] += a[i][k] * b[k][j];
}
}
}

return c;
}

#[wasm_bindgen]
pub fn multiply(a: usize, b: usize) -> i32 {
return _multiply(
vec![vec![1; a]; a],
vec![vec![1; b]; b]
)[0][0];
}

#[cfg(test)]
mod tests {

use super::*;

#[test]
fn test_private_multiply() {
assert_eq!(_multiply(
vec![vec![1, 2], vec![3, 4]],
vec![vec![1, 2], vec![3, 4]]
), vec![vec![7, 10], vec![15, 22]]);
}

#[test]
fn test_multiply() {
assert_eq!(multiply(10,10), 10);
}
}
@@ -1 +1,2 @@
pub mod primes;
pub mod matrix;
Oops, something went wrong.

0 comments on commit 8f20202

Please sign in to comment.
You can’t perform that action at this time.