Orthtree: Node insertion during orthtree build has linear complexity (#8959)

## Summary of Changes

Added inactive list for deleted nodes (nodes cannot be deleted for now)
avoiding linear search time for node insertion during Orthtree build

Reusing indices in properties from deleted nodes for insertion of a
group of nodes (i.e., during Orthtree refinement) is deactivated. This
has no impact as nodes cannot be deleted anyway.

## Release Management

* Affected package(s): Orthtree, Property_map
This commit is contained in:
Sebastien Loriot 2025-07-31 15:07:33 +02:00 committed by GitHub
commit 8a5621e5fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 35 additions and 26 deletions

View File

@ -50,7 +50,7 @@ public:
virtual void append(const Property_array_base<Index>& other) = 0;
virtual void reserve(std::size_t n) = 0;
virtual void resize(std::size_t n) = 0;
virtual void shrink_to_fit() = 0;
@ -89,7 +89,6 @@ public:
Property_array(const std::vector<bool>& active_indices, const T& default_value) :
m_data(), m_active_indices(active_indices), m_default_value(default_value) {
m_data.reserve(active_indices.capacity());
m_data.resize(active_indices.size(), m_default_value);
}
@ -124,7 +123,7 @@ public:
m_data.insert(m_data.end(), other.m_data.begin(), other.m_data.end());
}
virtual void reserve(std::size_t n) override {
virtual void resize(std::size_t n) override {
CGAL_precondition(m_active_indices.size() == n);
m_data.resize(n, m_default_value);
};
@ -248,6 +247,7 @@ class Property_container {
std::multimap<std::string, std::shared_ptr<Property_array_base<Index>>> m_properties;
std::vector<bool> m_active_indices{};
bool m_has_deleted_elements = false;
public:
@ -258,6 +258,7 @@ public:
Property_container(const Property_container<Index>& other) {
m_active_indices = other.m_active_indices;
m_has_deleted_elements = other.m_has_deleted_elements;
for (auto [name, array] : other.m_properties) {
// todo: this could probably be made faster using emplace_hint
@ -273,6 +274,7 @@ public:
// This is not exactly an assignment as existing unique properties are kept.
Property_container<Index>& operator=(const Property_container<Index>& other) {
m_active_indices = other.m_active_indices;
m_has_deleted_elements = other.m_has_deleted_elements;
for (auto [name, array] : other.m_properties) {
// search if property already exists
@ -295,6 +297,7 @@ public:
// This is not exactly an assignment as existing unique properties are kept.
Property_container<Index>& operator=(Property_container<Index>&& other) {
m_active_indices = std::move(other.m_active_indices);
m_has_deleted_elements = other.m_has_deleted_elements;
for (auto [name, array] : other.m_properties) {
// search if property already exists
@ -312,7 +315,7 @@ public:
}
// The moved-from property map should retain all of its properties, but contain 0 elements
other.reserve(0);
other.resize(0);
return *this;
}
@ -441,16 +444,15 @@ public:
}*/
public:
void reserve(std::size_t n) {
m_active_indices.resize(n);
for (auto [name, array]: m_properties)
array->reserve(n);
}
void resize(std::size_t n) {
reserve(n);
m_active_indices.resize(n);
for (auto [name, array] : m_properties)
array->resize(n);
std::fill(m_active_indices.begin(), m_active_indices.end(), true);
m_has_deleted_elements = false;
}
[[nodiscard]] std::size_t size() const { return std::count(m_active_indices.begin(), m_active_indices.end(), true); }
@ -460,8 +462,11 @@ public:
Index emplace_back() {
// Expand the storage and return the last element
reserve(capacity() + 1);
m_active_indices.back() = true;
m_active_indices.push_back(true);
for (auto [name, array] : m_properties)
array->resize(capacity());
auto first_new_index = Index(capacity() - 1);
reset(first_new_index);
return first_new_index;
@ -469,6 +474,9 @@ public:
Index emplace() {
if (!m_has_deleted_elements)
return emplace_back();
// If there are empty slots, return the index of one of them and mark it as full
auto first_unused = std::find_if(m_active_indices.begin(), m_active_indices.end(), [](bool used) { return !used; });
if (first_unused != m_active_indices.end()) {
@ -478,20 +486,27 @@ public:
return index;
}
m_has_deleted_elements = false;
return emplace_back();
}
Index emplace_group_back(std::size_t n) {
// Expand the storage and return the start of the new region
reserve(capacity() + n);
for (auto it = m_active_indices.end() - n; it < m_active_indices.end(); ++it)
*it = true;
m_active_indices.resize(capacity() + n, true);
for (auto [name, array] : m_properties)
array->resize(capacity());
return Index(capacity() - n);
}
Index emplace_group(std::size_t n) {
if (!m_has_deleted_elements)
return emplace_group_back(n);
auto search_start = m_active_indices.begin();
while (search_start != m_active_indices.end()) {
@ -544,6 +559,7 @@ public:
void erase(Index i) {
m_active_indices[i] = false;
m_has_deleted_elements = true;
for (auto [name, array]: m_properties)
array->reset(i);
}
@ -606,7 +622,7 @@ public:
if (it != range.second)
array->append(*it->second.get());
else
array->reserve(m_active_indices.size());
array->resize(m_active_indices.size());
}
}

View File

@ -41,11 +41,6 @@ void test_element_access() {
auto& integers = properties.add_property("integers", 5);
// Reserve space for 100 elements
properties.reserve(100);
assert(properties.capacity() == 100);
assert(properties.size() == 0);
// Newly emplaced elements should go at the front
assert(properties.emplace() == 0);
assert(properties.emplace() == 1);
@ -61,7 +56,6 @@ void test_element_access() {
auto& floats = properties.add_property("floats", 6.0f);
// The new property array should already be of the right size
assert(floats.capacity() == 100);
assert(properties.size() == 3);
// Pre-existing elements should contain the default value
@ -87,9 +81,8 @@ void test_element_access() {
// Erase an element, and the size should be reduced
properties.erase(1);
assert(properties.size() == 2);
assert(properties.capacity() == 100);
assert(properties.active_list().size() == 2);
assert(properties.inactive_list().size() == 98);
assert(properties.inactive_list().size() == 1);
// A newly emplaced element should take the empty slot
assert(properties.emplace() == 1);