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

Abstract Tree optimizations #521

Merged
merged 4 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
37 changes: 9 additions & 28 deletions bench/AbstractTreeBench.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

using FakeData = std::array<int32_t, 10>;

struct DataTree : chowdsp::AbstractTree<FakeData>
struct DataTree : chowdsp::AbstractTree<FakeData, DataTree>
{
static constexpr std::string_view positiveTag = "positive";
static constexpr std::string_view negativeTag = "negative";
Expand All @@ -28,35 +28,35 @@ struct DataTree : chowdsp::AbstractTree<FakeData>
std::string_view tag)
{
auto* new_sub_tree_node = tree.createEmptyNode();
new_sub_tree_node->tag = std::string { tag };
new_sub_tree_node->tag = tag;
new_sub_tree_node->prev_sibling = parent.last_child;
if (parent.last_child != nullptr)
parent.last_child->next_sibling = new_sub_tree_node;
parent.last_child = new_sub_tree_node;
return insertOneElement (std::move (element), *new_sub_tree_node, tree);
}

FakeData& insertElementInternal (FakeData&& element, Node& root) override
static FakeData& insertElementInternal (AbstractTree& self, FakeData&& element, Node& root)
{
for (auto* iter = root.first_child; iter != nullptr; iter = iter->next_sibling)
{
if (iter->tag == positiveTag && element[0] > 0)
return *insertOneElement (std::move (element), *iter, *this).leaf;
return *insertOneElement (std::move (element), *iter, self).leaf;

if (iter->tag == negativeTag && element[0] < 0)
return *insertOneElement (std::move (element), *iter, *this).leaf;
return *insertOneElement (std::move (element), *iter, self).leaf;

if (iter->tag == zeroTag && element[0] == 0)
return *insertOneElement (std::move (element), *iter, *this).leaf;
return *insertOneElement (std::move (element), *iter, self).leaf;
}

if (element[0] > 0)
return *insertElementIntoNewSubtree (std::move (element), root, *this, positiveTag).leaf;
return *insertElementIntoNewSubtree (std::move (element), root, self, positiveTag).leaf;

if (element[0] < 0)
return *insertElementIntoNewSubtree (std::move (element), root, *this, negativeTag).leaf;
return *insertElementIntoNewSubtree (std::move (element), root, self, negativeTag).leaf;

return *insertElementIntoNewSubtree (std::move (element), root, *this, zeroTag).leaf;
return *insertElementIntoNewSubtree (std::move (element), root, self, zeroTag).leaf;
}
};

Expand Down Expand Up @@ -108,25 +108,6 @@ static void insertTree (benchmark::State& state)
}
BENCHMARK (insertTree)->MinTime (0.5);

static void insertTreeMany (benchmark::State& state)
{
DataTree tree {};
for (auto _ : state)
{
std::vector<FakeData> vec;
vec.reserve (21);
for (int32_t i = -10; i <= 10; ++i)
{
FakeData data {};
std::fill (std::begin (data), std::end (data), i);
vec.emplace_back (std::move (data));
}
tree.insertElements (std::move (vec));
tree.clear();
}
}
BENCHMARK (insertTreeMany)->MinTime (0.5);

