-
Notifications
You must be signed in to change notification settings - Fork 339
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
Improve Performance on PD Instances #559
Conversation
A bit more thorough benchmark on 176 instances (li_lim_100, li_lim_200, li_lim_400): master:
this PR:
seems to be almost 20% faster |
Thanks for polishing this work and submitting a PR! I'll look into it soon. Looks like between this and #558 we have some great ongoing improvements on computing times. \o/ |
Did not go through the commits yet, but I can confirm the steady reduction around -21.5% on average for all PDPTW benchmarks (li_lim_*) across all exploration levels. This is great! |
I've been running a few non-regression tests on that one and found something breaking with the following instance. Running $ vroom -i pd-perf-problem.txt
vroom: structures/vroom/tw_route.cpp:177: void vroom::TWRoute::fwd_update_earliest_from(const vroom::Input&, vroom::Index): Assertion `current_earliest <= latest[i]' failed.
Aborted (core dumped) This kind of problem is not trivial to debug, it's a consistency check that breaks upon updating earliest times after applying some route change. Usually it's the sign that 1. the applied move is not valid or 2. an inconsistency has been previously introduced in earliest/latest dates by some other change. I'd go for 1. here since the changes do not touch the TW logic but only "client" code. |
Cool, the instance is pretty small. I'll look into it |
For what it's worth, here is a patch I find useful to log current state of TWRoute objects. It applies to current Also to avoid searching a needle in a haystack you can narrow down the error with a single heuristic parameter applied: |
I think I fixed the problem, it was clearly a mistake in this PR. I'm a bit scared that this didn't come up in any benchmark instance |
Trying to get the big picture for those changes, @krypt-n just let me know if the following summary fits. On P&D insertionBefore:
Now grouping both implementations and adjusting has the consequence that:
On amount-related allocationsNow for 0e3aa24, my understanding is that switching from maintaining |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great, both for the speedup related to added early stops and for the fact that this reduces code duplication. For the latter, I see two ways to go even further, by using the new ls::compute_best_insertion_pd
:
- From
cvrp::PDShift::compute_gain
too. - From the heuristics.
Item 1 seems pretty straightforward: I think it would work out-of-the-box if cvrp::PDShift::compute_gain
were to hold the new vrptw::PDShift::compute_gain
implementation (except for the removal check part) and vrptw::PDShift::compute_gain
were to call its parent counterpart. Item 2 may be more touchy so we can always schedule that for another PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good as is, thanks for the last changes. What's your take on the previous comment? If you don't plan to do anything on the cvrp
side (item 1), then I could probably handle it before merging.
Yep, your summary seems correct to me. I replaced the cvrp version in b88b11e |
On a first glance, there are two copies of this pickup/delivery insertion search in heuristics.cpp, with some minor differences to the one in this PR, so I would leave that as is for now |
I completed the usual Li&Lim checks by runs on real-life instances and also noticed a speedup there (in the 10% to 15% ballpark) so I'm looking forward to merge!
I do have a few instances where the solution is actually different with this PR. It's not always better or worse so it really looks like some heuristic choice is simply made differently at some point. I did not dig more into the problem but managed to narrow it down with a small-ish instance. Results I'm getting with this file using the parent commit 58f7411 and the tip of this PR:
|
I'll try to figure out the reason for that difference |
I did some debugging and found that this is a case of same-cost-but-different-choice in Applying this patch on top of b88b11e diff --git a/src/algorithms/local_search/local_search.cpp b/src/algorithms/local_search/local_search.cpp
index 61d3a78..8871588 100644
--- a/src/algorithms/local_search/local_search.cpp
+++ b/src/algorithms/local_search/local_search.cpp
@@ -7,6 +7,8 @@ All rights reserved (see LICENSE).
*/
+#include <iostream>
+
#include "algorithms/local_search/local_search.h"
#include "algorithms/local_search/insertion_search.h"
#include "problems/vrptw/operators/cross_exchange.h"
@@ -247,6 +249,15 @@ void LocalSearch<Route,
job_added = (best_cost < std::numeric_limits<double>::max());
if (job_added) {
+ bool log =
+ (best_route == 3 and best_job_rank == 30 and
+ best_insertion.cost == 169 and best_insertion.delivery_rank == 4);
+
+ if (log) {
+ std::cout << "best_insertion.pickup_rank = "
+ << best_insertion.pickup_rank << std::endl;
+ }
+
_sol_state.unassigned.erase(best_job_rank);
const auto& best_job = _input.jobs[best_job_rank]; yields:
Now logging the same at
All the rest of the execution path (operators applied, jobs added) looks similar prior to this choice and then diverges since solution differ after this insertion. Looks like all evaluations are matching but the order in which ranks are evaluated is changed somehow so a different option with same cost is picked. |
Back on this PR, I just noticed that my previous comment was misleading since I reported the same output: I think I found the reason for this difference by logging the actual costs evaluated within the different versions of
The thing is that normalizing the P&D insertion cost by dividing it by two now happens after the call to Wrapping this up:
I did not want to introduce a change of behavior without fully understanding the reason, but now I think we're good! @krypt-n do you think you could resolve conflicts with current |
Hi, I'll look into updating this PR in the next couple of days. I believe I already resolved some merge conflicts locally a while ago |
b88b11e
to
98234b4
Compare
Okay, I rebased the changes, added a changelog entry (pushed this to the master branch accidentally, apologies for that), and confirmed that this is still a 20% improvement compared to the current master branch with a few benchmarks. Ready to merge from my point of view! |
Issue
(Do I need one for performance improvements?)
Tasks
CHANGELOG.md
(remove if irrelevant)Description
This is work I did back in February which lead me to discover #462, rebased onto master and formatted. During profiling I noticed that PDShift and LocalSearch both contain code to look for a cheap insertion of a pickup and a delivery into a route.
This PR first de-duplicates the code and then improves the search somewhat by pruning based on known costs, leading to improved performance on PD instances.
Additionally, 0e3aa24 reduces the allocation of
Amount
objects, again, improving performance somewhat.As far as I am aware, this PR does not in any way change the solutions computed by vroom, I'd consider it a bug if it does. Initial benchmarking on the
li_lim_100
PD instances looks promising:master at 58f7411
this PR