diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml new file mode 100644 index 00000000..e4407c9e --- /dev/null +++ b/.github/workflows/bench.yml @@ -0,0 +1,88 @@ +name: Bench +on: + pull_request: + branches: [ main ] + push: + branches: [ main ] + +env: + RUST_NIGHTLY_TOOLCHAIN: nightly-2024-12-18 + RUSTFLAGS: -Clinker=/usr/bin/clang -Clink-arg=--ld-path=/usr/local/bin/mold -Ctarget-cpu=native -Zshare-generics=y -Zthreads=0 + +jobs: + export: + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + strategy: + matrix: + benchmark: [ general ] # Add your benchmarks here + steps: + - uses: actions/checkout@v4 + - uses: rui314/setup-mold@v1 + - name: Install cargo-export + uses: taiki-e/install-action@v2 + with: + tool: cargo-export + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.RUST_NIGHTLY_TOOLCHAIN }} + - uses: Swatinem/rust-cache@v2 + - name: Run cargo export + run: cargo export target/benchmarks -- bench --bench=${{ matrix.benchmark }} + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: bench_${{ matrix.benchmark }} + path: target/benchmarks/${{ matrix.benchmark }} + + compare: + if: github.event_name == 'pull_request' && github.base_ref == 'main' + runs-on: ubuntu-latest + strategy: + matrix: + benchmark: [ general ] # Add your benchmarks here + permissions: + pull-requests: write + steps: + - uses: actions/checkout@v4 + - name: Download Artifact + id: download + uses: dawidd6/action-download-artifact@v3 + continue-on-error: true + with: + commit: ${{ github.event.pull_request.base.sha }} + name: bench_${{ matrix.benchmark }} + path: /tmp/bench/ + - uses: rui314/setup-mold@v1 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.RUST_NIGHTLY_TOOLCHAIN }} + - uses: Swatinem/rust-cache@v2 + - name: Run cargo bench + if: steps.download.outcome == 'success' + run: cargo bench --bench=${{ matrix.benchmark }} -- compare /tmp/bench/${{ matrix.benchmark }} > bench_output_${{ matrix.benchmark }}.txt + - name: remove /tmp/bench + if: steps.download.outcome == 'success' + run: rm -rf /tmp/bench + - name: Post benchmark results as PR comment + if: steps.download.outcome == 'success' + uses: actions/github-script@v6 + with: + script: | + const fs = require('fs'); + const path = `bench_output_${{ matrix.benchmark }}.txt`; + const output = fs.readFileSync(path, 'utf8'); + const originalSha = context.payload.pull_request.base.sha; + + const issue_number = context.issue.number; + if(issue_number) { + const comment = { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue_number, + body: `### Benchmark Results for ${{ matrix.benchmark }}\n\`\`\`\n${output}\`\`\`\n\nComparing to ${originalSha}`, + }; + await github.rest.issues.createComment(comment); + } else { + console.log('This action must be run in the context of a pull request'); + } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index ded22629..297183eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -175,6 +175,15 @@ dependencies = [ "equator", ] +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -1210,6 +1219,15 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "colorz" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceb37c5798821e37369cb546f430f19da2f585e0364c9615ae340a9f2e6067b" +dependencies = [ + "supports-color", +] + [[package]] name = "com" version = "0.6.0" @@ -2287,7 +2305,9 @@ dependencies = [ "approx", "glam", "ordered-float", + "rand", "serde", + "tango-bench", ] [[package]] @@ -2355,6 +2375,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "glob-match" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985c9503b412198aa4197559e9a318524ebc4519c229bfa05a535828c950b9d" + [[package]] name = "glow" version = "0.13.1" @@ -2445,6 +2471,17 @@ dependencies = [ "gl_generator", ] +[[package]] +name = "goblin" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27c1b4369c2cd341b5de549380158b105a04c331be5db9110eef7b6d2742134" +dependencies = [ + "log", + "plain", + "scroll", +] + [[package]] name = "gpu-alloc" version = "0.6.0" @@ -3307,6 +3344,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "is_ci" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -4476,6 +4519,12 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "plist" version = "1.7.0" @@ -5239,6 +5288,26 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scroll" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.91", +] + [[package]] name = "sctk-adwaita" version = "0.10.1" @@ -5587,6 +5656,15 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "supports-color" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" +dependencies = [ + "is_ci", +] + [[package]] name = "syn" version = "1.0.109" @@ -5728,6 +5806,27 @@ dependencies = [ "valence_server", ] +[[package]] +name = "tango-bench" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "257822358c6f206fed78bfe6369cf959063b0644d70f88df6b19f2dadc93423e" +dependencies = [ + "alloca", + "anyhow", + "clap", + "colorz", + "glob-match", + "goblin", + "libloading", + "log", + "num-traits", + "rand", + "scroll", + "tempfile", + "thiserror 1.0.69", +] + [[package]] name = "tap" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index 728d2ec4..03d6defc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,6 +109,7 @@ serde_json = '1.0.117' slotmap = '1.0.7' snafu = '0.8.5' syn = '2.0.87' +tango-bench = "0.6.0" tar = '0.4.41' thiserror = '2.0.7' tikv-jemallocator = '0.6.0' diff --git a/crates/geometry/Cargo.toml b/crates/geometry/Cargo.toml index fd2d76b8..0cd9d9ef 100644 --- a/crates/geometry/Cargo.toml +++ b/crates/geometry/Cargo.toml @@ -13,6 +13,12 @@ ordered-float = "4.5.0" [dev-dependencies] approx = { workspace = true } +rand = { workspace = true } +tango-bench = { workspace = true } [lints] workspace = true + +[[bench]] +name = "general" +harness = false diff --git a/crates/geometry/benches/general.rs b/crates/geometry/benches/general.rs new file mode 100644 index 00000000..8c245677 --- /dev/null +++ b/crates/geometry/benches/general.rs @@ -0,0 +1,176 @@ +use std::hint::black_box; + +use geometry::{aabb::Aabb, ray::Ray}; +use glam::Vec3; +use rand::{Rng, SeedableRng, rngs::SmallRng}; +use tango_bench::{ + DEFAULT_SETTINGS, IntoBenchmarks, MeasurementSettings, benchmark_fn, tango_benchmarks, + tango_main, +}; + +// Helper function to generate random AABBs +fn random_aabb(rng: &mut SmallRng) -> Aabb { + let x = rng.gen_range(-10.0..10.0); + let y = rng.gen_range(-10.0..10.0); + let z = rng.gen_range(-10.0..10.0); + + let width = rng.gen_range(0.1..5.0); + let height = rng.gen_range(0.1..5.0); + let depth = rng.gen_range(0.1..5.0); + + Aabb::new( + Vec3::new(x, y, z), + Vec3::new(x + width, y + height, z + depth), + ) +} + +// Helper function to generate random rays +fn random_ray(rng: &mut SmallRng) -> Ray { + let origin = Vec3::new( + rng.gen_range(-15.0..15.0), + rng.gen_range(-15.0..15.0), + rng.gen_range(-15.0..15.0), + ); + + // Generate random direction and normalize + let direction = Vec3::new( + rng.gen_range(-1.0..1.0), + rng.gen_range(-1.0..1.0), + rng.gen_range(-1.0..1.0), + ) + .normalize(); + + Ray::new(origin, direction) +} + +fn ray_intersection_benchmarks() -> impl IntoBenchmarks { + let mut benchmarks = Vec::new(); + + // Test different AABB sizes + for &size in &[0.1, 1.0, 10.0] { + benchmarks.push(benchmark_fn( + format!("ray_intersection/aabb_size_{size}"), + move |b| { + let mut rng = SmallRng::seed_from_u64(b.seed); + let aabb = Aabb::new(Vec3::new(-size, -size, -size), Vec3::new(size, size, size)); + b.iter(move || { + let ray = random_ray(&mut rng); + black_box(aabb.intersect_ray(&ray)) + }) + }, + )); + } + + // Test rays from different positions + for &distance in &[1.0, 5.0, 20.0] { + benchmarks.push(benchmark_fn( + format!("ray_intersection/ray_distance_{distance}"), + move |b| { + let aabb = Aabb::new(Vec3::new(-1.0, -1.0, -1.0), Vec3::new(1.0, 1.0, 1.0)); + b.iter(move || { + let origin = Vec3::new(distance, 0.0, 0.0); + let ray = Ray::new(origin, -origin.normalize()); + black_box(aabb.intersect_ray(&ray)) + }) + }, + )); + } + + benchmarks +} + +fn overlap_benchmarks() -> impl IntoBenchmarks { + let mut benchmarks = Vec::new(); + + // Test different overlap scenarios + benchmarks.push(benchmark_fn("overlap/no_overlap", move |b| { + let mut rng = SmallRng::seed_from_u64(b.seed); + let aabb1 = Aabb::new(Vec3::ZERO, Vec3::ONE); + b.iter(move || { + let aabb2 = random_aabb(&mut rng).move_by(Vec3::new(2.0, 2.0, 2.0)); + black_box(Aabb::overlap(&aabb1, &aabb2)) + }) + })); + + benchmarks.push(benchmark_fn("overlap/partial_overlap", move |b| { + let mut rng = SmallRng::seed_from_u64(b.seed); + let aabb1 = Aabb::new(Vec3::ZERO, Vec3::ONE); + b.iter(move || { + let aabb2 = random_aabb(&mut rng).move_by(Vec3::new(0.5, 0.5, 0.5)); + black_box(Aabb::overlap(&aabb1, &aabb2)) + }) + })); + + benchmarks.push(benchmark_fn("overlap/full_containment", move |b| { + let mut rng = SmallRng::seed_from_u64(b.seed); + let aabb1 = Aabb::new(Vec3::splat(-2.0), Vec3::splat(2.0)); + b.iter(move || { + let aabb2 = random_aabb(&mut rng); + black_box(Aabb::overlap(&aabb1, &aabb2)) + }) + })); + + benchmarks +} + +fn point_containment_benchmarks() -> impl IntoBenchmarks { + let mut benchmarks = Vec::new(); + + benchmarks.push(benchmark_fn("point_containment/inside", move |b| { + let mut rng = SmallRng::seed_from_u64(b.seed); + let aabb = Aabb::new(Vec3::splat(-1.0), Vec3::splat(1.0)); + b.iter(move || { + let point = Vec3::new( + rng.gen_range(-0.9..0.9), + rng.gen_range(-0.9..0.9), + rng.gen_range(-0.9..0.9), + ); + black_box(aabb.contains_point(point)) + }) + })); + + benchmarks.push(benchmark_fn("point_containment/outside", move |b| { + let mut rng = SmallRng::seed_from_u64(b.seed); + let aabb = Aabb::new(Vec3::splat(-1.0), Vec3::splat(1.0)); + b.iter(move || { + let point = Vec3::new( + rng.gen_range(1.1..2.0), + rng.gen_range(1.1..2.0), + rng.gen_range(1.1..2.0), + ); + black_box(aabb.contains_point(point)) + }) + })); + + benchmarks.push(benchmark_fn("point_containment/boundary", move |b| { + let mut rng = SmallRng::seed_from_u64(b.seed); + let aabb = Aabb::new(Vec3::splat(-1.0), Vec3::splat(1.0)); + b.iter(move || { + // Generate points very close to the boundary + let point = Vec3::new( + rng.gen_range(-1.001..1.001), + rng.gen_range(-1.001..1.001), + rng.gen_range(-1.001..1.001), + ); + black_box(aabb.contains_point(point)) + }) + })); + + benchmarks +} + +// Custom settings for more stable results +const SETTINGS: MeasurementSettings = MeasurementSettings { + min_iterations_per_sample: 1000, + cache_firewall: Some(64), // 64KB cache firewall + yield_before_sample: true, + randomize_stack: Some(4096), // 4KB stack randomization + ..DEFAULT_SETTINGS +}; + +tango_benchmarks!( + ray_intersection_benchmarks(), + overlap_benchmarks(), + point_containment_benchmarks() +); +tango_main!(SETTINGS); diff --git a/crates/geometry/build.rs b/crates/geometry/build.rs new file mode 100644 index 00000000..a79d6753 --- /dev/null +++ b/crates/geometry/build.rs @@ -0,0 +1,5 @@ +fn main() { + // for tango-bench + println!("cargo:rustc-link-arg-benches=-rdynamic"); + println!("cargo:rerun-if-changed=build.rs"); +}