From cbd06b1c4da4a1016d067784f0a21bd1db283d32 Mon Sep 17 00:00:00 2001 From: Colin Dellow Date: Sun, 5 Nov 2023 12:44:05 -0500 Subject: [PATCH 1/2] shard the node store to reduce memory usage It looks like PR #499 adopts 36 bits as the max size of an OSM ID. The NodeStore currently uses a full 64 bits for these IDs. This PR changes it to shard the nodes across 16 collections (4 bits) and then store only the last 32 bits in the collection itself. This reduces memory usage for the NodeStore by 25%, without much impact on runtime. The CompactNodeStore is still much better, as it has no overhead and constant time lookups -- but I'm often lazy and not using a renumbered PBF file. --- include/geom.h | 4 ++++ include/osm_store.h | 58 ++++++++++++++++++++++++++++++++++++--------- src/osm_store.cpp | 10 ++++---- 3 files changed, 57 insertions(+), 15 deletions(-) diff --git a/include/geom.h b/include/geom.h index f946927f..5d1ad1b6 100644 --- a/include/geom.h +++ b/include/geom.h @@ -47,6 +47,10 @@ typedef boost::variant Geometry; typedef std::pair IndexValue; typedef boost::geometry::index::rtree< IndexValue, boost::geometry::index::quadratic<16> > RTree; +// A 36-bit integer can store all OSM node IDs; we represent this as 16 collections +// of 32-bit integers. +#define NODE_SHARDS 16 +typedef uint32_t ShardedNodeID; typedef uint64_t NodeID; typedef uint64_t WayID; diff --git a/include/osm_store.h b/include/osm_store.h index 2b9cdf64..912f3f8f 100644 --- a/include/osm_store.h +++ b/include/osm_store.h @@ -79,12 +79,19 @@ class NodeStore public: using element_t = std::pair; + using internal_element_t = std::pair; using map_t = std::deque>; void reopen() { std::lock_guard lock(mutex); - mLatpLons = std::make_unique(); + for (auto i = 0; i < mLatpLons.size(); i++) + mLatpLons[i]->clear(); + + mLatpLons.clear(); + for (auto i = 0; i < NODE_SHARDS; i++) { + mLatpLons.push_back(std::make_unique()); + } } // @brief Lookup a latp/lon pair @@ -92,11 +99,14 @@ class NodeStore // @return Latp/lon pair // @exception NotFound LatpLon at(NodeID i) const { - auto iter = std::lower_bound(mLatpLons->begin(), mLatpLons->end(), i, [](auto const &e, auto i) { + auto shard = mLatpLons[shardPart(i)]; + auto id = idPart(i); + + auto iter = std::lower_bound(shard->begin(), shard->end(), id, [](auto const &e, auto i) { return e.first < i; }); - if(iter == mLatpLons->end() || iter->first != i) + if(iter == shard->end() || iter->first != id) throw std::out_of_range("Could not find node with id " + std::to_string(i)); return iter->second; @@ -105,7 +115,11 @@ class NodeStore // @brief Return the number of stored items size_t size() const { std::lock_guard lock(mutex); - return mLatpLons->size(); + uint64_t size = 0; + for (auto i = 0; i < mLatpLons.size(); i++) + size += mLatpLons[i]->size(); + + return size; } // @brief Insert a latp/lon pair. @@ -114,27 +128,49 @@ class NodeStore // @invariant The OSM ID i must be larger than previously inserted OSM IDs of nodes // (though unnecessarily for current impl, future impl may impose that) void insert_back(NodeID i, LatpLon coord) { - mLatpLons->push_back(std::make_pair(i, coord)); + mLatpLons[shardPart(i)]->push_back(std::make_pair(idPart(i), coord)); } void insert_back(std::vector const &element) { + uint32_t newEntries[NODE_SHARDS] = {}; + + // Before taking the lock, do a pass to find out how much + // to grow each backing collection + for (auto it = element.begin(); it != element.end(); it++) { + newEntries[shardPart(it->first)]++; + } + std::lock_guard lock(mutex); - auto i = mLatpLons->size(); - mLatpLons->resize(i + element.size()); - std::copy(element.begin(), element.end(), mLatpLons->begin() + i); + for (auto i = 0; i < NODE_SHARDS; i++) { + if (newEntries[i] == 0) continue; + auto size = mLatpLons[i]->size(); + mLatpLons[i]->resize(size + newEntries[i]); + } + + for (auto it = element.begin(); it != element.end(); it++) { + insert_back(it->first, it->second); + } } // @brief Make the store empty void clear() { - std::lock_guard lock(mutex); - mLatpLons->clear(); + reopen(); } void sort(unsigned int threadNum); private: mutable std::mutex mutex; - std::shared_ptr mLatpLons; + std::vector> mLatpLons; + + uint32_t shardPart(NodeID id) const { + uint32_t rv = id >> 32; + return rv; + } + + uint32_t idPart(NodeID id) const { + return id; + } }; class CompactNodeStore diff --git a/src/osm_store.cpp b/src/osm_store.cpp index b9843ee4..75a6aadc 100644 --- a/src/osm_store.cpp +++ b/src/osm_store.cpp @@ -262,10 +262,12 @@ void void_mmap_allocator::destroy(void *p) void NodeStore::sort(unsigned int threadNum) { std::lock_guard lock(mutex); - boost::sort::block_indirect_sort( - mLatpLons->begin(), mLatpLons->end(), - [](auto const &a, auto const &b) { return a.first < b.first; }, - threadNum); + for (auto i = 0; i < NODE_SHARDS; i++) { + boost::sort::block_indirect_sort( + mLatpLons[i]->begin(), mLatpLons[i]->end(), + [](auto const &a, auto const &b) { return a.first < b.first; }, + threadNum); + } } void WayStore::sort(unsigned int threadNum) { From dc7089879e9f9be764eea82840f9676619a32d28 Mon Sep 17 00:00:00 2001 From: Colin Dellow Date: Tue, 7 Nov 2023 23:19:22 -0500 Subject: [PATCH 2/2] Actually use the smaller data structures Also fix the way entries are inserted into the deques - I'd really done a good job of screwing up the PR. :) --- include/osm_store.h | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/include/osm_store.h b/include/osm_store.h index 912f3f8f..8e7f2796 100644 --- a/include/osm_store.h +++ b/include/osm_store.h @@ -80,7 +80,7 @@ class NodeStore public: using element_t = std::pair; using internal_element_t = std::pair; - using map_t = std::deque>; + using map_t = std::deque>; void reopen() { @@ -122,17 +122,9 @@ class NodeStore return size; } - // @brief Insert a latp/lon pair. - // @param i OSM ID of a node - // @param coord a latp/lon pair to be inserted - // @invariant The OSM ID i must be larger than previously inserted OSM IDs of nodes - // (though unnecessarily for current impl, future impl may impose that) - void insert_back(NodeID i, LatpLon coord) { - mLatpLons[shardPart(i)]->push_back(std::make_pair(idPart(i), coord)); - } - void insert_back(std::vector const &element) { uint32_t newEntries[NODE_SHARDS] = {}; + std::vector iterators; // Before taking the lock, do a pass to find out how much // to grow each backing collection @@ -142,13 +134,17 @@ class NodeStore std::lock_guard lock(mutex); for (auto i = 0; i < NODE_SHARDS; i++) { - if (newEntries[i] == 0) continue; auto size = mLatpLons[i]->size(); mLatpLons[i]->resize(size + newEntries[i]); + iterators.push_back(mLatpLons[i]->begin() + size); } for (auto it = element.begin(); it != element.end(); it++) { - insert_back(it->first, it->second); + uint32_t shard = shardPart(it->first); + uint32_t id = idPart(it->first); + + *iterators[shard] = std::make_pair(id, it->second); + iterators[shard]++; } } @@ -505,12 +501,6 @@ class OSMStore void shapes_sort(unsigned int threadNum = 1); void generated_sort(unsigned int threadNum = 1); - void nodes_insert_back(NodeID i, LatpLon coord) { - if(!use_compact_nodes) - nodes.insert_back(i, coord); - else - compact_nodes.insert_back(i, coord); - } void nodes_insert_back(std::vector const &new_nodes) { if(!use_compact_nodes) nodes.insert_back(new_nodes);