static void iterateVector (benchmark::State& state)
{
std::vector<FakeData> vec;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,40 @@
namespace chowdsp
{
template <typename ElementType>
AbstractTree<ElementType>::AbstractTree() = default;

template <typename ElementType>
AbstractTree<ElementType>::~AbstractTree()
template <typename ElementType, typename DerivedType>
AbstractTree<ElementType, DerivedType>::AbstractTree()
{
clear();
}

template <typename ElementType>
ElementType& AbstractTree<ElementType>::insertElement (ElementType&& elementToInsert)
template <typename ElementType, typename DerivedType>
AbstractTree<ElementType, DerivedType>::~AbstractTree()
{
doForAllNodes ([] (Node& node)
{ node.~Node(); });
}

template <typename ElementType, typename DerivedType>
ElementType& AbstractTree<ElementType, DerivedType>::insertElement (ElementType&& elementToInsert)
{
count++;
return insertElementInternal (std::move (elementToInsert), root_node);
return DerivedType::insertElementInternal (static_cast<DerivedType&> (*this),
std::move (elementToInsert),
root_node);
}

template <typename ElementType>
void AbstractTree<ElementType>::insertElements (std::vector<ElementType>&& elements)
template <typename ElementType, typename DerivedType>
void AbstractTree<ElementType, DerivedType>::insertElements (std::vector<ElementType>&& elements)
{
count += static_cast<int> (elements.size());
for (auto& element : std::move (elements))
insertElementInternal (std::move (element), root_node);
DerivedType::insertElementInternal (static_cast<DerivedType&> (*this),
std::move (element),
root_node);
}

template <typename ElementType>
template <typename ElementType, typename DerivedType>
template <typename Comparator>
void AbstractTree<ElementType>::insertNodeSorted (Node& parent, Node* new_node, Comparator&& comparator)
void AbstractTree<ElementType, DerivedType>::insertNodeSorted (Node& parent, Node* new_node, Comparator&& comparator)
{
new_node->parent = &parent;

Expand Down Expand Up @@ -63,8 +71,8 @@ void AbstractTree<ElementType>::insertNodeSorted (Node& parent, Node* new_node,
parent.last_child = new_node;
}

template <typename ElementType>
void AbstractTree<ElementType>::removeNode (Node& node)
template <typename ElementType, typename DerivedType>
void AbstractTree<ElementType, DerivedType>::removeNode (Node& node)
{
if (node.parent == nullptr)
return; // this is the root node! please don't delete me :(
Expand Down Expand Up @@ -100,11 +108,11 @@ void AbstractTree<ElementType>::removeNode (Node& node)
node.parent->last_child = node.prev_sibling;
}

nodes.remove ({ node.locator.bucket_index, node.locator.slot_index });
node.~Node();
}

template <typename ElementType>
void AbstractTree<ElementType>::removeElement (const ElementType& element)
template <typename ElementType, typename DerivedType>
void AbstractTree<ElementType, DerivedType>::removeElement (const ElementType& element)
{
for (auto* node = &root_node; node != nullptr; node = node->next_linear)
{
Expand All @@ -116,9 +124,9 @@ void AbstractTree<ElementType>::removeElement (const ElementType& element)
}
}

template <typename ElementType>
template <typename ElementType, typename DerivedType>
template <typename Callable>
void AbstractTree<ElementType>::removeElements (const Callable& elementsToRemove)
void AbstractTree<ElementType, DerivedType>::removeElements (const Callable& elementsToRemove)
{
// This algorithm assumes that a child node has always been allocated _after_
// its parent. If later on we make it possible to "move" nodes, then this may
Expand All @@ -138,15 +146,17 @@ void AbstractTree<ElementType>::removeElements (const Callable& elementsToRemove
}
}

template <typename ElementType>
void AbstractTree<ElementType>::clear()
template <typename ElementType, typename DerivedType>
void AbstractTree<ElementType, DerivedType>::clear()
{
doForAllNodes ([] (Node& node)
{ node.~Node(); });
allocator.reset (64 * sizeof (Node));
count = 0;
nodes.reset();
}

template <typename ElementType>
ElementType* AbstractTree<ElementType>::findElement (const ElementType& element)
template <typename ElementType, typename DerivedType>
ElementType* AbstractTree<ElementType, DerivedType>::findElement (const ElementType& element)
{
ElementType* result = nullptr;
doForAllElements (
Expand All @@ -158,31 +168,31 @@ ElementType* AbstractTree<ElementType>::findElement (const ElementType& element)
return result;
}

template <typename ElementType>
const ElementType* AbstractTree<ElementType>::findElement (const ElementType& element) const
template <typename ElementType, typename DerivedType>
const ElementType* AbstractTree<ElementType, DerivedType>::findElement (const ElementType& element) const
{
return const_cast<AbstractTree&> (*this).findElement (element); // NOSONAR
}

template <typename ElementType>
template <typename ElementType, typename DerivedType>
template <typename Callable>
void AbstractTree<ElementType>::doForAllNodes (Callable&& callable)
void AbstractTree<ElementType, DerivedType>::doForAllNodes (Callable&& callable)
{
for (auto* iter = &root_node; iter != nullptr; iter = iter->next_linear)
callable (*iter);
}

template <typename ElementType>
template <typename ElementType, typename DerivedType>
template <typename Callable>
void AbstractTree<ElementType>::doForAllNodes (Callable&& callable) const
void AbstractTree<ElementType, DerivedType>::doForAllNodes (Callable&& callable) const
{
for (auto* iter = &root_node; iter != nullptr; iter = iter->next_linear)
callable (*iter);
}

template <typename ElementType>
template <typename ElementType, typename DerivedType>
template <typename Callable>
void AbstractTree<ElementType>::doForAllElements (Callable&& callable)
void AbstractTree<ElementType, DerivedType>::doForAllElements (Callable&& callable)
{
doForAllNodes (
[c = std::forward<Callable> (callable)] (Node& node)
Expand All @@ -192,9 +202,9 @@ void AbstractTree<ElementType>::doForAllElements (Callable&& callable)
});
}

template <typename ElementType>
template <typename ElementType, typename DerivedType>
template <typename Callable>
void AbstractTree<ElementType>::doForAllElements (Callable&& callable) const
void AbstractTree<ElementType, DerivedType>::doForAllElements (Callable&& callable) const
{
doForAllNodes (
[c = std::forward<Callable> (callable)] (const Node& node)
Expand All @@ -204,18 +214,23 @@ void AbstractTree<ElementType>::doForAllElements (Callable&& callable) const
});
}

template <typename ElementType>
typename AbstractTree<ElementType>::Node* AbstractTree<ElementType>::createEmptyNode()
template <typename ElementType, typename DerivedType>
typename AbstractTree<ElementType, DerivedType>::Node* AbstractTree<ElementType, DerivedType>::createEmptyNode()
{
auto [locator, new_node] = nodes.emplace();

new_node->locator.bucket_index = locator.bucket_index;
new_node->locator.slot_index = locator.slot_index;
auto* new_node = new (allocator.allocate<Node> (1)) Node {};

last_node->next_linear = new_node;
new_node->prev_linear = last_node;
last_node = new_node;

return new_node;
}

template <typename ElementType, typename DerivedType>
std::string_view AbstractTree<ElementType, DerivedType>::allocateTag (std::string_view str)
{
auto* str_data = allocator.allocate<char> (str.size());
std::copy (str.begin(), str.end(), str_data);
return { str_data, str.size() }; // NOLINT NOSONAR
}
} // namespace chowdsp
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
namespace chowdsp
{
/** An abstracted tree-like data structure (not a Binary Tree) */
template <typename ElementType>
template <typename ElementType, typename DerivedType>
class AbstractTree
{
public:
struct Node
{
std::optional<ElementType> leaf { std::nullopt };
std::string_view tag {};

Node* parent {}; // slot for parent in hierarchy
Node* first_child {}; // slot for first child in hierarchy
Expand All @@ -20,15 +21,6 @@ class AbstractTree
Node* prev_sibling {}; // slot for previous sibling in hierarchy
Node* next_linear {}; // slot for linked list through all nodes
Node* prev_linear {}; // slot for linked list through all nodes

std::string tag {};

struct Locator // bucket array locator
{
size_t bucket_index;
size_t slot_index;
};
Locator locator;
};

AbstractTree();
Expand All @@ -39,19 +31,10 @@ class AbstractTree
AbstractTree (AbstractTree&&) noexcept = default;
AbstractTree& operator= (AbstractTree&&) noexcept = default;

/**
* Inserts an element into the tree.
* Calling this invalidates any existing element indices.
*
* Note that if you need to insert a bunch of elements,
* the insertElements() method will be significantly faster.
*/
/** Inserts an element into the tree. */
ElementType& insertElement (ElementType&& elementToInsert);

/**
* Inserts a bunch of elements into the tree.
* Calling this invalidates any existing element indices.
*/
/** Inserts a bunch of elements into the tree. */
void insertElements (std::vector<ElementType>&& elements);

template <typename Comparator>
Expand Down Expand Up @@ -94,18 +77,21 @@ class AbstractTree
template <typename Callable>
void doForAllElements (Callable&& callable) const;

/** Creates a new empty node in the tree's memory arena. */
Node* createEmptyNode();

/** Allocates a new tag in the tree's memory arena. */
std::string_view allocateTag (std::string_view str);

[[nodiscard]] Node& getRootNode() noexcept { return root_node; }
[[nodiscard]] const Node& getRootNode() const noexcept { return root_node; }

protected:
virtual ElementType& insertElementInternal (ElementType&& element, Node& root_node) = 0;
virtual void onDelete (const Node& /*nodeBeingDeleted*/) {}

private:
BucketArray<Node, 32> nodes;
ChainedArenaAllocator allocator { 64 * sizeof (Node) };

private:
Node root_node {};
int count = 0;

Expand Down
8 changes: 4 additions & 4 deletions modules/plugin/chowdsp_presets_v2/Backend/chowdsp_Preset.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@ class Preset
[[nodiscard]] bool isValid() const;

/** Returns the name of the preset */
[[nodiscard]] juce::String getName() const noexcept { return name; }
[[nodiscard]] const juce::String& getName() const noexcept { return name; }

/** Returns the name of the vendor that created this preset */
[[nodiscard]] juce::String getVendor() const noexcept { return vendor; }
[[nodiscard]] const juce::String& getVendor() const noexcept { return vendor; }

/** Returns the name of the preset category */
[[nodiscard]] juce::String getCategory() const noexcept { return category; }
[[nodiscard]] const juce::String& getCategory() const noexcept { return category; }

/** Returns the version of the plugin that was used to create this preset */
[[nodiscard]] Version getVersion() const noexcept { return version; }
Expand All @@ -54,7 +54,7 @@ class Preset
* Returns the file path where this preset was loaded from.
* If the preset was not loaded from a file, this will return an empty path.
*/
[[nodiscard]] juce::File getPresetFile() const noexcept { return file; }
[[nodiscard]] const juce::File& getPresetFile() const noexcept { return file; }

/** Returns the preset state */
[[nodiscard]] const auto& getState() const noexcept { return state; }
Expand Down
Loading
Loading