Edit: passed weights in the benchmark
Here are the results:
This is run on weighted grids, PriorityQueue use priority updates, while the three other data structures donβt. As expected, Priority queue behave similarly as the Graphs.jl implementation, while the SortedVectorPriorityQueue
and HeapPriorityQueue
are a little bit faster. VectorPriorityQueue
just seems not worth it.
Here is my code, based on the actual benchmark of FastPriorityQueues
:
using FastPriorityQueues
using BenchmarkTools #src
using DataStructures
using FastPriorityQueues
using Graphs
using Plots
using Test
using SparseArrays
using LinearAlgebra
using Random
function dijkstra_priority_updates!(
q::Q, g::AbstractGraph{T}, s::Integer, w=weights(g)
) where {Q,T}
d = fill(Inf, nv(g))
d[s] = 0.
enqueue!(q, s, 0.)
while !isempty(q)
u, k = dequeue_pair!(q)
d[u] = k
for v in outneighbors(g, u)
if d[u] + w[u, v] < d[v]
d[v] = d[u] + w[u, v]
d[v] == Inf ? enqueue!(q, v, d[v]) : q[v] = d[v]
end
end
end
return d
end
function dijkstra_no_priority_updates!(
q::Q, g::AbstractGraph{T}, s::Integer, w=weights(g)
) where {Q,T}
d = fill(Inf, nv(g))
d[s] = 0.0
enqueue!(q, s, 0.0)
while !isempty(q)
u, d_u = dequeue_pair!(q)
if d_u <= d[u]
d[u] = d_u
for v in outneighbors(g, u)
d_v = d[u] + w[u, v]
if d_v < d[v]
enqueue!(q, v, d_v)
d[v] = d_v
end
end
end
end
return d
end
function random_weights(g)
srcs = src.(edges(g))
dsts = dst.(edges(g))
rng = Xoshiro(1234);
w = rand(rng, ne(g))
Symmetric(sparse(srcs, dsts, w, ne(g), ne(g))) # assuming src < dst
end
test_dijkstra_default(g, w=weights(g)) = Graphs.dijkstra_shortest_paths(g, 1, w).dists[end];
test_dijkstra_priority_updates(g, qtype, w=weights(g)) = dijkstra_priority_updates!(qtype(), g, 1, w)[end];
test_dijkstra_no_priority_updates(g, qtype, w=weights(g)) = dijkstra_no_priority_updates!(qtype(), g, 1, w)[end];
g_small = Graphs.grid([10, 10])
w_small = random_weights(g_small)
d1 = test_dijkstra_default(g_small);
d2 = test_dijkstra_priority_updates(g_small, PriorityQueue{Int,Float64});
d3 = test_dijkstra_no_priority_updates(g_small, VectorPriorityQueue{Int,Float64});
d4 = test_dijkstra_no_priority_updates(g_small, SortedVectorPriorityQueue{Int,Float64});
d5 = test_dijkstra_no_priority_updates(g_small, HeapPriorityQueue{Int,Float64});
@test d1 β d2 β d3 β d4 β d5
d1 = test_dijkstra_default(g_small, w_small);
d2 = test_dijkstra_priority_updates(g_small, PriorityQueue{Int,Float64}, w_small);
d3 = test_dijkstra_no_priority_updates(g_small, VectorPriorityQueue{Int,Float64}, w_small);
d4 = test_dijkstra_no_priority_updates(g_small, SortedVectorPriorityQueue{Int,Float64}, w_small);
d5 = test_dijkstra_no_priority_updates(g_small, HeapPriorityQueue{Int,Float64}, w_small);
@test d1 β d2 β d3 β d4 β d5
function compare_dijkstra_versions(n_values, weighted=true)
speed_gains = Dict(
"PriorityQueue" => Float64[],
"VectorPriorityQueue" => Float64[],
"SortedVectorPriorityQueue" => Float64[],
"HeapPriorityQueue" => Float64[],
)
memory_gains = Dict(
"PriorityQueue" => Float64[],
"VectorPriorityQueue" => Float64[],
"SortedVectorPriorityQueue" => Float64[],
"HeapPriorityQueue" => Float64[],
)
for n in n_values
@info "Testing grids of side length $n"
g = Graphs.grid([n, n])
if weighted
w = random_weights(g)
else
w = weights(g)
end
_, t0, m0, _, _ = @timed for _ in 1:5
test_dijkstra_default(g, w)
end
_, t1, m1, _, _ = @timed for _ in 1:5
test_dijkstra_priority_updates(g, PriorityQueue{Int,Float64}, w);
end
_, t3, m3, _, _ = @timed for _ in 1:5
test_dijkstra_no_priority_updates(g, VectorPriorityQueue{Int,Float64}, w);
end
_, t4, m4, _, _ = @timed for _ in 1:5
test_dijkstra_no_priority_updates(g, SortedVectorPriorityQueue{Int,Float64}, w);
end
_, t5, m5, _, _ = @timed for _ in 1:5
test_dijkstra_no_priority_updates(g, HeapPriorityQueue{Int,Float64}, w
![plot|600x400](upload://fDd1mzQVRFHaBpzZ096T9iTkNYI.png)
);
end
push!(speed_gains["PriorityQueue"], t0 / t1)
push!(speed_gains["VectorPriorityQueue"], t0 / t3)
push!(speed_gains["SortedVectorPriorityQueue"], t0 / t4)
push!(speed_gains["HeapPriorityQueue"], t0 / t5)
push!(memory_gains["PriorityQueue"], m0 / m1)
push!(memory_gains["VectorPriorityQueue"], m0 / m3)
push!(memory_gains["SortedVectorPriorityQueue"], m0 / m4)
push!(memory_gains["HeapPriorityQueue"], m0 / m5)
end
return speed_gains, memory_gains
end
# To gain precision, we could replace the built-in `@elapsed` with `@belapsed` from [BenchmarkTools.jl](https://github.com/JuliaCI/BenchmarkTools.jl).
n_values = [10, 30, 100, 300, 1000]
speed_gains, memory_gains = compare_dijkstra_versions(n_values)
# Finally, let us plot the results.
settings = [
"PriorityQueue", "SortedVectorPriorityQueue", "VectorPriorityQueue", "HeapPriorityQueue"
]
S = length(settings)
N = length(n_values)
# And we follow up with memory use
plt = plot(;
title="Dijkstra with no priority updates",
xlabel="Grid side length",
ylabel="Memory gain wrt. Graphs.jl",
ylim=(0, Inf),
xticks=(1:N, string.(n_values)),
margin=5Plots.mm,
# legend_position=:topleft
)
for (k, setting) in enumerate(settings)
bar!(
plt,
(1:N) .+ 0.7 * (k - (S + 1) / 2) / S,
memory_gains[setting];
label=setting,
bar_width=0.7 / S,
)
end
plt
# First we compare execution time
plt = plot(;
title="Dijkstra with no priority updates",
xlabel="Grid side length",
ylabel="Speed gain wrt. Graphs.jl",
ylim=(0, Inf),
xticks=(1:N, string.(n_values)),
margin=5Plots.mm,
legend_position=:legend
)
for (k, setting) in enumerate(settings)
bar!(
plt,
(1:N) .+ 0.7 * (k - (S + 1) / 2) / S,
speed_gains[setting];
label=setting,
bar_width=0.7 / S,
)
end
plt