core: do not use JSON serialization for layer grouping key (#4075)
This commit is contained in:
@@ -8,6 +8,7 @@ add_library(
|
||||
${PROJECT_SOURCE_DIR}/benchmark/parse/filter.benchmark.cpp
|
||||
${PROJECT_SOURCE_DIR}/benchmark/parse/tile_mask.benchmark.cpp
|
||||
${PROJECT_SOURCE_DIR}/benchmark/parse/vector_tile.benchmark.cpp
|
||||
${PROJECT_SOURCE_DIR}/benchmark/renderer/group_layers.benchmark.cpp
|
||||
${PROJECT_SOURCE_DIR}/benchmark/src/mbgl/benchmark/benchmark.cpp
|
||||
${PROJECT_SOURCE_DIR}/benchmark/storage/offline_database.benchmark.cpp
|
||||
${PROJECT_SOURCE_DIR}/benchmark/util/tilecover.benchmark.cpp
|
||||
|
||||
1
benchmark/fixtures/renderer/liberty.json
Normal file
1
benchmark/fixtures/renderer/liberty.json
Normal file
File diff suppressed because one or more lines are too long
191
benchmark/renderer/group_layers.benchmark.cpp
Normal file
191
benchmark/renderer/group_layers.benchmark.cpp
Normal file
@@ -0,0 +1,191 @@
|
||||
#include <benchmark/benchmark.h>
|
||||
|
||||
// Core function to benchmark
|
||||
#include <mbgl/renderer/group_by_layout.hpp>
|
||||
|
||||
// For mock layer properties
|
||||
#include <mbgl/style/layer_properties.hpp>
|
||||
#include <mbgl/style/layers/fill_layer_impl.hpp>
|
||||
#include <mbgl/style/layers/fill_layer_properties.hpp>
|
||||
#include <mbgl/style/layers/line_layer_impl.hpp>
|
||||
#include <mbgl/style/layers/line_layer_properties.hpp>
|
||||
#include <mbgl/style/layers/symbol_layer_impl.hpp>
|
||||
#include <mbgl/style/layers/symbol_layer_properties.hpp>
|
||||
#include <mbgl/style/layers/circle_layer_impl.hpp>
|
||||
#include <mbgl/style/layers/circle_layer_properties.hpp>
|
||||
#include <mbgl/util/immutable.hpp>
|
||||
|
||||
// For real style loading
|
||||
#include <mbgl/style/style_impl.hpp>
|
||||
#include <mbgl/storage/file_source.hpp>
|
||||
#include <mbgl/util/io.hpp>
|
||||
#include <mbgl/util/run_loop.hpp>
|
||||
#include <mbgl/layermanager/layer_manager.hpp>
|
||||
#include <mbgl/renderer/render_layer.hpp>
|
||||
#include <mbgl/util/thread_pool.hpp>
|
||||
|
||||
using namespace mbgl;
|
||||
using namespace mbgl::style;
|
||||
|
||||
// Minimal FileSource stub for benchmark (doesn't actually fetch resources)
|
||||
class StubFileSource : public FileSource {
|
||||
public:
|
||||
std::unique_ptr<AsyncRequest> request(const Resource&, Callback) override { return nullptr; }
|
||||
bool canRequest(const Resource&) const override { return false; }
|
||||
void setResourceOptions(ResourceOptions) override {}
|
||||
ResourceOptions getResourceOptions() override { return ResourceOptions(); }
|
||||
void setClientOptions(ClientOptions) override {}
|
||||
ClientOptions getClientOptions() override { return ClientOptions(); }
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to create mock layer properties for benchmarking.
|
||||
* Creates a diverse set of layers with varied properties to simulate
|
||||
* realistic grouping scenarios.
|
||||
*/
|
||||
static std::vector<Immutable<LayerProperties>> createMockLayerProperties(size_t count) {
|
||||
std::vector<Immutable<LayerProperties>> layers;
|
||||
layers.reserve(count);
|
||||
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
std::string layerId = "layer-" + std::to_string(i);
|
||||
// Vary source IDs to create natural grouping (10 different sources)
|
||||
std::string sourceId = "source-" + std::to_string(i % 10);
|
||||
|
||||
// Rotate through different layer types
|
||||
if (i % 4 == 0) {
|
||||
// Fill layers
|
||||
auto impl = makeMutable<FillLayer::Impl>(layerId, sourceId);
|
||||
impl->minZoom = (i % 2 == 0) ? 0.0f : 5.0f;
|
||||
impl->maxZoom = (i % 3 == 0) ? 20.0f : 22.0f;
|
||||
layers.push_back(makeMutable<FillLayerProperties>(std::move(impl)));
|
||||
} else if (i % 4 == 1) {
|
||||
// Line layers
|
||||
auto impl = makeMutable<LineLayer::Impl>(layerId, sourceId);
|
||||
impl->minZoom = (i % 2 == 0) ? 0.0f : 5.0f;
|
||||
impl->maxZoom = (i % 3 == 0) ? 20.0f : 22.0f;
|
||||
layers.push_back(makeMutable<LineLayerProperties>(std::move(impl)));
|
||||
} else if (i % 4 == 2) {
|
||||
// Symbol layers
|
||||
auto impl = makeMutable<SymbolLayer::Impl>(layerId, sourceId);
|
||||
impl->minZoom = (i % 2 == 0) ? 0.0f : 5.0f;
|
||||
impl->maxZoom = (i % 3 == 0) ? 20.0f : 22.0f;
|
||||
layers.push_back(makeMutable<SymbolLayerProperties>(std::move(impl)));
|
||||
} else {
|
||||
// Circle layers
|
||||
auto impl = makeMutable<CircleLayer::Impl>(layerId, sourceId);
|
||||
impl->minZoom = (i % 2 == 0) ? 0.0f : 5.0f;
|
||||
impl->maxZoom = (i % 3 == 0) ? 20.0f : 22.0f;
|
||||
layers.push_back(makeMutable<CircleLayerProperties>(std::move(impl)));
|
||||
}
|
||||
}
|
||||
|
||||
return layers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to load a real style and extract LayerProperties.
|
||||
* This uses the actual style parsing infrastructure to create realistic
|
||||
* benchmark data from a production style JSON.
|
||||
*/
|
||||
static std::vector<Immutable<LayerProperties>> loadStyleLayerProperties(const std::string& stylePath) {
|
||||
// Load JSON
|
||||
std::string styleJSON = util::read_file(stylePath);
|
||||
|
||||
// Create style infrastructure (minimal setup for parsing)
|
||||
util::RunLoop loop;
|
||||
auto fileSource = std::make_shared<StubFileSource>();
|
||||
auto threadPool = std::make_shared<ThreadPool>();
|
||||
TaggedScheduler scheduler{threadPool, util::SimpleIdentity{}};
|
||||
|
||||
Style::Impl style{fileSource, 1.0f, scheduler};
|
||||
|
||||
// Parse style
|
||||
style.loadJSON(styleJSON);
|
||||
|
||||
// Get layer implementations from parsed style
|
||||
auto layerImpls = style.getLayerImpls();
|
||||
|
||||
// Create LayerProperties from each layer implementation
|
||||
std::vector<Immutable<LayerProperties>> layerProperties;
|
||||
layerProperties.reserve(layerImpls->size());
|
||||
|
||||
for (const auto& impl : *layerImpls) {
|
||||
// Use LayerManager to create the appropriate RenderLayer type
|
||||
// which will initialize LayerProperties from the impl
|
||||
auto renderLayer = LayerManager::get()->createRenderLayer(impl);
|
||||
if (renderLayer) {
|
||||
layerProperties.push_back(renderLayer->evaluatedProperties);
|
||||
}
|
||||
}
|
||||
|
||||
return layerProperties;
|
||||
}
|
||||
|
||||
//
|
||||
// Mock-based benchmarks: Test with controlled synthetic data
|
||||
//
|
||||
|
||||
static void GroupLayers_Mock_SmallDataset(benchmark::State& state) {
|
||||
auto layers = createMockLayerProperties(10);
|
||||
|
||||
while (state.KeepRunning()) {
|
||||
auto result = groupLayers(layers);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
|
||||
state.SetLabel("10 layers");
|
||||
}
|
||||
|
||||
static void GroupLayers_Mock_MediumDataset(benchmark::State& state) {
|
||||
auto layers = createMockLayerProperties(50);
|
||||
|
||||
while (state.KeepRunning()) {
|
||||
auto result = groupLayers(layers);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
|
||||
state.SetLabel("50 layers");
|
||||
}
|
||||
|
||||
static void GroupLayers_Mock_LargeDataset(benchmark::State& state) {
|
||||
auto layers = createMockLayerProperties(150);
|
||||
|
||||
while (state.KeepRunning()) {
|
||||
auto result = groupLayers(layers);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
|
||||
state.SetLabel("150 layers");
|
||||
}
|
||||
|
||||
//
|
||||
// Real style benchmark: Test with production data
|
||||
//
|
||||
|
||||
class RealStyleBenchmark : public benchmark::Fixture {
|
||||
public:
|
||||
void SetUp(const ::benchmark::State&) override {
|
||||
// Load style once during setup to avoid measuring parsing time
|
||||
layers = loadStyleLayerProperties("benchmark/fixtures/renderer/liberty.json");
|
||||
}
|
||||
|
||||
std::vector<Immutable<LayerProperties>> layers;
|
||||
};
|
||||
|
||||
BENCHMARK_F(RealStyleBenchmark, GroupLayers_RealStyle_Liberty)(benchmark::State& state) {
|
||||
while (state.KeepRunning()) {
|
||||
auto result = groupLayers(layers);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
|
||||
state.SetLabel(std::to_string(layers.size()) + " layers (Liberty style)");
|
||||
}
|
||||
|
||||
//
|
||||
// Register benchmarks
|
||||
//
|
||||
|
||||
BENCHMARK(GroupLayers_Mock_SmallDataset);
|
||||
BENCHMARK(GroupLayers_Mock_MediumDataset);
|
||||
BENCHMARK(GroupLayers_Mock_LargeDataset);
|
||||
@@ -1,32 +1,86 @@
|
||||
#include <mbgl/renderer/group_by_layout.hpp>
|
||||
#include <mbgl/util/hash.hpp>
|
||||
#include <mbgl/util/instrumentation.hpp>
|
||||
#include <mbgl/style/filter.hpp>
|
||||
|
||||
#include <mbgl/style/conversion/stringify.hpp>
|
||||
#include <mbgl/util/rapidjson.hpp>
|
||||
|
||||
#include <rapidjson/writer.h>
|
||||
#include <rapidjson/stringbuffer.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
std::string layoutKey(const style::Layer::Impl& impl) {
|
||||
using namespace style::conversion;
|
||||
bool LayoutGroupKey::operator==(const LayoutGroupKey& other) const {
|
||||
// Fast path: compare pointers and scalars first
|
||||
if (typeInfo != other.typeInfo) return false;
|
||||
if (minZoom != other.minZoom) return false;
|
||||
if (maxZoom != other.maxZoom) return false;
|
||||
if (visibility != other.visibility) return false;
|
||||
|
||||
rapidjson::StringBuffer s;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(s);
|
||||
// Compare source strings
|
||||
if (source != other.source) return false;
|
||||
if (sourceLayer != other.sourceLayer) return false;
|
||||
|
||||
writer.StartArray();
|
||||
writer.Uint64(reinterpret_cast<uint64_t>(impl.getTypeInfo()));
|
||||
writer.String(impl.source);
|
||||
writer.String(impl.sourceLayer);
|
||||
writer.Double(impl.minZoom);
|
||||
writer.Double(impl.maxZoom);
|
||||
writer.Uint(static_cast<uint32_t>(impl.visibility));
|
||||
stringify(writer, impl.filter);
|
||||
impl.stringifyLayout(writer);
|
||||
writer.EndArray();
|
||||
// Filter comparison - prefer pointer equality
|
||||
if (filterExpression != other.filterExpression) {
|
||||
if (!filterExpression || !other.filterExpression) return false;
|
||||
// Deep comparison if pointers differ
|
||||
if (*filterExpression != *other.filterExpression) return false;
|
||||
}
|
||||
|
||||
return s.GetString();
|
||||
return !impl->hasLayoutDifference(*other.impl);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
LayoutGroupKey createGroupKey(const style::Layer::Impl& impl) {
|
||||
LayoutGroupKey key;
|
||||
key.typeInfo = impl.getTypeInfo();
|
||||
key.source = impl.source;
|
||||
key.sourceLayer = impl.sourceLayer;
|
||||
key.minZoom = impl.minZoom;
|
||||
key.maxZoom = impl.maxZoom;
|
||||
key.visibility = impl.visibility;
|
||||
key.filterExpression = impl.filter.expression.value_or(nullptr);
|
||||
key.impl = &impl;
|
||||
return key;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
GroupMap groupLayers(const std::vector<Immutable<style::LayerProperties>>& layers) {
|
||||
MLN_TRACE_FUNC();
|
||||
|
||||
GroupMap groupMap;
|
||||
groupMap.reserve(layers.size());
|
||||
|
||||
for (auto layer : layers) {
|
||||
groupMap[createGroupKey(*layer->baseImpl)].push_back(std::move(layer));
|
||||
}
|
||||
return groupMap;
|
||||
}
|
||||
|
||||
} // namespace mbgl
|
||||
|
||||
// std::hash specialization (outside namespace)
|
||||
std::size_t std::hash<mbgl::LayoutGroupKey>::operator()(const mbgl::LayoutGroupKey& key) const noexcept {
|
||||
using mbgl::util::hash_combine;
|
||||
|
||||
std::size_t seed = 0;
|
||||
|
||||
// Hash stable pointers and scalars only (NOT layout)
|
||||
hash_combine(seed, reinterpret_cast<std::size_t>(key.typeInfo));
|
||||
hash_combine(seed, std::hash<std::string>{}(key.source));
|
||||
hash_combine(seed, std::hash<std::string>{}(key.sourceLayer));
|
||||
hash_combine(seed, std::hash<float>{}(key.minZoom));
|
||||
hash_combine(seed, std::hash<float>{}(key.maxZoom));
|
||||
hash_combine(seed, static_cast<bool>(key.visibility));
|
||||
hash_combine(seed, reinterpret_cast<std::size_t>(key.filterExpression.get()));
|
||||
|
||||
// Note: We intentionally DON'T hash the layout here
|
||||
// This means keys with same source/zoom/type/filter but different layouts
|
||||
// will hash to the same bucket, then operator== distinguishes them
|
||||
|
||||
return seed;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/style/layer_impl.hpp>
|
||||
#include <mbgl/util/containers.hpp>
|
||||
#include <mbgl/style/layer_properties.hpp>
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
std::string layoutKey(const style::Layer::Impl& impl);
|
||||
struct LayoutGroupKey {
|
||||
// Direct comparable fields (fast path)
|
||||
const style::LayerTypeInfo* typeInfo;
|
||||
std::string source;
|
||||
std::string sourceLayer;
|
||||
float minZoom;
|
||||
float maxZoom;
|
||||
style::VisibilityType visibility;
|
||||
|
||||
// Filter - use shared_ptr for identity comparison
|
||||
std::shared_ptr<const style::expression::Expression> filterExpression;
|
||||
|
||||
// Reference to impl for layout comparison
|
||||
const style::Layer::Impl* impl;
|
||||
|
||||
// Comparison operator
|
||||
bool operator==(const LayoutGroupKey& other) const;
|
||||
};
|
||||
|
||||
using GroupMap = mbgl::unordered_map<LayoutGroupKey, std::vector<Immutable<style::LayerProperties>>>;
|
||||
|
||||
GroupMap groupLayers(const std::vector<Immutable<style::LayerProperties>>&);
|
||||
|
||||
} // namespace mbgl
|
||||
|
||||
template <>
|
||||
struct std::hash<mbgl::LayoutGroupKey> {
|
||||
std::size_t operator()(const mbgl::LayoutGroupKey& key) const noexcept;
|
||||
};
|
||||
|
||||
@@ -431,12 +431,7 @@ void GeometryTileWorker::parse() {
|
||||
ImageDependencies imageDependencies;
|
||||
|
||||
// Create render layers and group by layout
|
||||
mbgl::unordered_map<std::string, std::vector<Immutable<style::LayerProperties>>> groupMap;
|
||||
groupMap.reserve(layers->size());
|
||||
|
||||
for (auto layer : *layers) {
|
||||
groupMap[layoutKey(*layer->baseImpl)].push_back(std::move(layer));
|
||||
}
|
||||
GroupMap groupMap = groupLayers(*layers);
|
||||
|
||||
for (auto& pair : groupMap) {
|
||||
const auto& group = pair.second;
|
||||
|
||||
Reference in New Issue
Block a user