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

some memory and concurrency improvements #612

Merged
merged 24 commits into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b830bc0
extract ClipCache to own file
cldellow Dec 10, 2023
8576cfd
templatize ClipCache, apply to MultiLineStrings
cldellow Dec 10, 2023
72759d5
housekeeping: move test, minunit
cldellow Dec 10, 2023
605b13b
--log-tile-timings: verbose timing logs
cldellow Dec 10, 2023
b66da75
more, smaller caches; run destructors outside lock
cldellow Dec 11, 2023
036c7cf
use explicit types
cldellow Dec 11, 2023
0be9ef5
don't populate unnecessary vectors
cldellow Dec 11, 2023
c87d567
reserve vectors appropriately
cldellow Dec 11, 2023
3e7b9b6
don't eagerly call way:IsClosed()
cldellow Dec 11, 2023
04f3d1d
remove locks from geometry stores
cldellow Dec 11, 2023
6b30836
increase attributestore shards
cldellow Dec 11, 2023
59625cd
read_pbf: less lock contention on status
cldellow Dec 13, 2023
38a38aa
tile_worker: do syscall 1x/thread, not 1x/tile
cldellow Dec 13, 2023
2f94b1f
tilemaker: avoid lock contention on status update
cldellow Dec 13, 2023
8cf25b7
Revert "don't eagerly call way:IsClosed()"
cldellow Dec 13, 2023
f74c963
Merge remote-tracking branch 'origin/master' into perf-tweaks-2
cldellow Dec 13, 2023
f8c558a
update ifdef guard, add comments
cldellow Dec 13, 2023
eacdff3
lazy way geometries
cldellow Dec 13, 2023
7f7eb65
improve tile coordinate generation
cldellow Dec 14, 2023
22e6694
SortedNodeStore: only do arena allocations
cldellow Dec 14, 2023
55e50ea
remove TODO
cldellow Dec 14, 2023
b4462ff
fix Windows build
cldellow Dec 14, 2023
523d942
sigh
cldellow Dec 14, 2023
0fca3eb
fix bounds check
cldellow Dec 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ include/vector_tile.pb.h
# downloaded data
coastline
landcover
/test.*
33 changes: 16 additions & 17 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -87,33 +87,32 @@ ADD_CUSTOM_COMMAND(OUTPUT osmformat.pb.cc osmformat.pb.h

file(GLOB tilemaker_src_files
src/attribute_store.cpp
src/geom.cpp
src/mbtiles.cpp
src/osm_mem_tiles.cpp
src/output_object.cpp
src/read_pbf.cpp
src/shared_data.cpp
src/tile_data.cpp
src/tile_worker.cpp
src/coordinates.cpp
src/coordinates_geom.cpp
src/external/streamvbyte_decode.cc
src/external/streamvbyte_encode.cc
src/external/streamvbyte_zigzag.cc
src/geom.cpp
src/helpers.cpp
src/mbtiles.cpp
src/mmap_allocator.cpp
src/node_stores.cpp
src/osm_lua_processing.cpp
src/osm_mem_tiles.cpp
src/osm_store.cpp
src/mmap_allocator.cpp
src/output_object.cpp
src/pbf_blocks.cpp
src/read_pbf.cpp
src/read_shp.cpp
src/shared_data.cpp
src/shp_mem_tiles.cpp
src/tilemaker.cpp
src/write_geometry.cpp
src/node_stores.cpp
src/coordinates_geom.cpp
src/sorted_node_store.cpp
src/sorted_way_store.cpp
src/tile_data.cpp
src/tilemaker.cpp
src/tile_worker.cpp
src/way_stores.cpp
src/external/streamvbyte_decode.cc
src/external/streamvbyte_encode.cc
src/external/streamvbyte_zigzag.cc

src/write_geometry.cpp
)
add_executable(tilemaker vector_tile.pb.cc osmformat.pb.cc ${tilemaker_src_files})
target_include_directories(tilemaker PRIVATE include)
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ test_sorted_way_store: \
src/mmap_allocator.o \
src/sorted_way_store.o \
src/sorted_way_store.test.o
$(CXX) $(CXXFLAGS) -o test $^ $(INC) $(LIB) $(LDFLAGS) && ./test
$(CXX) $(CXXFLAGS) -o test.sorted_way_store $^ $(INC) $(LIB) $(LDFLAGS) && ./test.sorted_way_store


%.o: %.cpp
Expand Down
5 changes: 2 additions & 3 deletions include/attribute_store.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,8 @@ struct AttributePair {
// It should be at least 2x the number of your cores -- 256 shards is probably
// reasonable for most people.
//
// We also reserve the bottom shard for the hot pool. Since a shard is 16M entries,
// but the hot pool is only 64KB entries, we're wasting a little bit of key space.
#define SHARD_BITS 8
// We also reserve the bottom shard for the hot pool.
#define SHARD_BITS 14
#define ATTRIBUTE_SHARDS (1 << SHARD_BITS)

class AttributePairStore {
Expand Down
79 changes: 79 additions & 0 deletions include/clip_cache.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#ifndef _CLIP_CACHE_H
#define _CLIP_CACHE_H

#include "coordinates.h"
#include "coordinates_geom.h"
#include "geom.h"
#include <mutex>

class TileBbox;

template <class T>
class ClipCache {
public:
ClipCache(size_t threadNum, unsigned int baseZoom):
baseZoom(baseZoom),
clipCache(threadNum * 16),
clipCacheMutex(threadNum * 16),
clipCacheSize(threadNum * 16) {
}

const std::shared_ptr<T> get(uint zoom, TileCoordinate x, TileCoordinate y, NodeID objectID) const{
// Look for a previously clipped version at z-1, z-2, ...

std::lock_guard<std::mutex> lock(clipCacheMutex[objectID % clipCacheMutex.size()]);
while (zoom > 0) {
zoom--;
x /= 2;
y /= 2;
const auto& cache = clipCache[objectID % clipCache.size()];
const auto& rv = cache.find(std::make_tuple(zoom, TileCoordinates(x, y), objectID));
if (rv != cache.end()) {
return rv->second;
}
}

return nullptr;
}

void add(const TileBbox& bbox, const NodeID objectID, const T& output) {
// The point of caching is to reuse the clip, so caching at the terminal zoom is
// pointless.
if (bbox.zoom == baseZoom)
return;

std::shared_ptr<T> copy = std::make_shared<T>();
boost::geometry::assign(*copy, output);

size_t index = objectID % clipCacheMutex.size();
std::vector<std::shared_ptr<T>> objects;
std::lock_guard<std::mutex> lock(clipCacheMutex[index]);
auto& cache = clipCache[index];
// Reset the cache periodically so it doesn't grow without bound.
//
// I also tried boost's lru_cache -- but it seemed to perform worse, maybe
// due to the bookkeeping? We could try authoring a bounded map that
// evicts in FIFO order, which will have less bookkeeping.
clipCacheSize[index]++;
if (clipCacheSize[index] > 1024) {
clipCacheSize[index] = 0;
// Copy the map's contents to a vector so that calling .clear()
// and releasing the lock can happen separately from running the
// destructors of all of the objects.
objects.reserve(1025);
for (const auto& x : cache)
objects.push_back(x.second);
cache.clear();
}

cache[std::make_tuple(bbox.zoom, bbox.index, objectID)] = copy;
}

private:
unsigned int baseZoom;
std::vector<std::map<std::tuple<uint16_t, TileCoordinates, NodeID>, std::shared_ptr<T>>> clipCache;
mutable std::vector<std::mutex> clipCacheMutex;
std::vector<size_t> clipCacheSize;
};

#endif
File renamed without changes.
53 changes: 53 additions & 0 deletions include/geometry_cache.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#ifndef _GEOMETRY_CACHE_H
#define _GEOMETRY_CACHE_H

// A small class to map from ID -> geometry, with
// a bounded size and LRU-ish behaviour.
//
// Not safe for multi-threaded use!

#include "coordinates.h"
#include <memory>

#define NUM_BUCKETS 256
// Keep bucket size small so linear search is fast
#define BUCKET_SIZE 32


template <class T>
class GeometryCache {
public:
GeometryCache():
offsets(NUM_BUCKETS),
ids(NUM_BUCKETS * BUCKET_SIZE),
geometries(NUM_BUCKETS * BUCKET_SIZE) {}

T* get(NodeID objectID) {
const size_t bucket = objectID % NUM_BUCKETS;

for (size_t i = bucket * BUCKET_SIZE; i < (bucket + 1) * BUCKET_SIZE; i++)
if (ids[i] == objectID)
return geometries[i].get();

return nullptr;
}

void add(NodeID objectID, std::shared_ptr<T> geom) {
const size_t bucket = objectID % NUM_BUCKETS;
size_t offset = bucket * BUCKET_SIZE + offsets[bucket];
geometries[offset] = geom;
ids[offset] = objectID;
offsets[bucket]++;

if (offsets[bucket] == BUCKET_SIZE)
offsets[bucket] = 0;
}

private:
std::vector<uint8_t> offsets;
std::vector<NodeID> ids;
// CONSIDER: we could use a raw pointer - getOrBuildLinestring controls the lifetime
std::vector<std::shared_ptr<Linestring>> geometries;
};

#endif
82 changes: 82 additions & 0 deletions include/leased_store.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#ifndef _LEASED_STORE_H
#define _LEASED_STORE_H

#include "tile_data.h"

// LeasedStore implements "leases" for generated geometries.
//
// When Lua code calls Layer(...), we need to store the point, linestring,
// multilinestring, polygon geometry.
//
// Previously, we had code that would lock on each insertion:
//
// lock_guard<mutex> lock(mutex);
// insert(...)
//
// Instead, store leases let each of N threads claim a range of 1/N of the IDs.
// Then each thread can insert into their own stores without taking additional
// locks.

template<typename S>
std::vector<std::pair<size_t, S*>>& getAvailableLeases(TileDataSource* source) {
throw std::runtime_error("you need to specialize this");
}

template<>
inline std::vector<std::pair<size_t, TileDataSource::point_store_t*>>& getAvailableLeases(TileDataSource* source) {
return source->availablePointStoreLeases;
}

template<>
inline std::vector<std::pair<size_t, TileDataSource::linestring_store_t*>>& getAvailableLeases(TileDataSource* source) {
return source->availableLinestringStoreLeases;
}

template<>
inline std::vector<std::pair<size_t, TileDataSource::multi_linestring_store_t*>>& getAvailableLeases(TileDataSource* source) {
return source->availableMultiLinestringStoreLeases;
}

template<>
inline std::vector<std::pair<size_t, TileDataSource::multi_polygon_store_t*>>& getAvailableLeases(TileDataSource* source) {
return source->availableMultiPolygonStoreLeases;
}


template <class T>
class LeasedStore {
std::vector<std::pair<TileDataSource*, std::pair<size_t, T*>>> leases;

public:
~LeasedStore() {
for (const auto& lease : leases) {
auto source = lease.first;
std::lock_guard<std::mutex> lock(source->storeMutex);

std::vector<std::pair<size_t, T*>>& availableLeases = getAvailableLeases<T>(source);
availableLeases.push_back(lease.second);
}
}

std::pair<size_t, T*> get(TileDataSource* source) {
for (const auto& lease : leases) {
if (lease.first == source)
return lease.second;
}

std::lock_guard<std::mutex> lock(source->storeMutex);

std::vector<std::pair<size_t, T*>>& availableLeases = getAvailableLeases<T>(source);

if (availableLeases.empty())
throw std::runtime_error("fatal: no available stores to lease");

std::pair<size_t, T*> entry = availableLeases.back();
availableLeases.pop_back();

leases.push_back(std::make_pair(source, entry));
return entry;
}

};
#endif
42 changes: 25 additions & 17 deletions include/osm_lua_processing.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,15 @@ class OsmLuaProcessing {
// ---- initialization routines

OsmLuaProcessing(
OSMStore &osmStore,
const class Config &configIn, class LayerDefinition &layers,
OSMStore &osmStore,
const class Config &configIn,
class LayerDefinition &layers,
const std::string &luaFile,
const class ShpMemTiles &shpMemTiles,
class OsmMemTiles &osmMemTiles,
AttributeStore &attributeStore);
AttributeStore &attributeStore,
bool materializeGeometries
);
~OsmLuaProcessing();

// ---- Helpers provided for main routine
Expand Down Expand Up @@ -77,7 +80,7 @@ class OsmLuaProcessing {
void setNode(NodeID id, LatpLon node, const tag_map_t &tags);

/// \brief We are now processing a way
void setWay(WayID wayId, LatpLonVec const &llVec, const tag_map_t &tags);
bool setWay(WayID wayId, LatpLonVec const &llVec, const tag_map_t &tags);

/** \brief We are now processing a relation
* (note that we store relations as ways with artificial IDs, and that
Expand Down Expand Up @@ -123,32 +126,34 @@ class OsmLuaProcessing {
std::vector<double> Centroid();
Point calculateCentroid();

enum class CorrectGeometryResult: char { Invalid = 0, Valid = 1, Corrected = 2 };
// ---- Requests from Lua to write this way/node to a vector tile's Layer
template<class GeometryT>
bool CorrectGeometry(GeometryT &geom)
{
template<class GeometryT>
CorrectGeometryResult CorrectGeometry(GeometryT &geom)
{
#if BOOST_VERSION >= 105800
geom::validity_failure_type failure = geom::validity_failure_type::no_failure;
if (isRelation && !geom::is_valid(geom,failure)) {
if (verbose) std::cout << "Relation " << originalOsmID << " has " << boost_validity_error(failure) << std::endl;
} else if (isWay && !geom::is_valid(geom,failure)) {
if (verbose && failure!=22) std::cout << "Way " << originalOsmID << " has " << boost_validity_error(failure) << std::endl;
}
geom::validity_failure_type failure = geom::validity_failure_type::no_failure;
if (isRelation && !geom::is_valid(geom,failure)) {
if (verbose) std::cout << "Relation " << originalOsmID << " has " << boost_validity_error(failure) << std::endl;
} else if (isWay && !geom::is_valid(geom,failure)) {
if (verbose && failure!=22) std::cout << "Way " << originalOsmID << " has " << boost_validity_error(failure) << std::endl;
}

if (failure==boost::geometry::failure_spikes)
geom::remove_spikes(geom);
if (failure == boost::geometry::failure_few_points)
return false;
if (failure == boost::geometry::failure_few_points)
return CorrectGeometryResult::Invalid;
if (failure) {
std::time_t start = std::time(0);
make_valid(geom);
if (verbose && std::time(0)-start>3) {
std::cout << (isRelation ? "Relation " : "Way ") << originalOsmID << " took " << (std::time(0)-start) << " seconds to correct" << std::endl;
}
return CorrectGeometryResult::Corrected;
}
#endif
return true;
}
return CorrectGeometryResult::Valid;
}

// Add layer
void Layer(const std::string &layerName, bool area);
Expand Down Expand Up @@ -256,6 +261,9 @@ class OsmLuaProcessing {
const boost::container::flat_map<std::string, std::string>* currentTags;

std::vector<OutputObject> finalizeOutputs();

bool materializeGeometries;
bool wayEmitted;
};

#endif //_OSM_LUA_PROCESSING_H
Loading
Loading