Skip to content

Commit 927120c

Browse files
efaulhabersvchb
andauthored
Add benchmarks (#18)
* Add benchmarks * Add benchmarks to CI tests * Add benchmarks to tests * Add test dependencies * Fix typo * Add `PrecomputedNeighborhoodSearch` to benchmark plot * Fix typo * Migrate to v0.3 * Reformat * Fix benchmarks --------- Co-authored-by: Sven Berger <[email protected]>
1 parent f4109dd commit 927120c

File tree

9 files changed

+200
-4
lines changed

9 files changed

+200
-4
lines changed

.github/workflows/ci.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,14 @@ jobs:
6767
- uses: julia-actions/cache@v2
6868
- name: Build package
6969
uses: julia-actions/julia-buildpkg@v1
70-
- name: Run tests
70+
- name: Run unit tests
7171
uses: julia-actions/julia-runtest@v1
7272
with:
7373
annotate: true
7474
# Only run coverage in one Job (Ubuntu and latest Julia version)
7575
coverage: ${{ matrix.os == 'ubuntu-latest' && matrix.version == '1' }}
76+
env:
77+
POINTNEIGHBORS_TEST: unit
7678
- name: Process coverage results
7779
# Only run coverage in one Job (Ubuntu and latest Julia version)
7880
if: matrix.os == 'ubuntu-latest' && matrix.version == '1'
@@ -89,3 +91,11 @@ jobs:
8991
flags: unit
9092
env:
9193
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
94+
- name: Run benchmark tests
95+
uses: julia-actions/julia-runtest@v1
96+
with:
97+
annotate: true
98+
coverage: false
99+
env:
100+
POINTNEIGHBORS_TEST: benchmarks
101+

benchmarks/benchmarks.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
include("count_neighbors.jl")
2+
include("n_body.jl")
3+
4+
include("plot.jl")

benchmarks/count_neighbors.jl

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using PointNeighbors
2+
using BenchmarkTools
3+
4+
"""
5+
benchmark_count_neighbors(neighborhood_search, coordinates; parallel = true)
6+
7+
A very cheap and simple neighborhood search benchmark, only counting the neighbors of each
8+
point. For each point-neighbor pair, only an array entry is incremented.
9+
10+
Due to the minimal computational cost, differences between neighborhood search
11+
implementations are highlighted. On the other hand, this is the least realistic benchmark.
12+
13+
For a computationally heavier benchmark, see [`benchmark_n_body`](@ref).
14+
"""
15+
function benchmark_count_neighbors(neighborhood_search, coordinates; parallel = true)
16+
n_neighbors = zeros(Int, size(coordinates, 2))
17+
18+
function count_neighbors!(n_neighbors, coordinates, neighborhood_search, parallel)
19+
n_neighbors .= 0
20+
21+
foreach_point_neighbor(coordinates, coordinates, neighborhood_search,
22+
parallel = parallel) do i, _, _, _
23+
n_neighbors[i] += 1
24+
end
25+
end
26+
27+
return @belapsed $count_neighbors!($n_neighbors, $coordinates,
28+
$neighborhood_search, $parallel)
29+
end

benchmarks/n_body.jl

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using PointNeighbors
2+
using BenchmarkTools
3+
4+
"""
5+
benchmark_n_body(neighborhood_search, coordinates; parallel = true)
6+
7+
A simple neighborhood search benchmark, computing the right-hand side of an n-body
8+
simulation with a cutoff (corresponding to the search radius of `neighborhood_search`).
9+
10+
This is a more realistic benchmark for particle-based simulations than
11+
[`benchmark_count_neighbors`](@ref).
12+
However, due to the higher computational cost, differences between neighborhood search
13+
implementations are less pronounced.
14+
"""
15+
function benchmark_n_body(neighborhood_search, coordinates; parallel = true)
16+
mass = 1e10 * (rand(size(coordinates, 2)) .+ 1)
17+
G = 6.6743e-11
18+
19+
dv = similar(coordinates)
20+
21+
function compute_acceleration!(dv, coordinates, mass, G, neighborhood_search, parallel)
22+
dv .= 0.0
23+
24+
foreach_point_neighbor(coordinates, coordinates, neighborhood_search,
25+
parallel = parallel) do i, j, pos_diff, distance
26+
# Only consider particles with a distance > 0
27+
distance < sqrt(eps()) && return
28+
29+
dv_ = -G * mass[j] * pos_diff / distance^3
30+
31+
for dim in axes(dv, 1)
32+
@inbounds dv[dim, i] += dv_[dim]
33+
end
34+
end
35+
36+
return dv
37+
end
38+
39+
return @belapsed $compute_acceleration!($dv, $coordinates, $mass, $G,
40+
$neighborhood_search, $parallel)
41+
end

benchmarks/plot.jl

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using Plots
2+
using BenchmarkTools
3+
4+
# Generate a rectangular point cloud
5+
include("../test/point_cloud.jl")
6+
7+
"""
8+
plot_benchmarks(benchmark, n_points_per_dimension, iterations;
9+
seed = 1, perturbation_factor_position = 1.0,
10+
parallel = true, title = "")
11+
12+
Run a benchmark for with several neighborhood searches multiple times for increasing numbers
13+
of points and plot the results.
14+
15+
# Arguments
16+
- `benchmark`: The benchmark function. See [`benchmark_count_neighbors`](@ref)
17+
and [`benchmark_n_body`](@ref).
18+
- `n_points_per_dimension`: Initial resolution as tuple. The product is the initial number
19+
of points. For example, use `(100, 100)` for a 2D benchmark or
20+
`(10, 10, 10)` for a 3D benchmark.
21+
- `iterations`: Number of refinement iterations
22+
23+
# Keywords
24+
- `parallel = true`: Loop over all points in parallel
25+
- `title = ""`: Title of the plot
26+
- `seed = 1`: Seed to perturb the point positions. Different seeds yield
27+
slightly different point positions.
28+
- `perturbation_factor_position = 1.0`: Perturb point positions by this factor. A factor of
29+
`1.0` corresponds to points being moved by
30+
a maximum distance of `0.5` along each axis.
31+
"""
32+
function plot_benchmarks(benchmark, n_points_per_dimension, iterations;
33+
parallel = true, title = "",
34+
seed = 1, perturbation_factor_position = 1.0)
35+
neighborhood_searches_names = ["TrivialNeighborhoodSearch";;
36+
"GridNeighborhoodSearch";;
37+
"PrecomputedNeighborhoodSearch"]
38+
39+
# Multiply number of points in each iteration (roughly) by this factor
40+
scaling_factor = 4
41+
per_dimension_factor = scaling_factor^(1 / length(n_points_per_dimension))
42+
sizes = [round.(Int, n_points_per_dimension .* per_dimension_factor^(iter - 1))
43+
for iter in 1:iterations]
44+
45+
n_particles_vec = prod.(sizes)
46+
times = zeros(iterations, length(neighborhood_searches_names))
47+
48+
for iter in 1:iterations
49+
coordinates = point_cloud(sizes[iter], seed = seed,
50+
perturbation_factor_position = perturbation_factor_position)
51+
52+
search_radius = 3.0
53+
NDIMS = size(coordinates, 1)
54+
n_particles = size(coordinates, 2)
55+
56+
neighborhood_searches = [
57+
TrivialNeighborhoodSearch{NDIMS}(; search_radius, eachpoint = 1:n_particles),
58+
GridNeighborhoodSearch{NDIMS}(; search_radius, n_points = n_particles),
59+
PrecomputedNeighborhoodSearch{NDIMS}(; search_radius, n_points = n_particles),
60+
]
61+
62+
for i in eachindex(neighborhood_searches)
63+
neighborhood_search = neighborhood_searches[i]
64+
initialize!(neighborhood_search, coordinates, coordinates)
65+
66+
time = benchmark(neighborhood_search, coordinates, parallel = parallel)
67+
times[iter, i] = time
68+
time_string = BenchmarkTools.prettytime(time * 1e9)
69+
println("$(neighborhood_searches_names[i])")
70+
println("with $(join(sizes[iter], "x")) = $(prod(sizes[iter])) particles finished in $time_string\n")
71+
end
72+
end
73+
74+
plot(n_particles_vec, times,
75+
xaxis = :log, yaxis = :log,
76+
xticks = (n_particles_vec, n_particles_vec),
77+
xlabel = "#particles", ylabel = "Runtime [s]",
78+
legend = :outerright, size = (750, 400), dpi = 200,
79+
label = neighborhood_searches_names, title = title)
80+
end

test/Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
[deps]
2+
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
3+
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
24
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
35
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
46

test/benchmarks.jl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Check that all benchmarks are running without errors.
2+
# Note that these are only smoke tests, not verifying the result.
3+
# Also note that these tests are run without coverage checks, since we want to
4+
# cover everything with unit tests.
5+
@testset verbose=true "Benchmarks" begin
6+
include("../benchmarks/benchmarks.jl")
7+
8+
@testset verbose=true "$(length(size))D" for size in [(50,), (10, 10), (5, 5, 5)]
9+
@testset verbose=true "`benchmark_count_neighbors`" begin
10+
@test_nowarn_mod plot_benchmarks(benchmark_count_neighbors, size, 2)
11+
end
12+
13+
@testset verbose=true "`benchmark_n_body`" begin
14+
@test_nowarn_mod plot_benchmarks(benchmark_n_body, size, 2)
15+
end
16+
end
17+
end;

test/runtests.jl

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
include("test_util.jl")
22

3+
const POINTNEIGHBORS_TEST = lowercase(get(ENV, "POINTNEIGHBORS_TEST", "all"))
4+
35
@testset verbose=true "PointNeighbors.jl Tests" begin
4-
include("nhs_trivial.jl")
5-
include("nhs_grid.jl")
6-
include("neighborhood_search.jl")
6+
if POINTNEIGHBORS_TEST in ("all", "unit")
7+
include("unittest.jl")
8+
end
9+
10+
if POINTNEIGHBORS_TEST in ("all", "benchmarks")
11+
include("benchmarks.jl")
12+
end
713
end;

test/unittest.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Separate file that can be executed to only run unit tests.
2+
# Include `test_util.jl` first.
3+
@testset verbose=true "Unit Tests" begin
4+
include("nhs_trivial.jl")
5+
include("nhs_grid.jl")
6+
include("neighborhood_search.jl")
7+
end;

0 commit comments

Comments
 (0)