From 1ec7ae57f96b1bfbe7551b4fb1e2e887864ac68f Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Mon, 29 Jul 2024 09:37:59 +0200 Subject: [PATCH] rune: Add nbodies bench and test --- .github/workflows/ci.yml | 3 +- .github/workflows/release.yml | 2 +- crates/rune/benches/nbodies.lua | 123 +++++++++++++++++++++ crates/rune/benches/nbodies.rn | 172 ++++++++++++++++++++++++++++++ crates/rune/src/cli/benches.rs | 11 +- crates/rune/src/cli/mod.rs | 41 ++++--- crates/rune/src/modules/object.rs | 2 +- crates/rune/src/runtime/object.rs | 18 +--- crates/rune/src/runtime/value.rs | 3 - 9 files changed, 335 insertions(+), 40 deletions(-) create mode 100644 crates/rune/benches/nbodies.lua create mode 100644 crates/rune/benches/nbodies.rn diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8000c3be0..04b0d4eb2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -157,4 +157,5 @@ jobs: - run: cargo test --all-targets - run: cargo test --doc - run: cargo run --bin rune -- check --recursive scripts - - run: cargo run --bin rune -- test -O test-std=true + - run: cargo run --bin rune -- check --all-targets + - run: cargo run --bin rune -- test --all-targets -O test-std=true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4abb82a2d..f37c3d073 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,7 +31,7 @@ jobs: - run: cargo test --all-targets - run: cargo test --doc - run: cargo run --bin rune -- check --recursive scripts - - run: cargo run --bin rune -- test -O test-std + - run: cargo run --bin rune -- test --all-targets -O test-std build: runs-on: ${{matrix.os}} diff --git a/crates/rune/benches/nbodies.lua b/crates/rune/benches/nbodies.lua new file mode 100644 index 000000000..2a58f525f --- /dev/null +++ b/crates/rune/benches/nbodies.lua @@ -0,0 +1,123 @@ +-- The Computer Language Benchmarks Game +-- https://salsa.debian.org/benchmarksgame-team/benchmarksgame/ +-- contributed by Mike Pall +-- modified by Geoff Leyland + +local sqrt = math.sqrt + +local PI = 3.141592653589793 +local SOLAR_MASS = 4 * PI * PI +local DAYS_PER_YEAR = 365.24 +local bodies = { + { -- Sun + x = 0, + y = 0, + z = 0, + vx = 0, + vy = 0, + vz = 0, + mass = SOLAR_MASS + }, + { -- Jupiter + x = 4.84143144246472090e+00, + y = -1.16032004402742839e+00, + z = -1.03622044471123109e-01, + vx = 1.66007664274403694e-03 * DAYS_PER_YEAR, + vy = 7.69901118419740425e-03 * DAYS_PER_YEAR, + vz = -6.90460016972063023e-05 * DAYS_PER_YEAR, + mass = 9.54791938424326609e-04 * SOLAR_MASS + }, + { -- Saturn + x = 8.34336671824457987e+00, + y = 4.12479856412430479e+00, + z = -4.03523417114321381e-01, + vx = -2.76742510726862411e-03 * DAYS_PER_YEAR, + vy = 4.99852801234917238e-03 * DAYS_PER_YEAR, + vz = 2.30417297573763929e-05 * DAYS_PER_YEAR, + mass = 2.85885980666130812e-04 * SOLAR_MASS + }, + { -- Uranus + x = 1.28943695621391310e+01, + y = -1.51111514016986312e+01, + z = -2.23307578892655734e-01, + vx = 2.96460137564761618e-03 * DAYS_PER_YEAR, + vy = 2.37847173959480950e-03 * DAYS_PER_YEAR, + vz = -2.96589568540237556e-05 * DAYS_PER_YEAR, + mass = 4.36624404335156298e-05 * SOLAR_MASS + }, + { -- Neptune + x = 1.53796971148509165e+01, + y = -2.59193146099879641e+01, + z = 1.79258772950371181e-01, + vx = 2.68067772490389322e-03 * DAYS_PER_YEAR, + vy = 1.62824170038242295e-03 * DAYS_PER_YEAR, + vz = -9.51592254519715870e-05 * DAYS_PER_YEAR, + mass = 5.15138902046611451e-05 * SOLAR_MASS + } +} + +local function advance(bodies, nbody, dt) + for i=1,nbody do + local bi = bodies[i] + local bix, biy, biz, bimass = bi.x, bi.y, bi.z, bi.mass + local bivx, bivy, bivz = bi.vx, bi.vy, bi.vz + for j=i+1,nbody do + local bj = bodies[j] + local dx, dy, dz = bix-bj.x, biy-bj.y, biz-bj.z + local mag = sqrt(dx*dx + dy*dy + dz*dz) + mag = dt / (mag * mag * mag) + local bm = bj.mass*mag + bivx = bivx - (dx * bm) + bivy = bivy - (dy * bm) + bivz = bivz - (dz * bm) + bm = bimass*mag + bj.vx = bj.vx + (dx * bm) + bj.vy = bj.vy + (dy * bm) + bj.vz = bj.vz + (dz * bm) + end + bi.vx = bivx + bi.vy = bivy + bi.vz = bivz + bi.x = bix + dt * bivx + bi.y = biy + dt * bivy + bi.z = biz + dt * bivz + end +end + +local function energy(bodies, nbody) + local e = 0 + for i=1,nbody do + local bi = bodies[i] + local vx, vy, vz, bim = bi.vx, bi.vy, bi.vz, bi.mass + e = e + (0.5 * bim * (vx*vx + vy*vy + vz*vz)) + for j=i+1,nbody do + local bj = bodies[j] + local dx, dy, dz = bi.x-bj.x, bi.y-bj.y, bi.z-bj.z + local distance = sqrt(dx*dx + dy*dy + dz*dz) + e = e - ((bim * bj.mass) / distance) + end + end + return e +end + +local function offsetMomentum(b, nbody) + local px, py, pz = 0, 0, 0 + for i=1,nbody do + local bi = b[i] + local bim = bi.mass + px = px + (bi.vx * bim) + py = py + (bi.vy * bim) + pz = pz + (bi.vz * bim) + end + b[1].vx = -px / SOLAR_MASS + b[1].vy = -py / SOLAR_MASS + b[1].vz = -pz / SOLAR_MASS +end + +local N = 20000 +local nbody = #bodies + +offsetMomentum(bodies, nbody) +io.write( string.format("%0.9f",energy(bodies, nbody)), "\n") +for i=1,N do advance(bodies, nbody, 0.01) end +io.write( string.format("%0.9f",energy(bodies, nbody)), "\n") \ No newline at end of file diff --git a/crates/rune/benches/nbodies.rn b/crates/rune/benches/nbodies.rn new file mode 100644 index 000000000..4fb24e5e0 --- /dev/null +++ b/crates/rune/benches/nbodies.rn @@ -0,0 +1,172 @@ +const PI = 3.141592653589793; +const SOLAR_MASS = 4.0 * PI * PI; +const DAYS_PER_YEAR = 365.24; + +fn advance(bodies, dt) { + for i in 0..bodies.len() { + let bi = bodies[i]; + let bix = bi.x; + let biy = bi.y; + let biz = bi.z; + let bimass = bi.mass; + let bivx = bi.vx; + let bivy = bi.vy; + let bivz = bi.vz; + + for j in i + 1..bodies.len() { + let bj = bodies[j]; + let dx = bix - bj.x; + let dy = biy - bj.y; + let dz = biz - bj.z; + let mag = (dx * dx + dy * dy + dz * dz).sqrt(); + mag = dt / (mag * mag * mag); + let bm = bj.mass * mag; + bivx = bivx - (dx * bm); + bivy = bivy - (dy * bm); + bivz = bivz - (dz * bm); + bm = bimass * mag; + bj.vx = bj.vx + (dx * bm); + bj.vy = bj.vy + (dy * bm); + bj.vz = bj.vz + (dz * bm); + } + + bi.vx = bivx; + bi.vy = bivy; + bi.vz = bivz; + bi.x = bix + dt * bivx; + bi.y = biy + dt * bivy; + bi.z = biz + dt * bivz; + } +} + +fn energy(bodies) { + let e = 0.0; + + for i in 0..bodies.len() { + let bi = bodies[i]; + let vx = bi.vx; + let vy = bi.vy; + let vz = bi.vz; + let bim = bi.mass; + + e = e + (0.5 * bim * (vx * vx + vy * vy + vz * vz)); + + for j in i + 1..bodies.len() { + let bj = bodies[j]; + let dx = bi.x - bj.x; + let dy = bi.y - bj.y; + let dz = bi.z - bj.z; + let distance = (dx * dx + dy * dy + dz * dz).sqrt(); + e = e - ((bim * bj.mass) / distance); + } + } + + e +} + +fn offset_momentum(bodies) { + let px = 0.0; + let py = 0.0; + let pz = 0.0; + + for i in 0..bodies.len() { + let bi = bodies[i]; + let bim = bi.mass; + px = px + (bi.vx * bim); + py = py + (bi.vy * bim); + pz = pz + (bi.vz * bim); + } + + bodies[0].vx = -px / SOLAR_MASS; + bodies[0].vy = -py / SOLAR_MASS; + bodies[0].vz = -pz / SOLAR_MASS; +} + +fn bodies() { + [ + // Sun + #{ + x: 0.0, + y: 0.0, + z: 0.0, + vx: 0.0, + vy: 0.0, + vz: 0.0, + mass: SOLAR_MASS, + }, + // Jupiter + #{ + x: 4.84143144246472090e+00, + y: -1.16032004402742839e+00, + z: -1.03622044471123109e-01, + vx: 1.66007664274403694e-03 * DAYS_PER_YEAR, + vy: 7.69901118419740425e-03 * DAYS_PER_YEAR, + vz: -6.90460016972063023e-05 * DAYS_PER_YEAR, + mass: 9.54791938424326609e-04 * SOLAR_MASS, + }, + // Saturn + #{ + x: 8.34336671824457987e+00, + y: 4.12479856412430479e+00, + z: -4.03523417114321381e-01, + vx: -2.76742510726862411e-03 * DAYS_PER_YEAR, + vy: 4.99852801234917238e-03 * DAYS_PER_YEAR, + vz: 2.30417297573763929e-05 * DAYS_PER_YEAR, + mass: 2.85885980666130812e-04 * SOLAR_MASS, + }, + // Uranus + #{ + x: 1.28943695621391310e+01, + y: -1.51111514016986312e+01, + z: -2.23307578892655734e-01, + vx: 2.96460137564761618e-03 * DAYS_PER_YEAR, + vy: 2.37847173959480950e-03 * DAYS_PER_YEAR, + vz: -2.96589568540237556e-05 * DAYS_PER_YEAR, + mass: 4.36624404335156298e-05 * SOLAR_MASS, + }, + // Neptune + #{ + x: 1.53796971148509165e+01, + y: -2.59193146099879641e+01, + z: 1.79258772950371181e-01, + vx: 2.68067772490389322e-03 * DAYS_PER_YEAR, + vy: 1.62824170038242295e-03 * DAYS_PER_YEAR, + vz: -9.51592254519715870e-05 * DAYS_PER_YEAR, + mass: 5.15138902046611451e-05 * SOLAR_MASS, + } + ] +} + +#[test] +pub fn nbodies_validate() { + const EXPECTED = -0.16908926275527306; + const N = 20000; + + let bodies = bodies(); + + offset_momentum(bodies); + + for i in 0..N { + advance(bodies, 0.01); + } + + let e = energy(bodies); + assert!((e - EXPECTED).abs() < 0.0000001, "{e}"); +} + +#[bench] +pub fn nbodies(b) { + const N = 20000; + + let bodies = bodies(); + + b.iter(|| { + offset_momentum(bodies); + + for i in 0..N { + advance(bodies, 0.01); + } + + energy(bodies) + }); +} diff --git a/crates/rune/src/cli/benches.rs b/crates/rune/src/cli/benches.rs index 6425dc86a..339bbd509 100644 --- a/crates/rune/src/cli/benches.rs +++ b/crates/rune/src/cli/benches.rs @@ -1,4 +1,5 @@ use std::fmt; +use std::hint; use std::io::Write; use std::path::PathBuf; use std::sync::Arc; @@ -26,7 +27,7 @@ mod cli { pub(super) warmup: u32, /// Iterations to run of the benchmark #[arg(long, default_value = "100")] - pub(super) iterations: u32, + pub(super) iter: u32, /// Explicit paths to benchmark. pub(super) bench_path: Vec, } @@ -130,18 +131,18 @@ fn bench_fn( ) -> Result<()> { for _ in 0..args.warmup { let value = f.call::(()).into_result()?; - drop(value); + drop(hint::black_box(value)); } - let iterations = usize::try_from(args.iterations).expect("iterations out of bounds"); + let iterations = usize::try_from(args.iter).expect("iterations out of bounds"); let mut collected = Vec::try_with_capacity(iterations)?; - for _ in 0..args.iterations { + for _ in 0..args.iter { let start = Instant::now(); let value = f.call::(()).into_result()?; let duration = Instant::now().duration_since(start); collected.try_push(duration.as_nanos() as i128)?; - drop(value); + drop(hint::black_box(value)); } collected.sort_unstable(); diff --git a/crates/rune/src/cli/mod.rs b/crates/rune/src/cli/mod.rs index 6b74dd50a..0292eaf8f 100644 --- a/crates/rune/src/cli/mod.rs +++ b/crates/rune/src/cli/mod.rs @@ -330,8 +330,8 @@ struct CommandSharedRef<'a> { } impl CommandSharedRef<'_> { - fn find_bins(&self) -> Option> { - if !self.command.is_workspace(AssetKind::Bin) { + fn find_bins(&self, all_targets: bool) -> Option> { + if !all_targets && !self.command.is_workspace(AssetKind::Bin) { return None; } @@ -342,8 +342,8 @@ impl CommandSharedRef<'_> { }) } - fn find_tests(&self) -> Option> { - if !self.command.is_workspace(AssetKind::Test) { + fn find_tests(&self, all_targets: bool) -> Option> { + if !all_targets && !self.command.is_workspace(AssetKind::Test) { return None; } @@ -354,8 +354,8 @@ impl CommandSharedRef<'_> { }) } - fn find_examples(&self) -> Option> { - if !self.command.is_workspace(AssetKind::Bin) { + fn find_examples(&self, all_targets: bool) -> Option> { + if !all_targets && !self.command.is_workspace(AssetKind::Bin) { return None; } @@ -366,8 +366,8 @@ impl CommandSharedRef<'_> { }) } - fn find_benches(&self) -> Option> { - if !self.command.is_workspace(AssetKind::Bench) { + fn find_benches(&self, all_targets: bool) -> Option> { + if !all_targets && !self.command.is_workspace(AssetKind::Bench) { return None; } @@ -503,6 +503,8 @@ struct Config { test: bool, /// Whether or not to use verbose output. verbose: bool, + /// Always include all targets. + all_targets: bool, /// Manifest root directory. manifest_root: Option, /// Immediate found paths. @@ -522,25 +524,25 @@ impl Config { } } - if let Some(bin) = cmd.find_bins() { + if let Some(bin) = cmd.find_bins(self.all_targets) { for p in self.manifest.find_bins(bin)? { build_paths.try_push(BuildPath::Package(p))?; } } - if let Some(test) = cmd.find_tests() { + if let Some(test) = cmd.find_tests(self.all_targets) { for p in self.manifest.find_tests(test)? { build_paths.try_push(BuildPath::Package(p))?; } } - if let Some(example) = cmd.find_examples() { + if let Some(example) = cmd.find_examples(self.all_targets) { for p in self.manifest.find_examples(example)? { build_paths.try_push(BuildPath::Package(p))?; } } - if let Some(bench) = cmd.find_benches() { + if let Some(bench) = cmd.find_benches(self.all_targets) { for p in self.manifest.find_benches(bench)? { build_paths.try_push(BuildPath::Package(p))?; } @@ -658,6 +660,11 @@ struct SharedFlags { #[arg(long)] bench: Option, + /// Include all targets, and not just the ones which are default for the + /// current command. + #[arg(long)] + all_targets: bool, + /// Build paths to include in the command. /// /// By default, the tool searches for: @@ -722,6 +729,8 @@ fn find_manifest() -> Option<(PathBuf, PathBuf)> { } fn populate_config(io: &mut Io<'_>, c: &mut Config, cmd: CommandSharedRef<'_>) -> Result<()> { + c.all_targets = cmd.shared.all_targets; + c.found_paths .try_extend(cmd.shared.path.iter().map(|p| (p.clone(), false)))?; @@ -785,7 +794,7 @@ async fn main_with_out(io: &mut Io<'_>, entry: &mut Entry<'_>, mut args: Args) - } }; - let mut entrys = alloc::Vec::new(); + let mut entries = alloc::Vec::new(); if let Some(cmd) = cmd.as_command_shared_ref() { populate_config(io, &mut c, cmd)?; @@ -800,7 +809,7 @@ async fn main_with_out(io: &mut Io<'_>, entry: &mut Entry<'_>, mut args: Args) - match build_path { BuildPath::Path(path, explicit) => { for path in loader::recurse_paths(recursive, path.try_to_owned()?) { - entrys.try_push(EntryPoint::Path(path?, explicit))?; + entries.try_push(EntryPoint::Path(path?, explicit))?; } } BuildPath::Package(p) => { @@ -820,13 +829,13 @@ async fn main_with_out(io: &mut Io<'_>, entry: &mut Entry<'_>, mut args: Args) - )?; } - entrys.try_push(EntryPoint::Package(p))?; + entries.try_push(EntryPoint::Package(p))?; } } } } - match run_path(io, &c, cmd, entry, entrys).await? { + match run_path(io, &c, cmd, entry, entries).await? { ExitCode::Success => (), other => { return Ok(other); diff --git a/crates/rune/src/modules/object.rs b/crates/rune/src/modules/object.rs index dcb9c0d4c..68f676a1b 100644 --- a/crates/rune/src/modules/object.rs +++ b/crates/rune/src/modules/object.rs @@ -135,7 +135,7 @@ fn get(object: &Object, key: &str) -> Option { } #[rune::function(keep, instance, protocol = PARTIAL_EQ)] -fn partial_eq(this: &Object, other: Value) -> VmResult { +fn partial_eq(this: &Object, other: &Object) -> VmResult { Object::partial_eq_with(this, other, &mut EnvProtocolCaller) } diff --git a/crates/rune/src/runtime/object.rs b/crates/rune/src/runtime/object.rs index 794ff0d6e..b4421b7ee 100644 --- a/crates/rune/src/runtime/object.rs +++ b/crates/rune/src/runtime/object.rs @@ -363,31 +363,23 @@ impl Object { pub(crate) fn partial_eq_with( a: &Self, - b: Value, + b: &Self, caller: &mut dyn ProtocolCaller, ) -> VmResult { - let mut b = vm_try!(b.into_iter()); + if a.len() != b.len() { + return VmResult::Ok(false); + } for (k1, v1) in a.iter() { - let Some(value) = vm_try!(b.next()) else { + let Some(v2) = b.get(k1) else { return VmResult::Ok(false); }; - let (k2, v2) = vm_try!(<(Ref, Value)>::from_value(value)); - - if k1 != &*k2 { - return VmResult::Ok(false); - } - if !vm_try!(Value::partial_eq_with(v1, &v2, caller)) { return VmResult::Ok(false); } } - if vm_try!(b.next()).is_some() { - return VmResult::Ok(false); - } - VmResult::Ok(true) } diff --git a/crates/rune/src/runtime/value.rs b/crates/rune/src/runtime/value.rs index 903379f1a..41e199f39 100644 --- a/crates/rune/src/runtime/value.rs +++ b/crates/rune/src/runtime/value.rs @@ -1320,9 +1320,6 @@ impl Value { Mutable::Tuple(a) => { return Vec::partial_eq_with(a, b.clone(), caller); } - Mutable::Object(a) => { - return Object::partial_eq_with(a, b.clone(), caller); - } _ => {} } }