Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce code duplication in timedep forward/reverse #2987

Merged
merged 36 commits into from
Apr 23, 2021

Conversation

danpat
Copy link
Member

@danpat danpat commented Apr 8, 2021

Issue

PR No. 3 in the series towards #2885. This one tackles TimeDepForward/Reverse.

Main changes in this are:

  • Rename TimeDep to UnidirectionalAStar to be consistent with BidirectionalAStar
  • Templatize the behaviour and merge forward/reverse handling into a single unidirectionalastar.cc codebase.
  • Fix a bug in reverse routing that lead to incorrect routes, node counts and ETAs

Tasklist

  • Add tests
  • Add #fixes with the issue number that this PR addresses
  • Update the docs with any new request parameters or changes to behavior described
  • Update the changelog

Requirements / Relations

Link any requirements here. Other pull requests this PR is based on?

const TimeInfo& time_info,
const valhalla::Location& destination,
std::pair<int32_t, float>& best_path) {
template <TimeDepForward::ExpansionType expansion_direction, typename EdgeLabelT>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note the EdgeLabelT - this is a temporary WIP trying to figure out how to handle EdgeLabel-vs-BDEdgeLabel.

}

return disable_uturn;
}

template bool TimeDepForward::Expand<TimeDepForward::ExpansionType::reverse, BDEdgeLabel>(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explicit declaration of reverse implementation so that the TimeDepReverse translation unit can find the template instantiation at link time. As the code stands, it's a bit weird to have a derived class trying to use template methods from the parent TU - I expect to refactor this detail away as I remove more code.

std::pair<int32_t, float>& best_path) {
constexpr bool FORWARD = expansion_direction == TimeDepForward::ExpansionType::forward;

if (!FORWARD) {
Copy link
Member Author

@danpat danpat Apr 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question to readers: conceptually why is this early-exit only done for the reverse search? Is this a copypasta bug, or is there some rationale?

cc @kevinkreiser @dnesbitt61

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code has changed considerably since I left it, but in reverse searches I initially added an early exit based on the directed edge's reverseaccess to avoid having to get the opposing directed edge and perform the more complete costing Allowed check. I believe I did some benchmarking around this and found a slight performance improvement. I can't comment on why shortcut edges are skipped.

if (t2 == nullptr) {
return false;
}
opp_edge_id = t2->GetOpposingEdgeId(meta.edge);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: I renamed these from TimeDepReverse, they were oppedge and opp_edge there, which is a bit of a dubious naming scheme.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea, opp_edge_id is a better choice!

time_info.local_time, nodeinfo->timezone())) {
return false;
if (FORWARD) {
if (meta.edge->is_shortcut() ||
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the forward search wrapped in is_shortcut(), but the reverse is not?

if (FORWARD) {
newcost += transition_cost;
} else {
newcost.cost += transition_cost.cost;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was similar to the thing found in the Dijkstra refactor - why does reverse only add cost? This seems like a bug, as it means a TimeDepReverse path won't accumulate time?

: edgelabels_.size();
best_path.first = (meta.edge_status->set() == EdgeSet::kTemporary)
? meta.edge_status->index()
: (FORWARD ? edgelabels_.size() : edgelabels_.size());
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: these used to be different (edgelabels_rev_ and edgelabels_) - I've left this in for now as a reminder that forward/reverse behaviour may be specialized here.

EdgeLabel& lab = edgelabels_[meta.edge_status->index()];
// TODO(danpat): can we slices down to EdgeLabel here safely?
EdgeLabel& lab =
FORWARD ? edgelabels_[meta.edge_status->index()] : edgelabels_[meta.edge_status->index()];
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to above - previously had different EdgeLabel/BDEdgeLabel for forward/reverse - left this in as a temporary reminder until I confirm it's OK to use BDEdgeLabel in all cases.

if (newcost.cost < lab.cost().cost) {
float newsortcost = lab.sortcost() - (lab.cost().cost - newcost.cost);
adjacencylist_.decrease(meta.edge_status->index(), newsortcost);
if (FORWARD) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And one more instance of the same.

@@ -54,245 +33,20 @@ void TimeDepReverse::Init(const midgard::PointLL& origll, const midgard::PointLL
// Reserve size for edge labels - do this here rather than in constructor so
// to limit how much extra memory is used for persistent objects.
// TODO - reserve based on estimate based on distance and route type.
edgelabels_rev_.reserve(kInitialEdgeLabelCount);
edgelabels_.reserve(kInitialEdgeLabelCount);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole Init() thing can likely go away if we can use the BDEdgeLabel object for the forward search.

@kevinkreiser
Copy link
Member

kevinkreiser commented Apr 8, 2021

@danpat as an anecdote i can say that i semi recently refactored the forward direction of isochrones to use BDEdgeLabel. I seem to remember losing about 10% performance but I clawed it back with optimization to other parts of the code. I would much prefer the simplicity of using BDEdgeLabel everywhere (multimodal is another question mark though) if we can, so its definitely worth doing and measuring to see if its palatable.

@danpat
Copy link
Member Author

danpat commented Apr 14, 2021

Benchmark results. Switching to BDEdgeLabel for both doesn't seem to have a noticeable effect:

Invocation:

./bench/thor/benchmark-routes  --benchmark_min_time=3 --benchmark_filter=BM_GlobalFixedRandom --benchmark_counters_tabular=true --benchmark_repetitions=3 --planet-path=../../data/planet_2021_03_27-03_00_00.tar

master

-----------------------------------------------------------------------------------------------------------
Benchmark                                                      Time             CPU   Iterations     Routes
-----------------------------------------------------------------------------------------------------------
BM_GlobalFixedRandom<thor::TimeDepForward>                 14518 ms        14502 ms            1          6
BM_GlobalFixedRandom<thor::TimeDepForward>                 15051 ms        15031 ms            1          6
BM_GlobalFixedRandom<thor::TimeDepForward>                 15286 ms        15270 ms            1          6
BM_GlobalFixedRandom<thor::TimeDepForward>_mean            14952 ms        14934 ms            3          6
BM_GlobalFixedRandom<thor::TimeDepForward>_median          15051 ms        15031 ms            3          6
BM_GlobalFixedRandom<thor::TimeDepForward>_stddev            394 ms          393 ms            3          0
BM_GlobalFixedRandom<thor::TimeDepReverse>                  7616 ms         7612 ms            1          6
BM_GlobalFixedRandom<thor::TimeDepReverse>                  7364 ms         7361 ms            1          6
BM_GlobalFixedRandom<thor::TimeDepReverse>                  7690 ms         7685 ms            1          6
BM_GlobalFixedRandom<thor::TimeDepReverse>_mean             7556 ms         7553 ms            3          6
BM_GlobalFixedRandom<thor::TimeDepReverse>_median           7616 ms         7612 ms            3          6
BM_GlobalFixedRandom<thor::TimeDepReverse>_stddev            171 ms          170 ms            3          0
BM_GlobalFixedRandom<thor::BidirectionalAStar>             10702 ms        10685 ms            1          6
BM_GlobalFixedRandom<thor::BidirectionalAStar>              9951 ms         9944 ms            1          6
BM_GlobalFixedRandom<thor::BidirectionalAStar>              9918 ms         9912 ms            1          6
BM_GlobalFixedRandom<thor::BidirectionalAStar>_mean        10190 ms        10180 ms            3          6
BM_GlobalFixedRandom<thor::BidirectionalAStar>_median       9951 ms         9944 ms            3          6
BM_GlobalFixedRandom<thor::BidirectionalAStar>_stddev        443 ms          437 ms            3          0

this branch

-----------------------------------------------------------------------------------------------------------
Benchmark                                                      Time             CPU   Iterations     Routes
-----------------------------------------------------------------------------------------------------------
BM_GlobalFixedRandom<thor::TimeDepForward>                 14716 ms        14689 ms            1          6
BM_GlobalFixedRandom<thor::TimeDepForward>                 14435 ms        14423 ms            1          6
BM_GlobalFixedRandom<thor::TimeDepForward>                 14335 ms        14324 ms            1          6
BM_GlobalFixedRandom<thor::TimeDepForward>_mean            14495 ms        14479 ms            3          6
BM_GlobalFixedRandom<thor::TimeDepForward>_median          14435 ms        14423 ms            3          6
BM_GlobalFixedRandom<thor::TimeDepForward>_stddev            197 ms          189 ms            3          0
BM_GlobalFixedRandom<thor::TimeDepReverse>                  7445 ms         7437 ms            1          6
BM_GlobalFixedRandom<thor::TimeDepReverse>                  7602 ms         7598 ms            1          6
BM_GlobalFixedRandom<thor::TimeDepReverse>                  7427 ms         7424 ms            1          6
BM_GlobalFixedRandom<thor::TimeDepReverse>_mean             7491 ms         7486 ms            3          6
BM_GlobalFixedRandom<thor::TimeDepReverse>_median           7445 ms         7437 ms            3          6
BM_GlobalFixedRandom<thor::TimeDepReverse>_stddev           96.4 ms         96.7 ms            3          0
BM_GlobalFixedRandom<thor::BidirectionalAStar>              9566 ms         9556 ms            1          6
BM_GlobalFixedRandom<thor::BidirectionalAStar>              9578 ms         9575 ms            1          6
BM_GlobalFixedRandom<thor::BidirectionalAStar>              9558 ms         9554 ms            1          6
BM_GlobalFixedRandom<thor::BidirectionalAStar>_mean         9567 ms         9562 ms            3          6
BM_GlobalFixedRandom<thor::BidirectionalAStar>_median       9566 ms         9556 ms            3          6
BM_GlobalFixedRandom<thor::BidirectionalAStar>_stddev       10.1 ms         11.8 ms            3          0

@kevinkreiser
Copy link
Member

@danpat good news that the larger edgelabels didnt bother it. some of the results are suprisingly noisey in these benches though. ive been noticing it over the last few PRs. the standard deviation for example is off by orders of magnitude between runs sometimes. wtf is with that? im still so skeptical of gbench hahaha 😄

@danpat
Copy link
Member Author

danpat commented Apr 14, 2021

@kevinkreiser this bench running a small handful of quite slow routes - e.g. in the Forward test, 6 routes are calculated and it takes ~14 seconds. Given the duration of the individual test, I'm not all that surprised to see a pretty wobbly stddev, anything else happening on the box over that single timed 14 second run could interfere.

A better test might be to divide it up into smaller faster chunks - more iterations of a faster test would probably give better signal.

@kevinkreiser
Copy link
Member

looks good to me save for those two nit picks

@danpat
Copy link
Member Author

danpat commented Apr 21, 2021

@kevinkreiser yeah, not quite up-to-date with master.

I'm still digging into two curiosities before I mark this good:

https://github.com/valhalla/valhalla/blob/danpat_timedep_refactor_1/src/thor/unidirectional_astar.cc#L241-L247

I don't understand why reverse is only adding .cost here for transitions, rather than the whole thing.

The second, more concerning issue is this:

https://github.com/valhalla/valhalla/blob/danpat_timedep_refactor_1/test/gurka/test_time_tracking.cc#L265-L269

Even on a basic, unambiguous test, TimeDepReverse is returning different durations from node-to-node - IMO, this test should return identical results to the forward search version. It's also returning an extra node as you noted in the TODO there, so I think there's some off-by-one or missing termination condition somewhere in the reverse version of things.

So far, what I know is that it seems that the reverse search is continuing for one extra expansion after arriving at the origin - I suspect it might be a 0-cost u-turn or something.

This bug exists on master so I'm not going to kill myself trying to track it down, but I would like to clean up that cost adding thing.

@kevinkreiser
Copy link
Member

Ah yes, when i wrote a bunch of the test all algorithms tests I had to disable some because it wasnt adding up properly. It seems you are finding the same thing. I didnt have a chance to dive into it at the time but it will be good to get to the bottom of it!

@danpat
Copy link
Member Author

danpat commented Apr 22, 2021

Got to the bottom of it - two problems in the reverse search:

  1. SetOrigin wasn't swapping looking at begin_node/end_node like it should:
    has_other_edges = has_other_edges || !e.begin_node();
    and
    if (has_other_edges && edge.begin_node()) {

    This was the source of the extra node at the start of the path.
  2. FormPath in reverse mode was failing to subtract the current transition cost when it was adding the previous transition cost:
    cost += previous_transition_cost;

    This was the source of the differing timing information on the reverse path compared to the forward search.

With these two fixes, forward/reverse now give consistent results. Commits coming shortly once I verify all the other tests.

UPDATE: c9caf70 contains the fix

@danpat
Copy link
Member Author

danpat commented Apr 22, 2021

@kevinkreiser I think I'm good with the current state of this - 350 fewer lines of code and a couple of bugs fixed, :feelsgood: .

mandeepsandhu
mandeepsandhu previously approved these changes Apr 23, 2021
Copy link
Contributor

@mandeepsandhu mandeepsandhu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks for this big cleanup (and squashed an imp bug too!) 🙏

@danpat danpat merged commit 9fe3c6a into master Apr 23, 2021
@danpat danpat deleted the danpat_timedep_refactor_1 branch April 23, 2021 16:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants