Skip to content

Commit 061976e

Browse files
MaxGraeydcodeIO
authored andcommitted
Add rust n-body version for comparison (#88)
1 parent 99bde3a commit 061976e

File tree

9 files changed

+275
-26
lines changed

9 files changed

+275
-26
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ docs/
33
node_modules/
44
out/
55
raw/
6+
examples/n-body/rust/target/

examples/n-body/assembly/index.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const fs = require("fs");
2+
const path = require("path");
3+
4+
const compiled = new WebAssembly.Module(
5+
fs.readFileSync(path.resolve(__dirname, "..", "build", "optimized.wasm"))
6+
);
7+
8+
const imports = {
9+
env: {
10+
abort: (filename, line, column) => {
11+
throw Error("abort called at " + line + ":" + colum);
12+
}
13+
}
14+
};
15+
16+
Object.defineProperty(module, "exports", {
17+
get: () => new WebAssembly.Instance(compiled, imports).exports
18+
});

examples/n-body/index.js

Lines changed: 0 additions & 8 deletions
This file was deleted.

examples/n-body/rust/Cargo.lock

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/n-body/rust/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[package]
2+
name = "rust_nbody"
3+
version = "0.1.0"
4+
authors = ["MaxGraey <maxgraey@gmail.com>"]
5+
6+
[lib]
7+
path = "src/lib.rs"
8+
crate-type = ["cdylib"]

examples/n-body/rust/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
### Build
2+
3+
```bash
4+
cargo build --release --target=wasm32-unknown-unknown
5+
```
6+
7+
***Next step optimize target wasm via wasm-opt.***

examples/n-body/rust/index.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const fs = require("fs");
2+
const path = require("path");
3+
4+
const compiled = new WebAssembly.Module(
5+
fs.readFileSync(path.resolve(__dirname, "..", "build", "rust.optimized.wasm"))
6+
);
7+
8+
const imports = {
9+
env: {
10+
abort: (filename, line, column) => {
11+
throw Error("abort called at " + line + ":" + colum);
12+
}
13+
}
14+
};
15+
16+
Object.defineProperty(module, "exports", {
17+
get: () => new WebAssembly.Instance(compiled, imports).exports
18+
});

examples/n-body/rust/src/lib.rs

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
// Code adopted from https://benchmarksgame-team.pages.debian.net/benchmarksgame/program/nbody-rust-1.html
2+
3+
#![feature(core_intrinsics, lang_items)]
4+
#![no_std]
5+
6+
#[lang = "panic_fmt"]
7+
extern "C" fn panic_fmt(_args: ::core::fmt::Arguments, _file: &'static str, _line: u32) -> ! {
8+
use core::intrinsics;
9+
unsafe {
10+
intrinsics::abort();
11+
}
12+
}
13+
14+
#[inline(always)]
15+
fn sqrt(x: f64) -> f64 {
16+
use core::intrinsics;
17+
unsafe {
18+
intrinsics::sqrtf64(x)
19+
}
20+
}
21+
22+
const PI: f64 = 3.141592653589793;
23+
const SOLAR_MASS: f64 = 4.0 * PI * PI;
24+
const YEAR: f64 = 365.24;
25+
const N_BODIES: usize = 5;
26+
27+
static mut BODIES: [Planet; N_BODIES] = [
28+
// Sun
29+
Planet {
30+
x: 0.0,
31+
y: 0.0,
32+
z: 0.0,
33+
vx: 0.0,
34+
vy: 0.0,
35+
vz: 0.0,
36+
mass: SOLAR_MASS,
37+
},
38+
// Jupiter
39+
Planet {
40+
x: 4.84143144246472090e+00,
41+
y: -1.16032004402742839e+00,
42+
z: -1.03622044471123109e-01,
43+
vx: 1.66007664274403694e-03 * YEAR,
44+
vy: 7.69901118419740425e-03 * YEAR,
45+
vz: -6.90460016972063023e-05 * YEAR,
46+
mass: 9.54791938424326609e-04 * SOLAR_MASS,
47+
},
48+
// Saturn
49+
Planet {
50+
x: 8.34336671824457987e+00,
51+
y: 4.12479856412430479e+00,
52+
z: -4.03523417114321381e-01,
53+
vx: -2.76742510726862411e-03 * YEAR,
54+
vy: 4.99852801234917238e-03 * YEAR,
55+
vz: 2.30417297573763929e-05 * YEAR,
56+
mass: 2.85885980666130812e-04 * SOLAR_MASS,
57+
},
58+
// Uranus
59+
Planet {
60+
x: 1.28943695621391310e+01,
61+
y: -1.51111514016986312e+01,
62+
z: -2.23307578892655734e-01,
63+
vx: 2.96460137564761618e-03 * YEAR,
64+
vy: 2.37847173959480950e-03 * YEAR,
65+
vz: -2.96589568540237556e-05 * YEAR,
66+
mass: 4.36624404335156298e-05 * SOLAR_MASS,
67+
},
68+
// Neptune
69+
Planet {
70+
x: 1.53796971148509165e+01,
71+
y: -2.59193146099879641e+01,
72+
z: 1.79258772950371181e-01,
73+
vx: 2.68067772490389322e-03 * YEAR,
74+
vy: 1.62824170038242295e-03 * YEAR,
75+
vz: -9.51592254519715870e-05 * YEAR,
76+
mass: 5.15138902046611451e-05 * SOLAR_MASS,
77+
},
78+
];
79+
80+
#[derive(Clone, Copy)]
81+
struct Planet {
82+
x: f64,
83+
y: f64,
84+
z: f64,
85+
vx: f64,
86+
vy: f64,
87+
vz: f64,
88+
mass: f64,
89+
}
90+
91+
fn advance(bodies: &mut [Planet; N_BODIES], dt: f64) {
92+
let mut b_slice: &mut [_] = bodies;
93+
loop {
94+
let bi = match shift_mut_ref(&mut b_slice) {
95+
Some(bi) => bi,
96+
None => break,
97+
};
98+
99+
for bj in b_slice.iter_mut() {
100+
let dx = bi.x - bj.x;
101+
let dy = bi.y - bj.y;
102+
let dz = bi.z - bj.z;
103+
104+
let d2 = dx * dx + dy * dy + dz * dz;
105+
let mag = dt / (d2 * sqrt(d2));
106+
107+
let massj_mag = bj.mass * mag;
108+
bi.vx -= dx * massj_mag;
109+
bi.vy -= dy * massj_mag;
110+
bi.vz -= dz * massj_mag;
111+
112+
let massi_mag = bi.mass * mag;
113+
bj.vx += dx * massi_mag;
114+
bj.vy += dy * massi_mag;
115+
bj.vz += dz * massi_mag;
116+
}
117+
bi.x += dt * bi.vx;
118+
bi.y += dt * bi.vy;
119+
bi.z += dt * bi.vz;
120+
}
121+
}
122+
123+
fn energy(bodies: &[Planet; N_BODIES]) -> f64 {
124+
let mut e = 0.0;
125+
let mut bodies = bodies.iter();
126+
127+
loop {
128+
let bi = match bodies.next() {
129+
Some(bi) => bi,
130+
None => break,
131+
};
132+
133+
e += (bi.vx * bi.vx + bi.vy * bi.vy + bi.vz * bi.vz) * bi.mass / 2.0;
134+
for bj in bodies.clone() {
135+
let dx = bi.x - bj.x;
136+
let dy = bi.y - bj.y;
137+
let dz = bi.z - bj.z;
138+
let dist = sqrt(dx * dx + dy * dy + dz * dz);
139+
e -= bi.mass * bj.mass / dist;
140+
}
141+
}
142+
e
143+
}
144+
145+
fn offset_momentum(bodies: &mut [Planet; N_BODIES]) {
146+
let mut px = 0.0;
147+
let mut py = 0.0;
148+
let mut pz = 0.0;
149+
for bi in bodies.iter() {
150+
px += bi.vx * bi.mass;
151+
py += bi.vy * bi.mass;
152+
pz += bi.vz * bi.mass;
153+
}
154+
let sun = &mut bodies[0];
155+
sun.vx = -px / SOLAR_MASS;
156+
sun.vy = -py / SOLAR_MASS;
157+
sun.vz = -pz / SOLAR_MASS;
158+
}
159+
160+
#[no_mangle]
161+
pub unsafe extern "C" fn init() {
162+
offset_momentum(&mut BODIES);
163+
}
164+
165+
#[no_mangle]
166+
pub unsafe extern "C" fn step() -> f64 {
167+
advance(&mut BODIES, 0.01);
168+
energy(&BODIES)
169+
}
170+
171+
#[no_mangle]
172+
pub unsafe extern "C" fn bench(steps: i32) {
173+
for _ in 0..steps {
174+
advance(&mut BODIES, 0.01);
175+
}
176+
}
177+
178+
/// Pop a mutable reference off the head of a slice, mutating the slice to no
179+
/// longer contain the mutable reference.
180+
fn shift_mut_ref<'a, T>(r: &mut &'a mut [T]) -> Option<&'a mut T> {
181+
if r.len() == 0 {
182+
return None;
183+
}
184+
let tmp = core::mem::replace(r, &mut []);
185+
let (h, t) = tmp.split_at_mut(1);
186+
*r = t;
187+
Some(&mut h[0])
188+
}

