diff --git a/.github/workflows/osrm-backend.yml b/.github/workflows/osrm-backend.yml index 208d24707..98c6fbd0f 100644 --- a/.github/workflows/osrm-backend.yml +++ b/.github/workflows/osrm-backend.yml @@ -645,8 +645,10 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PR_NUMBER: ${{ github.event.pull_request.number }} GITHUB_REPOSITORY: ${{ github.repository }} + RUN_BIG_BENCHMARK: ${{ contains(github.event.pull_request.labels.*.name, 'Performance') }} steps: - name: Enable data.osm.pbf cache + if: ${{ ! env.RUN_BIG_BENCHMARK }} uses: actions/cache@v4 with: path: ~/data.osm.pbf @@ -678,10 +680,18 @@ jobs: sudo apt-get update -y && sudo apt-get install ccache - name: Prepare data run: | - if [ ! -f "~/data.osm.pbf" ]; then - wget http://download.geofabrik.de/europe/germany/berlin-latest.osm.pbf -O ~/data.osm.pbf + if [ "$RUN_BIG_BENCHMARK" = "true" ]; then + rm -rf ~/data.osm.pbf + wget http://download.geofabrik.de/europe/poland-latest.osm.pbf -O ~/data.osm.pbf --quiet + gunzip -c ./pr/test/data/poland_gps_traces.csv.gz > ~/gps_traces.csv + else + if [ ! -f "~/data.osm.pbf" ]; then + wget http://download.geofabrik.de/europe/germany/berlin-latest.osm.pbf -O ~/data.osm.pbf + else + echo "Using cached data.osm.pbf" + fi + gunzip -c ./pr/test/data/berlin_gps_traces.csv.gz > ~/gps_traces.csv fi - gunzip -c ./pr/test/data/berlin_gps_traces.csv.gz > ~/gps_traces.csv - name: Prepare environment run: | echo "CCACHE_DIR=$HOME/.ccache" >> $GITHUB_ENV diff --git a/CHANGELOG.md b/CHANGELOG.md index 630f8a264..e5b4c9662 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,13 @@ - NodeJS: - CHANGED: Use node-api instead of NAN. [#6452](https://github.com/Project-OSRM/osrm-backend/pull/6452) - Misc: + - CHANGED: Apply micro-optimisation for Table & Trip APIs. [#6949](https://github.com/Project-OSRM/osrm-backend/pull/6949) + - CHANGED: Apply micro-optimisation for Route API. [#6948](https://github.com/Project-OSRM/osrm-backend/pull/6948) + - CHANGED: Apply micro-optimisation for Match API. [#6945](https://github.com/Project-OSRM/osrm-backend/pull/6945) + - CHANGED: Apply micro-optimisation for Nearest API. [#6944](https://github.com/Project-OSRM/osrm-backend/pull/6944) + - CHANGED: Avoid copy of intersection in totalTurnAngle. [#6938](https://github.com/Project-OSRM/osrm-backend/pull/6938) + - CHANGED: Use std::unordered_map::emplace instead of operator[] when producing JSONs. [#6936](https://github.com/Project-OSRM/osrm-backend/pull/6936) + - CHANGED: Avoid copy of vectors in MakeRoute function. [#6939](https://github.com/Project-OSRM/osrm-backend/pull/6939) - FIXED: Fix bugprone-unused-return-value clang-tidy warning. [#6934](https://github.com/Project-OSRM/osrm-backend/pull/6934) - FIXED: Fix performance-noexcept-move-constructor clang-tidy warning. [#6931](https://github.com/Project-OSRM/osrm-backend/pull/6933) - FIXED: Fix performance-noexcept-swap clang-tidy warning. [#6931](https://github.com/Project-OSRM/osrm-backend/pull/6931) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1876d6b85..eac368946 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -330,20 +330,12 @@ if(ENABLE_CONAN) set(CONAN_BOOST_VERSION "1.85.0@#14265ec82b25d91305bbb3b30d3357f8") set(CONAN_BZIP2_VERSION "1.0.8@#d1b2d5816f25865acf978501dff1f897") - set(CONAN_EXPAT_VERSION "2.2.10@#916908d4a570ad839edd25322c3268cd") - set(CONAN_LUA_VERSION "5.4.4@#3ec62efc37cd0a5d80b9e5cb35277360") - set(CONAN_TBB_VERSION "2021.3.0@#507ec17cbd51a84167e143b20d170eea") + set(CONAN_EXPAT_VERSION "2.6.2@#2d385d0d50eb5561006a7ff9e356656b") + set(CONAN_LUA_VERSION "5.4.6@#658d6089093cf01992c2737ab2e96763") + set(CONAN_TBB_VERSION "2021.12.0@#e56e5b44be8d690530585dd3634c0106") set(CONAN_SYSTEM_INCLUDES ON) - # TODO: - # if we link TBB dynamically osrm-extract.exe finishes on the first access to any TBB symbol - # with exit code = -1073741515, which means that program cannot load required DLL. - if (MSVC) - set(TBB_SHARED False) - else() - set(TBB_SHARED True) - endif() set(CONAN_ARGS REQUIRES @@ -357,7 +349,6 @@ if(ENABLE_CONAN) KEEP_RPATHS NO_OUTPUT_DIRS OPTIONS boost:filesystem_version=3 # https://stackoverflow.com/questions/73392648/error-with-boost-filesystem-version-in-cmake - onetbb:shared=${TBB_SHARED} boost:without_stacktrace=True # Apple Silicon cross-compilation fails without it BUILD missing ) diff --git a/docker/Dockerfile-alpine b/docker/Dockerfile-alpine new file mode 100644 index 000000000..c636b1619 --- /dev/null +++ b/docker/Dockerfile-alpine @@ -0,0 +1,64 @@ +FROM alpine:3.20.0 as alpine-mimalloc + +RUN apk add --no-cache mimalloc + +ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 +ENV MIMALLOC_LARGE_OS_PAGES=1 + + +FROM alpine-mimalloc as builder +ARG DOCKER_TAG +ARG BUILD_CONCURRENCY +RUN mkdir -p /src && mkdir -p /opt + +RUN apk add --no-cache \ + cmake make git clang libbz2 libxml2 \ + boost-dev boost-program_options boost-filesystem boost-iostreams boost-thread \ + lua5.4-dev onetbb-dev expat-dev + +COPY . /src +WORKDIR /src + +RUN NPROC=${BUILD_CONCURRENCY:-$(nproc)} && \ + echo "Building OSRM ${DOCKER_TAG}" && \ + git show --format="%H" | head -n1 > /opt/OSRM_GITSHA && \ + echo "Building OSRM gitsha $(cat /opt/OSRM_GITSHA)" && \ + mkdir -p build && \ + cd build && \ + BUILD_TYPE="Release" && \ + ENABLE_ASSERTIONS="Off" && \ + BUILD_TOOLS="Off" && \ + case ${DOCKER_TAG} in *"-debug"*) BUILD_TYPE="Debug";; esac && \ + case ${DOCKER_TAG} in *"-assertions"*) BUILD_TYPE="RelWithDebInfo" && ENABLE_ASSERTIONS="On" && BUILD_TOOLS="On";; esac && \ + echo "Building ${BUILD_TYPE} with ENABLE_ASSERTIONS=${ENABLE_ASSERTIONS} BUILD_TOOLS=${BUILD_TOOLS}" && \ + cmake .. -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DENABLE_ASSERTIONS=${ENABLE_ASSERTIONS} -DBUILD_TOOLS=${BUILD_TOOLS} -DENABLE_LTO=On && \ + make -j${NPROC} install && \ + cd ../profiles && \ + cp -r * /opt && \ + strip /usr/local/bin/* && \ + rm -rf /src + + +# Multistage build to reduce image size - https://docs.docker.com/engine/userguide/eng-image/multistage-build/#use-multi-stage-builds +# Only the content below ends up in the image, this helps remove /src from the image (which is large) +FROM alpine-mimalloc as runstage + +COPY --from=builder /usr/local /usr/local +COPY --from=builder /opt /opt + +RUN apk add --no-cache \ + boost-program_options boost-regex \ + boost-date_time boost-chrono boost-filesystem \ + boost-iostreams boost-system boost-thread \ + expat lua5.4 onetbb && \ + ldconfig /usr/local/lib + +RUN /usr/local/bin/osrm-extract --help && \ + /usr/local/bin/osrm-routed --help && \ + /usr/local/bin/osrm-contract --help && \ + /usr/local/bin/osrm-partition --help && \ + /usr/local/bin/osrm-customize --help + +WORKDIR /opt + +EXPOSE 5000 diff --git a/docker/hooks/build b/docker/hooks/build old mode 100644 new mode 100755 index c27c851ad..8756e3e93 --- a/docker/hooks/build +++ b/docker/hooks/build @@ -6,4 +6,8 @@ # ensure that "COPY . /src" is referring to the repo root, not the directory # that contains the Dockerfile. # This script gets executed with a pwd of wherever the Dockerfile is. -docker build --build-arg BUILD_CONCURRENCY=${CONCURRENCY:-1} --build-arg DOCKER_TAG=${DOCKER_TAG} -t $IMAGE_NAME -f Dockerfile .. + +DOCKER_BUILD="docker build --build-arg BUILD_CONCURRENCY=${CONCURRENCY} --build-arg DOCKER_TAG=${DOCKER_TAG:?unset} -t ${IMAGE_NAME:?unset} -f" + +$DOCKER_BUILD Dockerfile .. +$DOCKER_BUILD Dockerfile-alpine .. diff --git a/include/engine/api/match_api.hpp b/include/engine/api/match_api.hpp index 012c01176..d7956bba4 100644 --- a/include/engine/api/match_api.hpp +++ b/include/engine/api/match_api.hpp @@ -77,19 +77,19 @@ class MatchAPI final : public RouteAPI sub_routes[index].unpacked_path_segments, sub_routes[index].source_traversed_in_reverse, sub_routes[index].target_traversed_in_reverse); - route.values["confidence"] = sub_matchings[index].confidence; - routes.values.push_back(std::move(route)); + route.values.emplace("confidence", sub_matchings[index].confidence); + routes.values.emplace_back(std::move(route)); } if (!parameters.skip_waypoints) { - response.values["tracepoints"] = MakeTracepoints(sub_matchings); + response.values.emplace("tracepoints", MakeTracepoints(sub_matchings)); } - response.values["matchings"] = std::move(routes); - response.values["code"] = "Ok"; + response.values.emplace("matchings", std::move(routes)); + response.values.emplace("code", "Ok"); auto data_timestamp = facade.GetTimestamp(); if (!data_timestamp.empty()) { - response.values["data_version"] = data_timestamp; + response.values.emplace("data_version", data_timestamp); } } @@ -132,13 +132,13 @@ class MatchAPI final : public RouteAPI if (tidy_result.can_be_removed[trace_index]) { - waypoints.push_back(fbresult::WaypointBuilder(fb_result).Finish()); + waypoints.emplace_back(fbresult::WaypointBuilder(fb_result).Finish()); continue; } auto matching_index = trace_idx_to_matching_idx[trace_index]; if (matching_index.NotMatched()) { - waypoints.push_back(fbresult::WaypointBuilder(fb_result).Finish()); + waypoints.emplace_back(fbresult::WaypointBuilder(fb_result).Finish()); continue; } const auto &phantom = @@ -165,7 +165,7 @@ class MatchAPI final : public RouteAPI { waypoint->add_waypoint_index(matching_index.point_index); } - waypoints.push_back(waypoint->Finish()); + waypoints.emplace_back(waypoint->Finish()); } return fb_result.CreateVector(waypoints); @@ -186,23 +186,23 @@ class MatchAPI final : public RouteAPI { if (tidy_result.can_be_removed[trace_index]) { - waypoints.values.push_back(util::json::Null()); + waypoints.values.emplace_back(util::json::Null()); continue; } auto matching_index = trace_idx_to_matching_idx[trace_index]; if (matching_index.NotMatched()) { - waypoints.values.push_back(util::json::Null()); + waypoints.values.emplace_back(util::json::Null()); continue; } const auto &phantom = sub_matchings[matching_index.sub_matching_index].nodes[matching_index.point_index]; auto waypoint = BaseAPI::MakeWaypoint({phantom}); - waypoint.values["matchings_index"] = matching_index.sub_matching_index; - waypoint.values["waypoint_index"] = matching_index.point_index; - waypoint.values["alternatives_count"] = - sub_matchings[matching_index.sub_matching_index] - .alternatives_count[matching_index.point_index]; + waypoint.values.emplace("matchings_index", matching_index.sub_matching_index); + waypoint.values.emplace("waypoint_index", matching_index.point_index); + waypoint.values.emplace("alternatives_count", + sub_matchings[matching_index.sub_matching_index] + .alternatives_count[matching_index.point_index]); // waypoint indices need to be adjusted if route legs were collapsed // waypoint parameter assumes there is only one match object if (!parameters.waypoints.empty()) @@ -217,7 +217,7 @@ class MatchAPI final : public RouteAPI waypoint.values["waypoint_index"] = util::json::Null(); } } - waypoints.values.push_back(std::move(waypoint)); + waypoints.values.emplace_back(std::move(waypoint)); } return waypoints; diff --git a/include/engine/api/nearest_api.hpp b/include/engine/api/nearest_api.hpp index 32a5898d3..beae38cec 100644 --- a/include/engine/api/nearest_api.hpp +++ b/include/engine/api/nearest_api.hpp @@ -100,23 +100,23 @@ class NearestAPI final : public BaseAPI auto waypoint = MakeWaypoint({phantom_node}); util::json::Array nodes; + nodes.values.reserve(2); auto node_values = MakeNodes(phantom_node); - nodes.values.push_back(node_values.first); - nodes.values.push_back(node_values.second); - waypoint.values["nodes"] = std::move(nodes); - + nodes.values.emplace_back(node_values.first); + nodes.values.emplace_back(node_values.second); + waypoint.values.emplace("nodes", std::move(nodes)); return waypoint; }); - response.values["waypoints"] = std::move(waypoints); + response.values.emplace("waypoints", std::move(waypoints)); } - response.values["code"] = "Ok"; + response.values.emplace("code", "Ok"); auto data_timestamp = facade.GetTimestamp(); if (!data_timestamp.empty()) { - response.values["data_version"] = data_timestamp; + response.values.emplace("data_version", data_timestamp); } } diff --git a/include/engine/api/route_api.hpp b/include/engine/api/route_api.hpp index f833d603c..211be7f8d 100644 --- a/include/engine/api/route_api.hpp +++ b/include/engine/api/route_api.hpp @@ -110,14 +110,14 @@ class RouteAPI : public BaseAPI if (!parameters.skip_waypoints) { - response.values["waypoints"] = BaseAPI::MakeWaypoints(waypoint_candidates); + response.values.emplace("waypoints", BaseAPI::MakeWaypoints(waypoint_candidates)); } - response.values["routes"] = std::move(jsRoutes); - response.values["code"] = "Ok"; + response.values.emplace("routes", std::move(jsRoutes)); + response.values.emplace("code", "Ok"); auto data_timestamp = facade.GetTimestamp(); if (!data_timestamp.empty()) { - response.values["data_version"] = data_timestamp; + response.values.emplace("data_version", data_timestamp); } } @@ -340,8 +340,8 @@ class RouteAPI : public BaseAPI unpacked_path_segments, source_traversed_in_reverse, target_traversed_in_reverse); - std::vector legs = legs_info.first; - std::vector leg_geometries = legs_info.second; + std::vector &legs = legs_info.first; + std::vector &leg_geometries = legs_info.second; auto route = guidance::assembleRoute(legs); // Fill legs @@ -716,8 +716,8 @@ class RouteAPI : public BaseAPI unpacked_path_segments, source_traversed_in_reverse, target_traversed_in_reverse); - std::vector legs = legs_info.first; - std::vector leg_geometries = legs_info.second; + std::vector &legs = legs_info.first; + std::vector &leg_geometries = legs_info.second; auto route = guidance::assembleRoute(legs); boost::optional json_overview = @@ -784,49 +784,57 @@ class RouteAPI : public BaseAPI if (requested_annotations & RouteParameters::AnnotationsType::Speed) { double prev_speed = 0; - annotation.values["speed"] = GetAnnotations( - leg_geometry, - [&prev_speed](const guidance::LegGeometry::Annotation &anno) - { - if (anno.duration < std::numeric_limits::min()) - { - return prev_speed; - } - else - { - auto speed = std::round(anno.distance / anno.duration * 10.) / 10.; - prev_speed = speed; - return util::json::clamp_float(speed); - } - }); + annotation.values.emplace( + "speed", + GetAnnotations(leg_geometry, + [&prev_speed](const guidance::LegGeometry::Annotation &anno) + { + if (anno.duration < std::numeric_limits::min()) + { + return prev_speed; + } + else + { + auto speed = + std::round(anno.distance / anno.duration * 10.) / + 10.; + prev_speed = speed; + return util::json::clamp_float(speed); + } + })); } if (requested_annotations & RouteParameters::AnnotationsType::Duration) { - annotation.values["duration"] = + annotation.values.emplace( + "duration", GetAnnotations(leg_geometry, [](const guidance::LegGeometry::Annotation &anno) - { return anno.duration; }); + { return anno.duration; })); } if (requested_annotations & RouteParameters::AnnotationsType::Distance) { - annotation.values["distance"] = + annotation.values.emplace( + "distance", GetAnnotations(leg_geometry, [](const guidance::LegGeometry::Annotation &anno) - { return anno.distance; }); + { return anno.distance; })); } if (requested_annotations & RouteParameters::AnnotationsType::Weight) { - annotation.values["weight"] = GetAnnotations( - leg_geometry, - [](const guidance::LegGeometry::Annotation &anno) { return anno.weight; }); + annotation.values.emplace( + "weight", + GetAnnotations(leg_geometry, + [](const guidance::LegGeometry::Annotation &anno) + { return anno.weight; })); } if (requested_annotations & RouteParameters::AnnotationsType::Datasources) { - annotation.values["datasources"] = + annotation.values.emplace( + "datasources", GetAnnotations(leg_geometry, [](const guidance::LegGeometry::Annotation &anno) - { return anno.datasource; }); + { return anno.datasource; })); } if (requested_annotations & RouteParameters::AnnotationsType::Nodes) { @@ -837,7 +845,7 @@ class RouteAPI : public BaseAPI nodes.values.push_back( static_cast(facade.GetOSMNodeIDOfNode(node_id))); } - annotation.values["nodes"] = std::move(nodes); + annotation.values.emplace("nodes", std::move(nodes)); } // Add any supporting metadata, if needed if (requested_annotations & RouteParameters::AnnotationsType::Datasources) @@ -853,8 +861,8 @@ class RouteAPI : public BaseAPI break; datasource_names.values.push_back(std::string(facade.GetDatasourceName(i))); } - metadata.values["datasource_names"] = datasource_names; - annotation.values["metadata"] = metadata; + metadata.values.emplace("datasource_names", datasource_names); + annotation.values.emplace("metadata", metadata); } annotations.push_back(std::move(annotation)); diff --git a/include/engine/api/table_api.hpp b/include/engine/api/table_api.hpp index 10b57f985..82bfe4e38 100644 --- a/include/engine/api/table_api.hpp +++ b/include/engine/api/table_api.hpp @@ -179,7 +179,7 @@ class TableAPI final : public BaseAPI { if (!parameters.skip_waypoints) { - response.values["sources"] = MakeWaypoints(candidates); + response.values.emplace("sources", MakeWaypoints(candidates)); } number_of_sources = candidates.size(); } @@ -187,7 +187,7 @@ class TableAPI final : public BaseAPI { if (!parameters.skip_waypoints) { - response.values["sources"] = MakeWaypoints(candidates, parameters.sources); + response.values.emplace("sources", MakeWaypoints(candidates, parameters.sources)); } } @@ -195,7 +195,7 @@ class TableAPI final : public BaseAPI { if (!parameters.skip_waypoints) { - response.values["destinations"] = MakeWaypoints(candidates); + response.values.emplace("destinations", MakeWaypoints(candidates)); } number_of_destinations = candidates.size(); } @@ -203,34 +203,37 @@ class TableAPI final : public BaseAPI { if (!parameters.skip_waypoints) { - response.values["destinations"] = - MakeWaypoints(candidates, parameters.destinations); + response.values.emplace("destinations", + MakeWaypoints(candidates, parameters.destinations)); } } if (parameters.annotations & TableParameters::AnnotationsType::Duration) { - response.values["durations"] = - MakeDurationTable(tables.first, number_of_sources, number_of_destinations); + response.values.emplace( + "durations", + MakeDurationTable(tables.first, number_of_sources, number_of_destinations)); } if (parameters.annotations & TableParameters::AnnotationsType::Distance) { - response.values["distances"] = - MakeDistanceTable(tables.second, number_of_sources, number_of_destinations); + response.values.emplace( + "distances", + MakeDistanceTable(tables.second, number_of_sources, number_of_destinations)); } if (parameters.fallback_speed != from_alias(INVALID_FALLBACK_SPEED) && parameters.fallback_speed > 0) { - response.values["fallback_speed_cells"] = MakeEstimatesTable(fallback_speed_cells); + response.values.emplace("fallback_speed_cells", + MakeEstimatesTable(fallback_speed_cells)); } - response.values["code"] = "Ok"; + response.values.emplace("code", "Ok"); auto data_timestamp = facade.GetTimestamp(); if (!data_timestamp.empty()) { - response.values["data_version"] = data_timestamp; + response.values.emplace("data_version", data_timestamp); } } diff --git a/include/engine/api/trip_api.hpp b/include/engine/api/trip_api.hpp index 2f821cc1b..96486f026 100644 --- a/include/engine/api/trip_api.hpp +++ b/include/engine/api/trip_api.hpp @@ -79,14 +79,14 @@ class TripAPI final : public RouteAPI } if (!parameters.skip_waypoints) { - response.values["waypoints"] = MakeWaypoints(sub_trips, candidates); + response.values.emplace("waypoints", MakeWaypoints(sub_trips, candidates)); } - response.values["trips"] = std::move(routes); - response.values["code"] = "Ok"; + response.values.emplace("trips", std::move(routes)); + response.values.emplace("code", "Ok"); auto data_timestamp = facade.GetTimestamp(); if (!data_timestamp.empty()) { - response.values["data_version"] = data_timestamp; + response.values.emplace("data_version", data_timestamp); } } @@ -151,8 +151,8 @@ class TripAPI final : public RouteAPI BOOST_ASSERT(!trip_index.NotUsed()); auto waypoint = BaseAPI::MakeWaypoint(candidates[input_index]); - waypoint.values["trips_index"] = trip_index.sub_trip_index; - waypoint.values["waypoint_index"] = trip_index.point_index; + waypoint.values.emplace("trips_index", trip_index.sub_trip_index); + waypoint.values.emplace("waypoint_index", trip_index.point_index); waypoints.values.push_back(std::move(waypoint)); } diff --git a/include/engine/guidance/collapsing_utility.hpp b/include/engine/guidance/collapsing_utility.hpp index 9177ef8d4..174dba019 100644 --- a/include/engine/guidance/collapsing_utility.hpp +++ b/include/engine/guidance/collapsing_utility.hpp @@ -202,8 +202,8 @@ inline double totalTurnAngle(const RouteStep &entry_step, const RouteStep &exit_ if (entry_step.geometry_begin > exit_step.geometry_begin) return totalTurnAngle(exit_step, entry_step); - const auto exit_intersection = exit_step.intersections.front(); - const auto entry_intersection = entry_step.intersections.front(); + const auto &exit_intersection = exit_step.intersections.front(); + const auto &entry_intersection = entry_step.intersections.front(); if ((exit_intersection.out >= exit_intersection.bearings.size()) || (entry_intersection.in >= entry_intersection.bearings.size())) return entry_intersection.bearings[entry_intersection.out]; diff --git a/scripts/ci/download_gps_traces.py b/scripts/ci/download_gps_traces.py index 961acd532..7974833ac 100644 --- a/scripts/ci/download_gps_traces.py +++ b/scripts/ci/download_gps_traces.py @@ -4,41 +4,43 @@ import csv import sys import argparse -def get_osm_gps_traces(min_lon, min_lat, max_lon, max_lat): +def get_osm_gps_traces(bboxes): url = 'https://api.openstreetmap.org/api/0.6/trackpoints' traces = [] lon_step = 0.25 lat_step = 0.25 - - current_min_lon = min_lon - while current_min_lon < max_lon: - current_max_lon = min(current_min_lon + lon_step, max_lon) + for bbox in bboxes: + min_lon, min_lat, max_lon, max_lat = map(float, bbox.split(',')) - current_min_lat = min_lat - while current_min_lat < max_lat: - current_max_lat = min(current_min_lat + lat_step, max_lat) + current_min_lon = min_lon + while current_min_lon < max_lon: + current_max_lon = min(current_min_lon + lon_step, max_lon) - bbox = f'{current_min_lon},{current_min_lat},{current_max_lon},{current_max_lat}' - print(f"Requesting bbox: {bbox}", file=sys.stderr) - - params = { - 'bbox': bbox, - 'page': 0 - } - headers = { - 'Accept': 'application/xml' - } - - response = requests.get(url, params=params, headers=headers) - if response.status_code == 200: - traces.append(response.content) - else: - print(f"Error fetching data for bbox {bbox}: {response.status_code} {response.text}", file=sys.stderr) - - current_min_lat += lat_step - current_min_lon += lon_step + current_min_lat = min_lat + while current_min_lat < max_lat: + current_max_lat = min(current_min_lat + lat_step, max_lat) + + bbox_str = f'{current_min_lon},{current_min_lat},{current_max_lon},{current_max_lat}' + print(f"Requesting bbox: {bbox_str}", file=sys.stderr) + + params = { + 'bbox': bbox_str, + 'page': 0 + } + headers = { + 'Accept': 'application/xml' + } + + response = requests.get(url, params=params, headers=headers) + if response.status_code == 200: + traces.append(response.content) + else: + print(f"Error fetching data for bbox {bbox_str}: {response.status_code} {response.text}", file=sys.stderr) + + current_min_lat += lat_step + current_min_lon += lon_step return traces @@ -68,15 +70,12 @@ def save_to_csv(data, file): writer.writerows(data) if __name__ == '__main__': - parser = argparse.ArgumentParser(description='Fetch and output OSM GPS traces for a given bounding box.') - parser.add_argument('min_lon', type=float, help='Minimum longitude of the bounding box') - parser.add_argument('min_lat', type=float, help='Minimum latitude of the bounding box') - parser.add_argument('max_lon', type=float, help='Maximum longitude of the bounding box') - parser.add_argument('max_lat', type=float, help='Maximum latitude of the bounding box') + parser = argparse.ArgumentParser(description='Fetch and output OSM GPS traces for given bounding boxes.') + parser.add_argument('bboxes', nargs='+', help='Bounding boxes in the format min_lon,min_lat,max_lon,max_lat') args = parser.parse_args() - gpx_data_traces = get_osm_gps_traces(args.min_lon, args.min_lat, args.max_lon, args.max_lat) + gpx_data_traces = get_osm_gps_traces(args.bboxes) print(f"Collected {len(gpx_data_traces)} trace segments", file=sys.stderr) all_data = [] diff --git a/scripts/ci/run_benchmarks.sh b/scripts/ci/run_benchmarks.sh index ffde03bf0..2affff0f7 100755 --- a/scripts/ci/run_benchmarks.sh +++ b/scripts/ci/run_benchmarks.sh @@ -42,6 +42,12 @@ function run_benchmarks_for_folder { measure_peak_ram_and_time "$BINARIES_FOLDER/osrm-customize $FOLDER/data.osrm" "$RESULTS_FOLDER/osrm_customize.bench" measure_peak_ram_and_time "$BINARIES_FOLDER/osrm-contract $FOLDER/data.osrm" "$RESULTS_FOLDER/osrm_contract.bench" + for BENCH in nearest table trip route match; do + ./$BENCHMARKS_FOLDER/bench "$FOLDER/data.osrm" mld ~/gps_traces.csv ${BENCH} > "$RESULTS_FOLDER/random_${BENCH}_mld.bench" || true + ./$BENCHMARKS_FOLDER/bench "$FOLDER/data.osrm" ch ~/gps_traces.csv ${BENCH} > "$RESULTS_FOLDER/random_${BENCH}_ch.bench" || true + done + + for ALGORITHM in ch mld; do $BINARIES_FOLDER/osrm-routed --algorithm $ALGORITHM $FOLDER/data.osrm & OSRM_ROUTED_PID=$! @@ -59,8 +65,6 @@ function run_benchmarks_for_folder { kill -9 $OSRM_ROUTED_PID done - - } run_benchmarks_for_folder $1 "${1}_results" $2 diff --git a/src/benchmarks/CMakeLists.txt b/src/benchmarks/CMakeLists.txt index 86353dbbf..d2478ab4a 100644 --- a/src/benchmarks/CMakeLists.txt +++ b/src/benchmarks/CMakeLists.txt @@ -18,6 +18,7 @@ target_link_libraries(rtree-bench ${TBB_LIBRARIES} ${MAYBE_SHAPEFILE}) + add_executable(match-bench EXCLUDE_FROM_ALL ${MatchBenchmarkSources} @@ -35,6 +36,7 @@ add_executable(route-bench route.cpp $) + target_link_libraries(route-bench osrm ${BOOST_BASE_LIBRARIES} @@ -42,6 +44,18 @@ target_link_libraries(route-bench ${TBB_LIBRARIES} ${MAYBE_SHAPEFILE}) +add_executable(bench + EXCLUDE_FROM_ALL + bench.cpp + $) + +target_link_libraries(bench + osrm + ${BOOST_BASE_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT} + ${TBB_LIBRARIES} + ${MAYBE_SHAPEFILE}) + add_executable(json-render-bench EXCLUDE_FROM_ALL json_render.cpp @@ -85,5 +99,6 @@ add_custom_target(benchmarks packedvector-bench match-bench route-bench + bench json-render-bench alias-bench) diff --git a/src/benchmarks/bench.cpp b/src/benchmarks/bench.cpp new file mode 100644 index 000000000..ef53c398d --- /dev/null +++ b/src/benchmarks/bench.cpp @@ -0,0 +1,573 @@ +#include "osrm/match_parameters.hpp" +#include "osrm/nearest_parameters.hpp" +#include "osrm/table_parameters.hpp" +#include "osrm/trip_parameters.hpp" + +#include "engine/engine_config.hpp" +#include "util/coordinate.hpp" +#include "util/timing_util.hpp" + +#include "osrm/route_parameters.hpp" + +#include "osrm/coordinate.hpp" +#include "osrm/engine_config.hpp" +#include "osrm/json_container.hpp" + +#include "osrm/osrm.hpp" +#include "osrm/status.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace osrm; + +namespace +{ + +class GPSTraces +{ + private: + std::set trackIDs; + std::unordered_map> traces; + std::vector coordinates; + mutable std::mt19937 gen; + + public: + GPSTraces(int seed) : gen(std::random_device{}()) { gen.seed(seed); } + + bool readCSV(const std::string &filename) + { + std::ifstream file(filename); + if (!file.is_open()) + { + std::cerr << "Error opening file: " << filename << std::endl; + return false; + } + + std::string line; + std::getline(file, line); + + while (std::getline(file, line)) + { + std::istringstream ss(line); + std::string token; + + int trackID; + double latitude, longitude; + std::string time; + + std::getline(ss, token, ','); + trackID = std::stoi(token); + + std::getline(ss, token, ','); + latitude = std::stod(token); + + std::getline(ss, token, ','); + longitude = std::stod(token); + + // handle empty fields + if (std::getline(ss, token, ',')) + { + time = token; + } + + trackIDs.insert(trackID); + traces[trackID].emplace_back(osrm::util::Coordinate{ + osrm::util::FloatLongitude{longitude}, osrm::util::FloatLatitude{latitude}}); + coordinates.emplace_back(osrm::util::Coordinate{osrm::util::FloatLongitude{longitude}, + osrm::util::FloatLatitude{latitude}}); + } + + file.close(); + return true; + } + + const osrm::util::Coordinate &getRandomCoordinate() const + { + std::uniform_int_distribution<> dis(0, coordinates.size() - 1); + return coordinates[dis(gen)]; + } + + const std::vector &getRandomTrace() const + { + std::uniform_int_distribution<> dis(0, trackIDs.size() - 1); + auto it = trackIDs.begin(); + std::advance(it, dis(gen)); + return traces.at(*it); + } +}; + +class Statistics +{ + public: + void push(double timeMs) + { + times.push_back(timeMs); + sorted = false; + } + + double mean() { return sum() / times.size(); } + + double sum() + { + double sum = 0; + for (auto time : times) + { + sum += time; + } + return sum; + } + + double min() { return *std::min_element(times.begin(), times.end()); } + + double max() { return *std::max_element(times.begin(), times.end()); } + + double percentile(double p) + { + const auto × = getTimes(); + return times[static_cast(p * times.size())]; + } + + private: + std::vector getTimes() + { + if (!sorted) + { + std::sort(times.begin(), times.end()); + sorted = true; + } + return times; + } + + std::vector times; + + bool sorted = false; +}; + +std::ostream &operator<<(std::ostream &os, Statistics &statistics) +{ + os << std::fixed << std::setprecision(2); + os << "total: " << statistics.sum() << "ms" << std::endl; + os << "avg: " << statistics.mean() << "ms" << std::endl; + os << "min: " << statistics.min() << "ms" << std::endl; + os << "max: " << statistics.max() << "ms" << std::endl; + os << "p99: " << statistics.percentile(0.99) << "ms" << std::endl; + return os; +} + +void runRouteBenchmark(const OSRM &osrm, const GPSTraces &gpsTraces) +{ + struct Benchmark + { + std::string name; + size_t coordinates; + RouteParameters::OverviewType overview; + bool steps = false; + std::optional alternatives = std::nullopt; + std::optional radius = std::nullopt; + }; + + auto run_benchmark = [&](const Benchmark &benchmark) + { + Statistics statistics; + + auto NUM = 10000; + for (int i = 0; i < NUM; ++i) + { + RouteParameters params; + params.overview = benchmark.overview; + params.steps = benchmark.steps; + + for (size_t i = 0; i < benchmark.coordinates; ++i) + { + params.coordinates.push_back(gpsTraces.getRandomCoordinate()); + } + + if (benchmark.alternatives) + { + params.alternatives = *benchmark.alternatives; + } + + if (benchmark.radius) + { + params.radiuses = std::vector>( + params.coordinates.size(), boost::make_optional(*benchmark.radius)); + } + + engine::api::ResultT result = json::Object(); + TIMER_START(routes); + const auto rc = osrm.Route(params, result); + TIMER_STOP(routes); + + statistics.push(TIMER_MSEC(routes)); + + auto &json_result = std::get(result); + if (rc != Status::Ok || json_result.values.find("routes") == json_result.values.end()) + { + auto code = std::get(json_result.values["code"]).value; + if (code != "NoSegment" && code != "NoRoute") + { + throw std::runtime_error{"Couldn't route: " + code}; + } + } + } + std::cout << benchmark.name << std::endl; + std::cout << statistics << std::endl; + }; + + std::vector benchmarks = { + {"10000 routes, 3 coordinates, no alternatives, overview=full, steps=true", + 3, + RouteParameters::OverviewType::Full, + true, + std::nullopt}, + {"10000 routes, 2 coordinates, no alternatives, overview=full, steps=true", + 2, + RouteParameters::OverviewType::Full, + true, + std::nullopt}, + {"10000 routes, 2 coordinates, 3 alternatives, overview=full, steps=true", + 2, + RouteParameters::OverviewType::Full, + true, + 3}, + {"10000 routes, 3 coordinates, no alternatives, overview=false, steps=false", + 3, + RouteParameters::OverviewType::False, + false, + std::nullopt}, + {"10000 routes, 2 coordinates, no alternatives, overview=false, steps=false", + 2, + RouteParameters::OverviewType::False, + false, + std::nullopt}, + {"10000 routes, 2 coordinates, 3 alternatives, overview=false, steps=false", + 2, + RouteParameters::OverviewType::False, + false, + 3}, + {"10000 routes, 3 coordinates, no alternatives, overview=false, steps=false, radius=750", + 3, + RouteParameters::OverviewType::False, + false, + std::nullopt, + 750}, + {"10000 routes, 2 coordinates, no alternatives, overview=false, steps=false, radius=750", + 2, + RouteParameters::OverviewType::False, + false, + std::nullopt, + 750}, + {"10000 routes, 2 coordinates, 3 alternatives, overview=false, steps=false, radius=750", + 2, + RouteParameters::OverviewType::False, + false, + 3, + 750} + + }; + + for (const auto &benchmark : benchmarks) + { + run_benchmark(benchmark); + } +} + +void runMatchBenchmark(const OSRM &osrm, const GPSTraces &gpsTraces) +{ + struct Benchmark + { + std::string name; + std::optional radius = std::nullopt; + }; + + auto run_benchmark = [&](const Benchmark &benchmark) + { + Statistics statistics; + + auto NUM = 1000; + for (int i = 0; i < NUM; ++i) + { + engine::api::ResultT result = json::Object(); + + engine::api::MatchParameters params; + params.coordinates = gpsTraces.getRandomTrace(); + params.radiuses = {}; + if (benchmark.radius) + { + for (size_t index = 0; index < params.coordinates.size(); ++index) + { + params.radiuses.emplace_back(*benchmark.radius); + } + } + + TIMER_START(match); + const auto rc = osrm.Match(params, result); + TIMER_STOP(match); + + statistics.push(TIMER_MSEC(match)); + + auto &json_result = std::get(result); + if (rc != Status::Ok || + json_result.values.find("matchings") == json_result.values.end()) + { + auto code = std::get(json_result.values["code"]).value; + if (code != "NoSegment" && code != "NoMatch") + { + throw std::runtime_error{"Couldn't route: " + code}; + } + } + } + + std::cout << benchmark.name << std::endl; + std::cout << statistics << std::endl; + }; + + std::vector benchmarks = {{"1000 matches, default radius"}, + {"1000 matches, radius=10", 10}, + {"1000 matches, radius=20", 20}}; + + for (const auto &benchmark : benchmarks) + { + run_benchmark(benchmark); + } +} + +void runNearestBenchmark(const OSRM &osrm, const GPSTraces &gpsTraces) +{ + struct Benchmark + { + std::string name; + std::optional number_of_results = std::nullopt; + }; + + auto run_benchmark = [&](const Benchmark &benchmark) + { + Statistics statistics; + auto NUM = 10000; + for (int i = 0; i < NUM; ++i) + { + engine::api::ResultT result = json::Object(); + NearestParameters params; + params.coordinates.push_back(gpsTraces.getRandomCoordinate()); + + if (benchmark.number_of_results) + { + params.number_of_results = *benchmark.number_of_results; + } + + TIMER_START(nearest); + const auto rc = osrm.Nearest(params, result); + TIMER_STOP(nearest); + + statistics.push(TIMER_MSEC(nearest)); + + auto &json_result = std::get(result); + if (rc != Status::Ok || + json_result.values.find("waypoints") == json_result.values.end()) + { + auto code = std::get(json_result.values["code"]).value; + if (code != "NoSegment") + { + throw std::runtime_error{"Couldn't find nearest point"}; + } + } + } + + std::cout << benchmark.name << std::endl; + std::cout << statistics << std::endl; + }; + + std::vector benchmarks = {{"10000 nearest, number_of_results=1", 1}, + {"10000 nearest, number_of_results=5", 5}, + {"10000 nearest, number_of_results=10", 10}}; + + for (const auto &benchmark : benchmarks) + { + run_benchmark(benchmark); + } +} + +void runTripBenchmark(const OSRM &osrm, const GPSTraces &gpsTraces) +{ + struct Benchmark + { + std::string name; + size_t coordinates; + }; + + auto run_benchmark = [&](const Benchmark &benchmark) + { + Statistics statistics; + auto NUM = 1000; + for (int i = 0; i < NUM; ++i) + { + engine::api::ResultT result = json::Object(); + TripParameters params; + params.roundtrip = true; + + for (size_t i = 0; i < benchmark.coordinates; ++i) + { + params.coordinates.push_back(gpsTraces.getRandomCoordinate()); + } + + TIMER_START(trip); + const auto rc = osrm.Trip(params, result); + TIMER_STOP(trip); + + statistics.push(TIMER_MSEC(trip)); + + auto &json_result = std::get(result); + if (rc != Status::Ok || json_result.values.find("trips") == json_result.values.end()) + { + auto code = std::get(json_result.values["code"]).value; + if (code != "NoSegment") + { + throw std::runtime_error{"Couldn't find trip"}; + } + } + } + + std::cout << benchmark.name << std::endl; + std::cout << statistics << std::endl; + }; + + std::vector benchmarks = { + {"1000 trips, 3 coordinates", 3}, + {"1000 trips, 4 coordinates", 4}, + {"1000 trips, 5 coordinates", 5}, + }; + + for (const auto &benchmark : benchmarks) + { + run_benchmark(benchmark); + } +} +void runTableBenchmark(const OSRM &osrm, const GPSTraces &gpsTraces) +{ + struct Benchmark + { + std::string name; + size_t coordinates; + }; + + auto run_benchmark = [&](const Benchmark &benchmark) + { + Statistics statistics; + auto NUM = 250; + for (int i = 0; i < NUM; ++i) + { + engine::api::ResultT result = json::Object(); + TableParameters params; + + for (size_t i = 0; i < benchmark.coordinates; ++i) + { + params.coordinates.push_back(gpsTraces.getRandomCoordinate()); + } + + TIMER_START(table); + const auto rc = osrm.Table(params, result); + TIMER_STOP(table); + + statistics.push(TIMER_MSEC(table)); + + auto &json_result = std::get(result); + if (rc != Status::Ok || + json_result.values.find("durations") == json_result.values.end()) + { + auto code = std::get(json_result.values["code"]).value; + if (code != "NoSegment") + { + throw std::runtime_error{"Couldn't compute table"}; + } + } + } + + std::cout << benchmark.name << std::endl; + std::cout << statistics << std::endl; + }; + + std::vector benchmarks = {{"250 tables, 3 coordinates", 3}, + {"250 tables, 25 coordinates", 25}, + {"250 tables, 50 coordinates", 50}, + {"250 tables, 100 coordinates", 100}}; + + for (const auto &benchmark : benchmarks) + { + run_benchmark(benchmark); + } +} + +} // namespace + +int main(int argc, const char *argv[]) +try +{ + if (argc < 5) + { + std::cerr + << "Usage: " << argv[0] + << " data.osrm \n"; + return EXIT_FAILURE; + } + + // Configure based on a .osrm base path, and no datasets in shared mem from osrm-datastore + EngineConfig config; + config.storage_config = {argv[1]}; + config.algorithm = + std::string{argv[2]} == "mld" ? EngineConfig::Algorithm::MLD : EngineConfig::Algorithm::CH; + config.use_shared_memory = false; + + // Routing machine with several services (such as Route, Table, Nearest, Trip, Match) + OSRM osrm{config}; + + GPSTraces gpsTraces{42}; + gpsTraces.readCSV(argv[3]); + + const auto benchmarkToRun = std::string{argv[4]}; + + if (benchmarkToRun == "route") + { + runRouteBenchmark(osrm, gpsTraces); + } + else if (benchmarkToRun == "match") + { + runMatchBenchmark(osrm, gpsTraces); + } + else if (benchmarkToRun == "nearest") + { + runNearestBenchmark(osrm, gpsTraces); + } + else if (benchmarkToRun == "trip") + { + runTripBenchmark(osrm, gpsTraces); + } + else if (benchmarkToRun == "table") + { + runTableBenchmark(osrm, gpsTraces); + } + else + { + std::cerr << "Unknown benchmark: " << benchmarkToRun << std::endl; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} +catch (const std::exception &e) +{ + std::cerr << "Error: " << e.what() << std::endl; + return EXIT_FAILURE; +} diff --git a/src/engine/api/json_factory.cpp b/src/engine/api/json_factory.cpp index 673de0496..6765c9351 100644 --- a/src/engine/api/json_factory.cpp +++ b/src/engine/api/json_factory.cpp @@ -44,21 +44,22 @@ util::json::Array lanesFromIntersection(const guidance::IntermediateIntersection { BOOST_ASSERT(intersection.lanes.lanes_in_turn >= 1); util::json::Array result; + result.values.reserve(intersection.lane_description.size()); LaneID lane_id = intersection.lane_description.size(); for (const auto &lane_desc : intersection.lane_description) { --lane_id; util::json::Object lane; - lane.values["indications"] = toJSON(lane_desc); + lane.values.emplace("indications", toJSON(lane_desc)); if (lane_id >= intersection.lanes.first_lane_from_the_right && lane_id < intersection.lanes.first_lane_from_the_right + intersection.lanes.lanes_in_turn) - lane.values["valid"] = util::json::True(); + lane.values.emplace("valid", util::json::True()); else - lane.values["valid"] = util::json::False(); + lane.values.emplace("valid", util::json::False()); - result.values.push_back(lane); + result.values.emplace_back(std::move(lane)); } return result; @@ -77,6 +78,7 @@ std::string waypointTypeToString(const guidance::WaypointType waypoint_type) util::json::Value coordinateToLonLat(const util::Coordinate &coordinate) { util::json::Array array; + array.values.reserve(2); array.values.push_back(static_cast(util::toFloating(coordinate.lon))); array.values.push_back(static_cast(util::toFloating(coordinate.lat))); return util::json::Value{std::move(array)}; @@ -98,17 +100,20 @@ util::json::Object makeStepManeuver(const guidance::StepManeuver &maneuver) // These invalid responses should never happen: log if they do happen BOOST_ASSERT_MSG(maneuver_type != "invalid", "unexpected invalid maneuver type"); - step_maneuver.values["type"] = std::move(maneuver_type); + step_maneuver.values.emplace("type", std::move(maneuver_type)); if (detail::isValidModifier(maneuver)) - step_maneuver.values["modifier"] = - osrm::guidance::instructionModifierToString(maneuver.instruction.direction_modifier); + step_maneuver.values.emplace( + "modifier", + osrm::guidance::instructionModifierToString(maneuver.instruction.direction_modifier)); - step_maneuver.values["location"] = detail::coordinateToLonLat(maneuver.location); - step_maneuver.values["bearing_before"] = detail::roundAndClampBearing(maneuver.bearing_before); - step_maneuver.values["bearing_after"] = detail::roundAndClampBearing(maneuver.bearing_after); + step_maneuver.values.emplace("location", detail::coordinateToLonLat(maneuver.location)); + step_maneuver.values.emplace("bearing_before", + detail::roundAndClampBearing(maneuver.bearing_before)); + step_maneuver.values.emplace("bearing_after", + detail::roundAndClampBearing(maneuver.bearing_after)); if (maneuver.exit != 0) - step_maneuver.values["exit"] = maneuver.exit; + step_maneuver.values.emplace("exit", maneuver.exit); return step_maneuver; } @@ -137,16 +142,16 @@ util::json::Object makeIntersection(const guidance::IntermediateIntersection &in return util::json::False(); }); - result.values["location"] = detail::coordinateToLonLat(intersection.location); - result.values["bearings"] = bearings; - result.values["entry"] = entry; + result.values.emplace("location", detail::coordinateToLonLat(intersection.location)); + result.values.emplace("bearings", bearings); + result.values.emplace("entry", entry); if (intersection.in != guidance::IntermediateIntersection::NO_INDEX) - result.values["in"] = intersection.in; + result.values.emplace("in", intersection.in); if (intersection.out != guidance::IntermediateIntersection::NO_INDEX) - result.values["out"] = intersection.out; + result.values.emplace("out", intersection.out); if (detail::hasValidLanes(intersection)) - result.values["lanes"] = detail::lanesFromIntersection(intersection); + result.values.emplace("lanes", detail::lanesFromIntersection(intersection)); if (!intersection.classes.empty()) { @@ -157,7 +162,7 @@ util::json::Object makeIntersection(const guidance::IntermediateIntersection &in std::back_inserter(classes.values), [](const std::string &class_name) { return util::json::String{class_name}; }); - result.values["classes"] = std::move(classes); + result.values.emplace("classes", std::move(classes)); } return result; @@ -166,39 +171,44 @@ util::json::Object makeIntersection(const guidance::IntermediateIntersection &in util::json::Object makeRouteStep(guidance::RouteStep step, util::json::Value geometry) { util::json::Object route_step; - route_step.values["distance"] = std::round(step.distance * 10) / 10.; - route_step.values["duration"] = step.duration; - route_step.values["weight"] = step.weight; - route_step.values["name"] = std::move(step.name); + route_step.values.reserve(15); + + route_step.values.emplace("distance", std::round(step.distance * 10) / 10.); + route_step.values.emplace("duration", step.duration); + route_step.values.emplace("weight", step.weight); + route_step.values.emplace("name", step.name); + if (!step.ref.empty()) - route_step.values["ref"] = std::move(step.ref); + route_step.values.emplace("ref", step.ref); if (!step.pronunciation.empty()) - route_step.values["pronunciation"] = std::move(step.pronunciation); + route_step.values.emplace("pronunciation", step.pronunciation); if (!step.destinations.empty()) - route_step.values["destinations"] = std::move(step.destinations); + route_step.values.emplace("destinations", step.destinations); if (!step.exits.empty()) - route_step.values["exits"] = std::move(step.exits); + route_step.values.emplace("exits", step.exits); if (!step.rotary_name.empty()) { - route_step.values["rotary_name"] = std::move(step.rotary_name); + route_step.values.emplace("rotary_name", step.rotary_name); if (!step.rotary_pronunciation.empty()) { - route_step.values["rotary_pronunciation"] = std::move(step.rotary_pronunciation); + route_step.values.emplace("rotary_pronunciation", step.rotary_pronunciation); } } - route_step.values["mode"] = extractor::travelModeToString(step.mode); - route_step.values["maneuver"] = makeStepManeuver(step.maneuver); - route_step.values["geometry"] = std::move(geometry); - route_step.values["driving_side"] = step.is_left_hand_driving ? "left" : "right"; + route_step.values.emplace("mode", extractor::travelModeToString(step.mode)); + route_step.values.emplace("maneuver", makeStepManeuver(step.maneuver)); + route_step.values.emplace("geometry", std::move(geometry)); + route_step.values.emplace("driving_side", step.is_left_hand_driving ? "left" : "right"); util::json::Array intersections; intersections.values.reserve(step.intersections.size()); + std::transform(step.intersections.begin(), step.intersections.end(), std::back_inserter(intersections.values), makeIntersection); - route_step.values["intersections"] = std::move(intersections); + + route_step.values.emplace("intersections", std::move(intersections)); return route_step; } @@ -209,14 +219,16 @@ util::json::Object makeRoute(const guidance::Route &route, const char *weight_name) { util::json::Object json_route; - json_route.values["distance"] = route.distance; - json_route.values["duration"] = route.duration; - json_route.values["weight"] = route.weight; - json_route.values["weight_name"] = weight_name; - json_route.values["legs"] = std::move(legs); + json_route.values.reserve(6); + + json_route.values.emplace("distance", route.distance); + json_route.values.emplace("duration", route.duration); + json_route.values.emplace("weight", route.weight); + json_route.values.emplace("weight_name", weight_name); + json_route.values.emplace("legs", std::move(legs)); if (geometry) { - json_route.values["geometry"] = *std::move(geometry); + json_route.values.emplace("geometry", *std::move(geometry)); } return json_route; } @@ -225,9 +237,11 @@ util::json::Object makeWaypoint(const util::Coordinate &location, const double &distance, std::string name) { util::json::Object waypoint; - waypoint.values["location"] = detail::coordinateToLonLat(location); - waypoint.values["name"] = std::move(name); - waypoint.values["distance"] = distance; + waypoint.values.reserve(3); + + waypoint.values.emplace("location", detail::coordinateToLonLat(location)); + waypoint.values.emplace("name", std::move(name)); + waypoint.values.emplace("distance", distance); return waypoint; } @@ -237,26 +251,29 @@ util::json::Object makeWaypoint(const util::Coordinate &location, const Hint &location_hints) { auto waypoint = makeWaypoint(location, distance, std::move(name)); - waypoint.values["hint"] = location_hints.ToBase64(); + waypoint.values.reserve(1); + waypoint.values.emplace("hint", location_hints.ToBase64()); return waypoint; } util::json::Object makeRouteLeg(guidance::RouteLeg leg, util::json::Array steps) { util::json::Object route_leg; - route_leg.values["distance"] = leg.distance; - route_leg.values["duration"] = leg.duration; - route_leg.values["weight"] = leg.weight; - route_leg.values["summary"] = std::move(leg.summary); - route_leg.values["steps"] = std::move(steps); + route_leg.values.reserve(5); + + route_leg.values.emplace("distance", leg.distance); + route_leg.values.emplace("duration", leg.duration); + route_leg.values.emplace("weight", leg.weight); + route_leg.values.emplace("summary", std::move(leg.summary)); + route_leg.values.emplace("steps", std::move(steps)); return route_leg; } - util::json::Object makeRouteLeg(guidance::RouteLeg leg, util::json::Array steps, util::json::Object annotation) { util::json::Object route_leg = makeRouteLeg(std::move(leg), std::move(steps)); - route_leg.values["annotation"] = std::move(annotation); + route_leg.values.reserve(1); + route_leg.values.emplace("annotation", std::move(annotation)); return route_leg; } diff --git a/test/data/poland_gps_traces.csv.gz b/test/data/poland_gps_traces.csv.gz new file mode 100644 index 000000000..6d1e8a16e Binary files /dev/null and b/test/data/poland_gps_traces.csv.gz differ