Skip to content

Commit

Permalink
Improve iter benchmark
Browse files Browse the repository at this point in the history
This adds a mandelbrot set rendering test.
  • Loading branch information
rj00a committed Apr 15, 2024
1 parent 6a2f784 commit a54298b
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 51 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ slab = "0.4.9"

[dev-dependencies]
ahash = "0.8.7"
bevy_ecs = { version = "0.13.0", features = ["multi-threaded"] }
bevy_tasks = "0.13.0"
bevy_ecs = { version = "0.13.2", features = ["multi-threaded"] }
bevy_tasks = "0.13.2"
divan = "0.1.11"
rand = "0.8.5"

Expand Down
162 changes: 113 additions & 49 deletions benches/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ fn main() {
}

const DATA: f64 = 123456789.0;
const ARGS: [usize; 7] = [1, 10, 100, 1_000, 10_000, 100_000, 1_000_000];
const LENS: [usize; 7] = [1, 10, 100, 1_000, 10_000, 100_000, 1_000_000];

#[derive(evenio::component::Component, bevy_ecs::component::Component)]
struct C1(f64);
Expand All @@ -21,9 +21,11 @@ struct C3(#[allow(dead_code)] f64);
#[derive(evenio::event::Event)]
struct E;

#[divan::bench(args = ARGS)]
fn iter_evenio(bencher: Bencher, len: usize) {
#[cfg(feature = "rayon")]
#[divan::bench(args = LENS, consts = [false, true])]
fn iter_simple_evenio<const PARALLEL: bool>(bencher: Bencher, len: usize) {
use evenio::prelude::*;
use evenio::rayon::prelude::*;

let mut world = World::new();

Expand All @@ -42,17 +44,28 @@ fn iter_evenio(bencher: Bencher, len: usize) {
}

world.add_handler(move |_: Receiver<E>, f: Fetcher<&mut C1>| {
for c in f {
c.0 = c.0.sqrt();
if PARALLEL {
f.into_par_iter().for_each(|c| {
c.0 = c.0.sqrt();
});
} else {
f.into_iter().for_each(|c| {
c.0 = c.0.sqrt();
});
}
});

bencher.bench_local(|| world.send(E));
}

#[divan::bench(args = ARGS)]
fn iter_bevy(bencher: Bencher, len: usize) {
#[divan::bench(args = LENS, consts = [false, true])]
fn iter_simple_bevy<const PARALLEL: bool>(bencher: Bencher, len: usize) {
use bevy_ecs::prelude::*;
use bevy_tasks::{ComputeTaskPool, TaskPool};

if PARALLEL {
ComputeTaskPool::get_or_init(TaskPool::new);
}

let mut world = World::new();

Expand All @@ -71,76 +84,127 @@ fn iter_bevy(bencher: Bencher, len: usize) {
let mut state = world.query::<&mut C1>();

bencher.bench_local(|| {
for mut c in state.iter_mut(&mut world) {
c.bypass_change_detection().0 = c.0.sqrt();
if PARALLEL {
state.par_iter_mut(&mut world).for_each(|mut c| {
c.bypass_change_detection().0 = c.0.sqrt();
});
} else {
state.iter_mut(&mut world).for_each(|mut c| {
c.bypass_change_detection().0 = c.0.sqrt();
});
}
});
}

// TODO: iteration with high fragmentation.
const ITERS_PER_PIXEL: [usize; 11] = [1, 2, 3, 8, 16, 32, 64, 128, 256, 512, 1024];
const IMAGE_SIZE: u16 = 100;

#[derive(evenio::component::Component, bevy_ecs::component::Component)]
struct Pixel(u16, u16);

#[derive(evenio::component::Component, bevy_ecs::component::Component)]
struct Output(bool);

#[cfg(feature = "rayon")]
#[divan::bench(args = ARGS)]
fn par_iter_evenio(bencher: Bencher, len: usize) {
#[divan::bench(args = ITERS_PER_PIXEL, consts = [true])]
fn iter_mandelbrot_evenio<const PARALLEL: bool>(bencher: Bencher, iters: usize) {
use evenio::prelude::*;
use rayon::prelude::*;

#[derive(Event)]
struct E;
use evenio::rayon::prelude::*;

let mut world = World::new();

for i in 0..len {
let e = world.spawn();

world.insert(e, C1(42.0));

if i % 2 == 0 {
world.insert(e, C2(42.0));
}

if i % 3 == 0 {
world.insert(e, C3(42.0));
for y in 0..IMAGE_SIZE {
for x in 0..IMAGE_SIZE {
let e = world.spawn();
world.insert(e, Pixel(x, y));
world.insert(e, Output(false));
}
}

world.add_handler(move |_: Receiver<E>, f: Fetcher<&mut C1>| {
f.into_par_iter().for_each(|c| {
c.0 = c.0.sqrt();
});
world.add_handler(move |_: Receiver<E>, f: Fetcher<(&Pixel, &mut Output)>| {
if PARALLEL {
f.into_par_iter().for_each(|(&Pixel(x, y), out)| {
out.0 = mandelbrot(x, y, iters);
});
} else {
f.into_iter().for_each(|(&Pixel(x, y), out)| {
out.0 = mandelbrot(x, y, iters);
});
}
});

bencher.bench_local(|| world.send(E));
bencher.bench_local(|| {
world.send(E);
});
}

#[divan::bench(args = ARGS)]
fn par_iter_bevy(bencher: Bencher, len: usize) {
#[divan::bench(args = ITERS_PER_PIXEL, consts = [true])]
fn iter_mandelbrot_bevy<const PARALLEL: bool>(bencher: Bencher, iters: usize) {
use bevy_ecs::prelude::*;
use bevy_tasks::{ComputeTaskPool, TaskPool};

ComputeTaskPool::get_or_init(TaskPool::new);
if PARALLEL {
ComputeTaskPool::get_or_init(TaskPool::new);
}

let mut world = World::new();

for i in 0..len {
let mut e = world.spawn(C1(DATA));

if i % 2 == 0 {
e.insert(C2(DATA));
}

if i % 3 == 0 {
e.insert(C3(DATA));
for y in 0..IMAGE_SIZE {
for x in 0..IMAGE_SIZE {
world.spawn((Pixel(x, y), Output(false)));
}
}

let mut state = world.query::<&mut C1>();
let mut state = world.query::<(&Pixel, &mut Output)>();

bencher.bench_local(|| {
state.par_iter_mut(&mut world).for_each(|mut c| {
c.bypass_change_detection().0 = c.0.sqrt();
});
if PARALLEL {
state
.par_iter_mut(&mut world)
.for_each(move |(&Pixel(x, y), mut out)| {
out.bypass_change_detection().0 = mandelbrot(x, y, iters);
});
} else {
state
.iter_mut(&mut world)
.for_each(|(&Pixel(x, y), mut out)| {
out.bypass_change_detection().0 = mandelbrot(x, y, iters);
});
}
});
}

// TODO: parallel iteration with varying amounts of work per iteration.
#[inline]
fn mandelbrot(x: u16, y: u16, iters: usize) -> bool {
let scale = 2.;

let cr = x as f64 / (IMAGE_SIZE - 1) as f64 * scale - scale / 2.;

Check failure on line 181 in benches/iter.rs

View workflow job for this annotation

GitHub Actions / Clippy

casting `u16` to `f64` may become silently lossy if you later change the type

Check failure on line 181 in benches/iter.rs

View workflow job for this annotation

GitHub Actions / Clippy

casting `u16` to `f64` may become silently lossy if you later change the type
let ci = y as f64 / (IMAGE_SIZE - 1) as f64 * scale - scale / 2.;

Check failure on line 182 in benches/iter.rs

View workflow job for this annotation

GitHub Actions / Clippy

casting `u16` to `f64` may become silently lossy if you later change the type

Check failure on line 182 in benches/iter.rs

View workflow job for this annotation

GitHub Actions / Clippy

casting `u16` to `f64` may become silently lossy if you later change the type

let mut zr = cr;
let mut zi = ci;

for _ in 0..iters {
if zr * zr + zi * zi >= 4. {
return false;
}

(zr, zi) = (zr * zr - zi * zi + cr, 2. * zr * zi + ci);
}

true
}

#[allow(unused)]
fn make_mandel_pgm(iters: usize) -> std::io::Result<()> {
let mut pgm = format!("P2 {0} {0} 1\n", IMAGE_SIZE);

Check failure on line 200 in benches/iter.rs

View workflow job for this annotation

GitHub Actions / Clippy

variables can be used directly in the `format!` string

for y in 0..IMAGE_SIZE {
for x in 0..IMAGE_SIZE {
pgm += if mandelbrot(x, y, iters) { " 1" } else { " 0" };
}
pgm += "\n";
}

std::fs::write("mandel.pgm", pgm)
}

0 comments on commit a54298b

Please sign in to comment.