Speed up map matching up to 23x - #288
Open
zihaooo wants to merge 4 commits into
Open
Conversation
…4 and boost 1.92, GDAL 3.13 and SWIG 4.5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Speeds up map matching by 13x to 23x on real-world data without changing the results. Most of the gain comes from the candidate search: a bounding box bug that made the rtree return most of the network for every point, a k-nearest search that skips the edges which cannot be among the k best, and an adaptive query box. The rest comes from an open addressing UBODT hash table, faster CSV parsing and writing, and reading the next block of trajectories while the current one is matched.
Depends on the toolchain PR opened alongside this one (this code uses
std::from_chars/std::to_chars, which need C++17); the branch is stacked on it, so its commit shows up here as well until it is merged.Benchmark
Test map matching of connected vehicle GPS points in three sub-regions in Michigan, with the settings
k = 8, search radius 3000 m, GPS error 100 m, all output fields written. The time is theTime takesvalue reported byFastMapMatch.match_gps_file, i.e. reading the input CSV, matching and writing the result file, measured on an AMD Ryzen 9 9950X (16 cores, 32 threads) with 32 OpenMP threads.master(19ef34e)19ef34eis currentmaster, built with the newest tool chain it compiles with (GCC 13, Boost 1.74, GDAL 3.4.3, Python 3.10).What was slow
A gprof profile (single thread) showed 69 % of the time in
linear_referencing, called about 1,750 times per GPS point: the exact point-to-edge distance was computed for most of the network for every point.ALGORITHM::boundingbox_geometry): the running maximum was initialised withDBL_MIN, which is the smallest positive double, not the most negative one. With negative coordinates (western or southern hemisphere) the max corner of every edge box stays at ~0, so all boxes extend to the prime meridian / equator and any query returns roughly half of the network. Fixed by initialising with-DBL_MAX. With positive coordinates the bug is invisible, which is probably why it was never noticed.Network::search_tr_cs_knn): the exact distance was computed for every edge in the query box, then the k nearest were selected withpartial_sort_copy. With a large search radius the box holds hundreds of edges even with correct boxes.Changes
candidate_compareis a total order (distance, then edge index), so the k best candidates and their order are unique and the result is exactly whatpartial_sort_copyproduced.malloced chained records. Same "last inserted record for a key wins" semantics.UBODT::insert(Record *)copies the record;Record::nextis kept for API compatibility but unused.std::to_charsinstead ofstd::stringstream, reproducing the stream output exactly, including thesetprecision(12)thatoperator<<(LineString)leaves on the stream for the fields written afterpgeom/mgeom.std::from_charsdirectly on the line instead ofstringstream+getline+stof; any tokenfrom_charsdoes not fully accept falls back tostd::stoi/std::stof, so values and exceptions are unchanged (coordinates are still parsed asfloat, as before).match_gps_file: the next block of trajectories is parsed in a background thread (std::async) while the current block is matched; blocks of 10,000 trajectories andschedule(dynamic, 16).update_layercomputeslog(ep)once per candidate instead of once per candidate pair (same floating point result, same summation order).Verification
masterand this PR for regions 1 and 2 above (20.7 M points), compared after sorting because the output order depends on thread scheduling. It is also identical between this PR built with the old and with the new toolchain.-march=native, compiling out trace logging) gave no measurable gain on top of these changes, so defaults are unchanged.Also in this PR
Two small follow-up commits that are not performance related but touch the same code:
total_points,total_trajsandprogressinmatch_gps_filewere incremented outside theomp criticalblock (only the firstifstatement was covered) and under-counted in multi-threaded runs. With the faster loop the under-count became clearly visible in the printed statistics. The same fix is part of the restart-on-disconnect PR opened alongside this one; whichever is merged second needs a trivial rebase of that block.CSVPointReader::has_next_trajectoryonly looked at the stream, ignoring the line thatread_next_trajectorybuffers inprev_linewhen it hits the first point of the next trajectory. When that buffered line was the last line of the file, a trailing single-point trajectory was dropped, and the trajectory before it could be relabelled with the new id.has_next_trajectorynow also reports the buffered line as pending, and the end-of-file case is detected from the loop state instead of the stream. Pre-existing bug, fixed here because the reader is changed anyway.Progress Nmessage printed every 1000 trajectories byfmm,stmatchandh3mmis now rewritten in place (\r+ flush) instead of adding a line each time.Co-Authored-By: Claude Fable 5.1 noreply@anthropic.com