Skip to content

Commit 36b9fec

Browse files
authored
Add parameters to limit number of settled nodes during preparation, much faster preparation for certain graphs (#37)
1 parent 8508735 commit 36b9fec

File tree

6 files changed

+336
-63
lines changed

6 files changed

+336
-63
lines changed

README.md

+8-8
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,14 @@ For this to work `another_input_graph` must have the same number of nodes as `in
117117
|California&Nevada|1.890.816|4.630.444|
118118
|USA|23.947.348|57.708.624|
119119

120-
|graph|metric|preparation time|average query time|
121-
|-|-|-|-|
122-
|NY city|distance|15 s|108 μs|
123-
|CAL&NV|distance|70 s|243 μs|
124-
|USA|distance|21 min|1452 μs|
125-
|NY city|time|10 s|54 μs|
126-
|CAL&NV|time|50 s|149 μs|
127-
|USA|time|11 min|856 μs|
120+
|graph|metric|preparation time|average query time|out edges|in edges|
121+
|-|-|-|-|-|-|
122+
|NY city|distance|13 s|100 μs|747.555|747.559|
123+
|CAL&NV|distance|57 s|244 μs|4.147.109|4.147.183|
124+
|USA|distance|17 min|1433 μs|52.617.216|52.617.642|
125+
|NY city|time|9 s|55 μs|706.053|706.084|
126+
|CAL&NV|time|42 s|148 μs|3.975.276|3.975.627|
127+
|USA|time|9.8 min|872 μs|49.277.058|49.283.162|
128128

129129
The shortest path calculation time was averaged over 100k random routing queries. The benchmarks were run using Rust 1.50.0
130130

changelog

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
0.3.0 (not yet released)
2+
breaking: add max_settled_nodes parameters to Params, important performance tuning for graphs with large-weight edges, #37
3+
add InputGraph::to_file, #35
4+
faster fast_graph building, 82e9a2ef417af6de1b2cb41bcee41b6302db1b4a
25
allow passing &[T] instead of &Vec<T> in a few places, #32
36
add calc_path_multiple_sources_and_targets, #30
4-
0.2.0 add clone and copy derives, #26
7+
0.2.0 [April 25th 2021]
8+
add clone and copy derives, #26
59
faster fast_graph building
610
faster queries (stall on demand optimization), #18
711
fix serious performance regression for Rust >=1.45, #21
@@ -10,5 +14,7 @@
1014
WebAssembly compatibility, #11
1115
deterministic fast_graph building, #10
1216
license change to dual MIT/Apache 2.0, #4
13-
0.1.1 adds travis and meta information to Cargo.toml
14-
0.1.0 initial version
17+
0.1.1 [June 17th 2019]
18+
adds travis and meta information to Cargo.toml
19+
0.1.0 [June 17th 2019]
20+
initial version

src/fast_graph_builder.rs

+88-6
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,26 @@ impl FastGraphBuilder {
6262
pub fn build_with_order(
6363
input_graph: &InputGraph,
6464
order: &[NodeId],
65+
) -> Result<FastGraph, String> {
66+
FastGraphBuilder::build_with_order_with_params(
67+
input_graph,
68+
order,
69+
&ParamsWithOrder::default(),
70+
)
71+
}
72+
73+
pub fn build_with_order_with_params(
74+
input_graph: &InputGraph,
75+
order: &[NodeId],
76+
params: &ParamsWithOrder,
6577
) -> Result<FastGraph, String> {
6678
if input_graph.get_num_nodes() != order.len() {
6779
return Err(String::from(
6880
"The given order must have as many nodes as the input graph",
6981
));
7082
}
7183
let mut builder = FastGraphBuilder::new(input_graph);
72-
builder.run_contraction_with_order(input_graph, order);
84+
builder.run_contraction_with_order(input_graph, order, params);
7385
Ok(builder.fast_graph)
7486
}
7587

@@ -85,6 +97,7 @@ impl FastGraphBuilder {
8597
&mut witness_search,
8698
node,
8799
0,
100+
params.max_settled_nodes_initial_relevance,
88101
) as Weight;
89102
queue.push(node, Reverse(priority));
90103
}
@@ -121,7 +134,12 @@ impl FastGraphBuilder {
121134
self.fast_graph.first_edge_ids_bwd[rank + 1] = self.fast_graph.get_num_in_edges();
122135

123136
self.fast_graph.ranks[node] = rank;
124-
node_contractor::contract_node(&mut preparation_graph, &mut witness_search, node);
137+
node_contractor::contract_node(
138+
&mut preparation_graph,
139+
&mut witness_search,
140+
node,
141+
params.max_settled_nodes_contraction,
142+
);
125143
for neighbor in neighbors {
126144
levels[neighbor] = max(levels[neighbor], levels[node] + 1);
127145
let priority = node_contractor::calc_relevance(
@@ -130,6 +148,7 @@ impl FastGraphBuilder {
130148
&mut witness_search,
131149
neighbor,
132150
levels[neighbor],
151+
params.max_settled_nodes_neighbor_relevance,
133152
) as Weight;
134153
queue.change_priority(&neighbor, Reverse(priority));
135154
}
@@ -145,7 +164,12 @@ impl FastGraphBuilder {
145164
self.finish_contraction();
146165
}
147166

148-
fn run_contraction_with_order(&mut self, input_graph: &InputGraph, order: &[NodeId]) {
167+
fn run_contraction_with_order(
168+
&mut self,
169+
input_graph: &InputGraph,
170+
order: &[NodeId],
171+
params: &ParamsWithOrder,
172+
) {
149173
let mut preparation_graph = PreparationGraph::from_input_graph(input_graph);
150174
let mut witness_search = WitnessSearch::new(self.num_nodes);
151175
for (rank, node) in order.iter().cloned().enumerate() {
@@ -177,7 +201,12 @@ impl FastGraphBuilder {
177201
self.fast_graph.first_edge_ids_bwd[rank + 1] = self.fast_graph.get_num_in_edges();
178202

179203
self.fast_graph.ranks[node] = rank;
180-
node_contractor::contract_node(&mut preparation_graph, &mut witness_search, node);
204+
node_contractor::contract_node(
205+
&mut preparation_graph,
206+
&mut witness_search,
207+
node,
208+
params.max_settled_nodes_contraction_with_order,
209+
);
181210
debug!(
182211
"contracted node {} / {}, num edges fwd: {}, num edges bwd: {}",
183212
rank + 1,
@@ -240,20 +269,73 @@ impl FastGraphBuilder {
240269
}
241270

242271
pub struct Params {
272+
/// Smaller values typically yield less shortcuts and a faster preparation time. The relation to
273+
/// query speeds is less clear. For large values that yield a much higher number of shortcuts
274+
/// queries become slow, but otherwise the query speed might only change by a small amount
275+
/// when you change this parameter. The optimal value can only be found heuristically for your
276+
/// specific graph and can even depend on the other parameters below. We recommend trying
277+
/// different values between 0 and 1.
243278
pub hierarchy_depth_factor: f32,
279+
/// This parameter is simply set to 1.0, because only it's relative size compared to
280+
/// hierarchy_depth_factor plays a role.
244281
pub edge_quotient_factor: f32,
282+
/// The maximum number of settled nodes per witness search performed when priorities are
283+
/// calculated for all nodes initially. Since this does not take much time you should probably
284+
/// keep the default.
285+
pub max_settled_nodes_initial_relevance: usize,
286+
/// The maximum number of settled nodes per witness search performed when updating priorities
287+
/// of neighbor nodes after a node was contracted. The preparation time can strongly depend on
288+
/// this value and even setting it to a very small value like 0 or 1 might be feasible.
289+
/// Higher values (like ~500+) should yield less shortcuts and faster query times at the cost of
290+
/// a longer preparation time. Lower values (like ~0-100) should yield a faster preparation at
291+
/// the cost of slower query times and more shortcuts. To know for sure you should still run
292+
/// your own experiments for your specific graph.
293+
pub max_settled_nodes_neighbor_relevance: usize,
294+
/// The maximum number of settled nodes per witness search when contracting a node. Higher values
295+
/// like ~500+ mean less shortcuts (fast graph edges), slower preparation and faster queries.
296+
/// Lower values mean more shortcuts, slower queries and faster preparation.
297+
pub max_settled_nodes_contraction: usize,
245298
}
246299

247300
impl Params {
248-
pub fn new(ratio: f32) -> Self {
301+
pub fn new(
302+
ratio: f32,
303+
max_settled_nodes_initial_relevance: usize,
304+
max_settled_nodes_neighbor_relevance: usize,
305+
max_settled_nodes_contraction: usize,
306+
) -> Self {
249307
Params {
250308
hierarchy_depth_factor: ratio,
251309
edge_quotient_factor: 1.0,
310+
max_settled_nodes_initial_relevance,
311+
max_settled_nodes_neighbor_relevance,
312+
max_settled_nodes_contraction,
313+
}
314+
}
315+
316+
pub fn default() -> Self {
317+
Params::new(0.1, 500, 100, 500)
318+
}
319+
}
320+
321+
pub struct ParamsWithOrder {
322+
/// The maximum number of settled nodes per witness search when contracting a node. Smaller
323+
/// values mean slower queries, more shortcuts, but a faster preparation. Note that the
324+
/// performance can also strongly depend on the relation between this parameter and
325+
/// Params::max_settled_nodes_contraction that was used to build the FastGraph and obtain the
326+
/// node ordering initially. In most cases you should use the same value for these two parameters.
327+
pub max_settled_nodes_contraction_with_order: usize,
328+
}
329+
330+
impl ParamsWithOrder {
331+
pub fn new(max_settled_nodes_contraction_with_order: usize) -> Self {
332+
ParamsWithOrder {
333+
max_settled_nodes_contraction_with_order,
252334
}
253335
}
254336

255337
pub fn default() -> Self {
256-
Params::new(0.1)
338+
ParamsWithOrder::new(100)
257339
}
258340
}
259341

src/lib.rs

+50-7
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub use crate::fast_graph::FastGraph;
2727
pub use crate::fast_graph32::FastGraph32;
2828
pub use crate::fast_graph_builder::FastGraphBuilder;
2929
pub use crate::fast_graph_builder::Params;
30+
use crate::fast_graph_builder::ParamsWithOrder;
3031
pub use crate::input_graph::Edge;
3132
pub use crate::input_graph::InputGraph;
3233
pub use crate::path_calculator::PathCalculator;
@@ -67,6 +68,15 @@ pub fn prepare_with_order(input_graph: &InputGraph, order: &[NodeId]) -> Result<
6768
FastGraphBuilder::build_with_order(input_graph, order)
6869
}
6970

71+
/// Like `prepare_with_order()`, but allows specifying some parameters used for the graph preparation
72+
pub fn prepare_with_order_with_params(
73+
input_graph: &InputGraph,
74+
order: &[NodeId],
75+
params: &ParamsWithOrder,
76+
) -> Result<FastGraph, String> {
77+
FastGraphBuilder::build_with_order_with_params(input_graph, order, params)
78+
}
79+
7080
/// Calculates the shortest path from `source` to `target`.
7181
pub fn calc_path(fast_graph: &FastGraph, source: NodeId, target: NodeId) -> Option<ShortestPath> {
7282
let mut calc = PathCalculator::new(fast_graph.get_num_nodes());
@@ -136,6 +146,7 @@ mod tests {
136146
use crate::preparation_graph::PreparationGraph;
137147

138148
use super::*;
149+
use crate::fast_graph_builder::ParamsWithOrder;
139150

140151
#[test]
141152
fn routing_on_random_graph() {
@@ -391,9 +402,11 @@ mod tests {
391402
fn run_performance_test_dist() {
392403
println!("Running performance test for Bremen dist");
393404
// road network extracted from OSM data from Bremen, Germany using the road distance as weight
405+
// prep: 190ms, query: 16μs, out: 68494, in: 68426
406+
// todo: try to tune parameters
394407
run_performance_test(
395408
&InputGraph::from_file("meta/test_maps/bremen_dist.gr"),
396-
&Params::default(),
409+
&Params::new(0.1, 500, 2, 50),
397410
845493338,
398411
30265,
399412
)
@@ -404,9 +417,11 @@ mod tests {
404417
fn run_performance_test_time() {
405418
println!("Running performance test for Bremen time");
406419
// road network extracted from OSM data from Bremen, Germany using the travel time as weight
420+
// prep: 256ms, query: 11μs, out: 64825, in: 65027
421+
// todo: try to tune parameters
407422
run_performance_test(
408423
&InputGraph::from_file("meta/test_maps/bremen_time.gr"),
409-
&Params::default(),
424+
&Params::new(0.1, 100, 2, 100),
410425
88104267255,
411426
30265,
412427
);
@@ -416,9 +431,11 @@ mod tests {
416431
#[test]
417432
fn run_performance_test_ballard() {
418433
println!("Running performance test for ballard");
434+
// prep: 1150ms, query: 53μs, out: 43849, in: 43700
435+
// todo: try to tune parameters
419436
run_performance_test(
420437
&InputGraph::from_file("meta/test_maps/graph_ballard.gr"),
421-
&Params::new(0.01),
438+
&Params::new(0.1, 100, 3, 100),
422439
28409159409,
423440
14992,
424441
);
@@ -428,9 +445,11 @@ mod tests {
428445
#[test]
429446
fn run_performance_test_23rd() {
430447
println!("Running performance test for 23rd");
448+
// prep: 170ms, query: 23μs, out: 11478, in: 11236
449+
// todo: try to tune parameters
431450
run_performance_test(
432451
&InputGraph::from_file("meta/test_maps/graph_23rd.gr"),
433-
&Params::default(),
452+
&Params::new(0.1, 100, 3, 100),
434453
19438403873,
435454
20421,
436455
);
@@ -440,26 +459,46 @@ mod tests {
440459
#[test]
441460
fn run_performance_test_south_seattle_car() {
442461
println!("Running performance test for South Seattle car");
462+
// prep: 877ms, query: 26μs, out: 68777, in: 68161
463+
// todo: try to tune parameters
443464
run_performance_test(
444465
&InputGraph::from_file("meta/test_maps/south_seattle_car.gr"),
445-
&Params::default(),
466+
&Params::new(0.1, 100, 10, 100),
446467
77479396,
447468
30805,
448469
);
449470
}
450471

451472
#[ignore]
452473
#[test]
453-
fn run_performance_test_dist_fixed_ordering() {
474+
fn run_performance_test_bremen_dist_fixed_ordering() {
454475
println!("Running performance test for Bremen dist (fixed node ordering)");
476+
// prep: 340ms, prep_order: 64ms, query: 16μs, out: 66646, in: 66725
477+
// todo: try to tune parameters
455478
run_performance_test_fixed_ordering(
456479
&InputGraph::from_file("meta/test_maps/bremen_dist.gr"),
457480
&Params::default(),
481+
&ParamsWithOrder::default(),
458482
845493338,
459483
30265,
460484
);
461485
}
462486

487+
#[ignore]
488+
#[test]
489+
fn run_performance_test_south_seattle_fixed_ordering() {
490+
println!("Running performance test for South Seattle car (fixed node ordering)");
491+
// prep: 811ms, prep order: 138ms, query: 27μs, out: 68777, in: 68161
492+
// todo: try to tune parameters
493+
run_performance_test_fixed_ordering(
494+
&InputGraph::from_file("meta/test_maps/south_seattle_car.gr"),
495+
&Params::new(0.1, 100, 10, 100),
496+
&ParamsWithOrder::new(100),
497+
77479396,
498+
30805,
499+
);
500+
}
501+
463502
fn run_performance_test(
464503
input_graph: &InputGraph,
465504
params: &Params,
@@ -484,6 +523,7 @@ mod tests {
484523
fn run_performance_test_fixed_ordering(
485524
input_graph: &InputGraph,
486525
params: &Params,
526+
params_with_order: &ParamsWithOrder,
487527
expected_checksum: usize,
488528
expected_num_not_found: usize,
489529
) {
@@ -496,7 +536,10 @@ mod tests {
496536
);
497537
let order = get_node_ordering(&fast_graph);
498538
prepare_algo(
499-
&mut |input_graph| fast_graph = prepare_with_order(input_graph, &order).unwrap(),
539+
&mut |input_graph| {
540+
fast_graph =
541+
prepare_with_order_with_params(input_graph, &order, params_with_order).unwrap()
542+
},
500543
&input_graph,
501544
);
502545
print_fast_graph_stats(&fast_graph);

0 commit comments

Comments
 (0)