examples/n-body/tests/index.js

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
const fs = require("fs");
22

33
// Load WASM version
4-
const nbodyWASM = require("../index.js");
4+
const nbodyAS = require("../assembly/index.js");
5+
const nbodyRS = require("../rust/index.js");
56

67
// Load ASMJS version
7-
src = fs.readFileSync(__dirname + "/../build/index.asm.js", "utf8");
8+
var src = fs.readFileSync(__dirname + "/../build/index.asm.js", "utf8");
89
if (src.indexOf("var Math_sqrt =") < 0) { // currently missing in asm.js output
910
let p = src.indexOf(" var abort = env.abort;");
1011
src = src.substring(0, p) + " var Math_sqrt = global.Math.sqrt;\n " + src.substring(p);
1112
}
12-
var nbodyASMJS = eval("0," + src)({
13+
14+
const nbodyAsmJS = eval("0," + src)({
1315
Int8Array,
1416
Int16Array,
1517
Int32Array,
@@ -20,17 +22,20 @@ var nbodyASMJS = eval("0," + src)({
2022
Float64Array,
2123
Math
2224
}, {
23-
abort: function() { throw Error(); }
25+
abort: () => { throw Error(); }
2426
}, new ArrayBuffer(0x10000));
2527

2628
// Load JS version
27-
var src = fs.readFileSync(__dirname + "/../build/index.js", "utf8");
28-
var scopeJS = {
29-
require: function() {},
30-
exports: {},
31-
unchecked: function(expr) { return expr }
29+
src = fs.readFileSync(__dirname + "/../build/index.js", "utf8");
30+
const scopeJS = {
31+
require: () => {},
32+
exports: {},
33+
unchecked: expr => expr
3234
};
33-
var nbodyJS = new Function(...Object.keys(scopeJS).concat(src + "\nreturn exports"))(...Object.values(scopeJS));
35+
36+
const nbodyJS = new Function(
37+
...Object.keys(scopeJS).concat(src + "\nreturn exports"))(...Object.values(scopeJS)
38+
);
3439

3540
function test(nbody, steps) {
3641
nbody.init();
@@ -42,28 +47,36 @@ function test(nbody, steps) {
4247
var steps = process.argv.length > 2 ? parseInt(process.argv[2], 10) : 20000000;
4348
var time;
4449

45-
console.log("Performing " + steps + " steps (WASM) ...");
46-
time = test(nbodyWASM, steps);
50+
console.log("Performing " + steps + " steps (AssemblyScript WASM) ...");
51+
time = test(nbodyAS, steps);
4752
console.log("Took " + (time[0] * 1e3 + time[1] / 1e6) + "ms");
4853

49-
console.log("Performing " + steps + " steps (ASMJS) ...");
50-
time = test(nbodyASMJS, steps);
54+
console.log("Performing " + steps + " steps (AssemblyScript ASMJS) ...");
55+
time = test(nbodyAsmJS, steps);
5156
console.log("Took " + (time[0] * 1e3 + time[1] / 1e6) + "ms");
5257

5358
console.log("Performing " + steps + " steps (JS) ...");
5459
time = test(nbodyJS, steps);
5560
console.log("Took " + (time[0] * 1e3 + time[1] / 1e6) + "ms");
5661

62+
console.log("Performing " + steps + " steps (Rust WASM) ...");
63+
time = test(nbodyRS, steps);
64+
console.log("Took " + (time[0] * 1e3 + time[1] / 1e6) + "ms");
65+
5766
console.log("\nWARMED UP:\n");
5867

59-
console.log("Performing " + steps + " steps (WASM) ...");
60-
time = test(nbodyWASM, steps);
68+
console.log("Performing " + steps + " steps (AssemblyScript WASM) ...");
69+
time = test(nbodyAS, steps);
6170
console.log("Took " + (time[0] * 1e3 + time[1] / 1e6) + "ms");
6271

63-
console.log("Performing " + steps + " steps (ASMJS) ...");
64-
time = test(nbodyASMJS, steps);
72+
console.log("Performing " + steps + " steps (AssemblyScript ASMJS) ...");
73+
time = test(nbodyAsmJS, steps);
6574
console.log("Took " + (time[0] * 1e3 + time[1] / 1e6) + "ms");
6675

6776
console.log("Performing " + steps + " steps (JS) ...");
6877
time = test(nbodyJS, steps);
6978
console.log("Took " + (time[0] * 1e3 + time[1] / 1e6) + "ms");
79+
80+
console.log("Performing " + steps + " steps (Rust WASM) ...");
81+
time = test(nbodyRS, steps);
82+
console.log("Took " + (time[0] * 1e3 + time[1] / 1e6) + "ms");

0 commit comments

Comments
 (0)