pass2coefficient = 1.0 measures ~1.5x faster than the c=0 default across nine profiles
Hi,
I run BRouter 1.7.9 in server mode as the routing engine for a cycle route planner. While investigating long-distance performance I ended up setting `assign pass2coefficient = 1.0` in all nine profiles I ship, which turned out to be a large and consistent speedup at essentially unchanged routes.
Since [algorithm.html](https://brouter.de/brouter/algorithm.html) gives an explicit rationale for the `c=0` default that points the other way, I would rather bring the numbers here than sit on a local patch. There may well be something I measured wrong.
## Setup
BRouter 1.7.9, server mode, identical rd5 tiles, single container, nothing changed but the one profile line. Endpoints are German main stations, e.g.
Koblenz Hbf `7.5976,50.3299` -> Würzburg Hbf `9.9346,49.8019`. The comparison metric is `properties.cost`, not length or ascent — those two are
proxies for the objective function and can move against each other. Cost is deterministic and reproducible; the timings below are single default/variant pairs measured back to back per corridor, so treat them as indicative. The effect is far outside the run-to-run scatter (~3 % between adjacent runs), but it is not a repeated-trials study.
## 1. Timing
Nine shipped profiles, three corridors each, 150–360 km, one of them hilly:
| profile | speedup | Δ cost |
|---|---|---|
| trekking | 1.63x | +0.0494 % |
| trekking-steep | 1.60x | 0 |
| trekking-ignore-cr | 1.44x | 0 |
| trekking-noferries | 1.62x | +0.0494 % |
| fastbike | 1.59x | +0.0043 % |
| fastbike-lowtraffic | 1.54x | 0 |
| fastbike-verylowtraffic | 1.55x | 0 |
| gravel | 1.68x | 0 |
| mtb | 1.09x | 0 |
Mean 1.52x. Six of nine return a bit-identical route.
On longer corridors with `trekking` (500 km / 750 km / 1250 km) the factor is 1.57x / 1.84x / 2.18x - it grows with distance.
Re-checked against a freshly built image on Koblenz -> Würzburg with identical tiles, baked-in 1.0 against the stock default: trekking 1.43x, fastbike 1.45x, gravel 1.77x, mtb 1.12x, `Δ cost = 0` in all four.
`mtb` is the outlier at 1.09x, and it explains itself: it sets `pass1coefficient 1.8`, so pass 0 is already greedy and little is left for pass 1 to save.
## 2. The part I would like to understand
[algorithm.html](https://brouter.de/brouter/algorithm.html) says:
> The reason that c=0 (=Dijkstra) is used in the second pass and not c=1
> (=A-Star) is simply that for c=0 the open-set is smaller, because many paths
> run into the cutoff at an early time and do not have to be managed in the
> open-set anymore.
and that open-set size drives performance and memory consumption.
My numbers point the other way. **but I measured wall-clock time, and the argument above is about open-set size.** Those are different quantities and both statements can hold at once: a smaller open set that still costs more overall, for instance because far more nodes reach the cutoff at `RoutingEngine.java:2139` before being discarded.
So the actual question is: was the open-set argument measured, and on which metric? If it is open-set peak or memory, my result is not a contradiction at all, it would just mean the trade-off the docs describe is no longer the one that dominates on current hardware and tile sizes.
I can run `nodesVisited` per pass over these corridors if that is the useful number, and post it here.
## 3. Secondary observation: `c=1.0` is not exactly optimal
Happy to split this into its own issue if you prefer, it only matters if someone actually runs the second pass with a non-zero coefficient.
The commonly stated guarantee is coefficient <= 1 implies the optimal route. I see a small but strictly one-sided deviation at exactly 1.0: three of nine profiles return a slightly more expensive route, worst case +0.0494 %. It is never cheaper.
Concrete case, `fastbike`, Koblenz -> Würzburg, 227 km:
| pass2coefficient | cost | Δ |
|---|---|---|
| 0.0 | 303028 | - |
| 1.0 … 0.75 | 303041 | +13 |
| 0.5 | 303028 | 0 |
A step, not a gradient. 13 cost units are 13 m of perfect road on 227 km, so this is not a practical problem for us, but it does suggest the admissibility condition is not quite holding.
What I ruled out:
* **Cost factors below 1 in the profile.** Of 1688 way segments on that route, none is below `CostPerKm 1000`. `mtb`'s sub-1 uphill/downhill factors are wrapped in a `max 1.0`, and the profile says so itself.
* **Path-dependent elevation cost.** That would affect the default equally and would make the variant cheaper about as often as more expensive. It is consistently more expensive.
That leaves `airdistance` itself. `CheapRuler.distance` is an approximation with a signed error, and where it overestimates it stops being a lower bound on the remaining cost regardless of what the profile contains. **This is a hypothesis, not a measurement** — I have not yet compared `CheapRuler.distance` against a geodesic reference over these corridors. If the sign and magnitude of that error are already known to someone, it would save the experiment.
For context on why I suspect it there and not in the rounding: #499 fixed a systematic overestimate in `calcDistance` caused by `(int) d + 1`, but that one accumulated over many short links, whereas `airdistance` is a single node-to-target call, rounding can contribute at most ~0.5 m here and cannot account for 13. #128 adopted CheapRuler explicitly for speed and does not discuss its error.
Thanks for BRouter, and for the algorithm page, it is the reason this was findable at all.
0 条评论