From f0a8a19d86f75ad8a950a5502df70afef6af96d0 Mon Sep 17 00:00:00 2001 From: q40603 Date: Fri, 20 Oct 2023 18:49:02 +0800 Subject: [PATCH 1/6] radix tree prototype for profiler Enhance: 1. generate key for function name to do key comparison 2. move getChild logic from tree to node enhance: 1. use try_emplace to get function id enhance: 1. add operator overloading in TimedEntry for printing. 2. add template for better usability enhance: 1. add ref in comment enhance: 1. rename getFunctionId to getID for general purpose enhance: 1. change to header file 2. add testing to gtests dir radix tree prototype for profiler Enhance: 1. generate key for function name to do key comparison 2. move getChild logic from tree to node enhance: 1. use try_emplace to get function id enhance: 1. add operator overloading in TimedEntry for printing. 2. add template for better usability enhance: 1. add ref in comment enhance: 1. rename getFunctionId to getID for general purpose enhance: 1. change to header file 2. add testing to gtests dir remove int main in header file clang format add parameter dialog RParameter: support int64/double types RParameter: allow building parameters list at runtime modmesh/params.py: add license RParameter: clang-format fix modmesh/params.py: fix flake8 issue check PUI version modmesh/params.py: fix test example [hotfix] reopen brew update freeze llvm version --- cpp/modmesh/toggle/CMakeLists.txt | 1 + cpp/modmesh/toggle/RadixTree.hpp | 181 ++++++++++++++++++++++++++++ gtests/CMakeLists.txt | 1 + gtests/test_nonpython_radixtree.cpp | 32 +++++ 4 files changed, 215 insertions(+) create mode 100644 cpp/modmesh/toggle/RadixTree.hpp create mode 100644 gtests/test_nonpython_radixtree.cpp diff --git a/cpp/modmesh/toggle/CMakeLists.txt b/cpp/modmesh/toggle/CMakeLists.txt index 403b08c9..dbf127da 100644 --- a/cpp/modmesh/toggle/CMakeLists.txt +++ b/cpp/modmesh/toggle/CMakeLists.txt @@ -5,6 +5,7 @@ cmake_minimum_required(VERSION 3.16) set(MODMESH_TOGGLE_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/profile.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/RadixTree.hpp ${CMAKE_CURRENT_SOURCE_DIR}/toggle.hpp CACHE FILEPATH "" FORCE) diff --git a/cpp/modmesh/toggle/RadixTree.hpp b/cpp/modmesh/toggle/RadixTree.hpp new file mode 100644 index 00000000..5b7111a4 --- /dev/null +++ b/cpp/modmesh/toggle/RadixTree.hpp @@ -0,0 +1,181 @@ +#pragma once + +/* + * Copyright (c) 2023, Quentin Tsai + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +namespace modmesh +{ +/** + * Simple Timed Entry + */ +class TimedEntry { +public: + size_t count() const { return m_count; } + double time() const { return m_time; } + + TimedEntry& add_time(double time) { + ++m_count; + m_time += time; + return *this; + } + + friend std::ostream& operator<<(std::ostream& os, const TimedEntry& entry) { + os << "Count: " << entry.count() << " - Time: " << entry.time(); + return os; + } + +private: + size_t m_count = 0; + double m_time = 0.0; +}; /* end class TimedEntry */ + +template +class RadixTreeNode { +private: + using ChildList = std::list>; + +public: + RadixTreeNode() : prev(nullptr) {}; + RadixTreeNode(std::string name, int key) : m_name(std::move(name)), m_key(key), prev(nullptr) {} + + const int& getKey() const { return m_key; } + const std::string& getName() const { return m_name; } + T& getData() { return m_data; } + const T& getData() const { return m_data; } + const ChildList& getChildren() const { return m_children; } + + // Add a child node + RadixTreeNode* addChild(std::string childName, int childKey) { + auto newChild = std::make_unique>(std::move(childName), std::move(childKey)); + newChild->prev = this; + m_children.push_back(std::move(newChild)); + return m_children.back().get(); + } + + // Get a child node with a given key + RadixTreeNode* getChild(int key) const { + auto it = std::find_if(m_children.begin(), m_children.end(), [&](const auto& child) { + return child->getKey() == key; + }); + return (it != m_children.end()) ? it->get() : nullptr; + } + + // Get prev node in the tree + RadixTreeNode* getPrev() const { + return prev; + } + + // // print node information + std::string Info() const { + std::ostringstream oss; + if (nullptr != prev) { + oss << getName() << " - "; + oss << getData(); + } + return oss.str(); + } + +private: + int m_key = -1; + std::string m_name; + T m_data; + ChildList m_children; + RadixTreeNode* prev; +}; /* end class RadixTreeNode */ + +/* +Ref: +https://kalkicode.com/radix-tree-implementation +https://www.algotree.org/algorithms/trie/ +*/ +template +class RadixTree { +public: + RadixTree() : m_root(std::make_unique>()), m_currentNode(m_root.get()) {} + + T& entry(const std::string& name) { + int id = getId(name); + + RadixTreeNode* child = m_currentNode->getChild(id); + + if (!child) { + m_currentNode = m_currentNode->addChild(name, id); + } else { + m_currentNode = child; + } + + return m_currentNode->getData(); + } + + void moveCurrentToParent() { + if (m_currentNode != m_root.get()) { + m_currentNode = m_currentNode->getPrev(); + } + } + + const RadixTreeNode* getCurrentNode() const { return m_currentNode; } + const int getUniqueNode() const { + return m_unique_id; + } + + void print() const { + printRecursive(m_root.get(), 0); + } + +private: + void printRecursive(const RadixTreeNode* node, int depth) const { + for (int i = 0; i < depth; ++i) { + std::cout << " "; + } + + std::cout << node->Info(); + + for (const auto& child : node->getChildren()) { + printRecursive(child.get(), depth + 1); + } + } + + int getId(const std::string& name) { + auto [it, inserted] = m_id_map.try_emplace(name, m_unique_id++); + return it->second; + } + + std::unique_ptr> m_root; + RadixTreeNode* m_currentNode; + std::unordered_map m_id_map; + int m_unique_id = 0; +}; /* end class RadixTree */ + +} /* end namespace modmesh */ diff --git a/gtests/CMakeLists.txt b/gtests/CMakeLists.txt index 29b1db7b..9a5f011c 100644 --- a/gtests/CMakeLists.txt +++ b/gtests/CMakeLists.txt @@ -22,6 +22,7 @@ add_executable( test_nopython_buffer.cpp test_nopython_modmesh.cpp test_nopython_inout.cpp + test_nonpython_radixtree.cpp ) target_link_libraries( test_nopython diff --git a/gtests/test_nonpython_radixtree.cpp b/gtests/test_nonpython_radixtree.cpp new file mode 100644 index 00000000..4d253a70 --- /dev/null +++ b/gtests/test_nonpython_radixtree.cpp @@ -0,0 +1,32 @@ +#include +#include + + +#ifdef Py_PYTHON_H +#error "Python.h should not be included." +#endif + +TEST(RadixTree, construction) +{ + using namespace modmesh; + RadixTree radixTree; + EXPECT_EQ(radixTree.getUniqueNode(), 0); +} + +TEST(RadixTree, insertion) +{ + using namespace modmesh; + RadixTree radixTree; + TimedEntry& entry1 = radixTree.entry("a"); + entry1.add_time(5.2); + + EXPECT_EQ(entry1.count(), 1); + EXPECT_DOUBLE_EQ(entry1.time(), 5.2); + EXPECT_EQ(radixTree.getUniqueNode(), 1); + + const RadixTreeNode* const node = radixTree.getCurrentNode(); + EXPECT_EQ(node->getName(), "a"); + EXPECT_EQ(node->Info(), "a - Count: 1 - Time: 5.2"); +} + +// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: From 89cc60ae87db8d531f83f160bd8079716b133840 Mon Sep 17 00:00:00 2001 From: q40603 Date: Sat, 11 Nov 2023 14:59:00 +0800 Subject: [PATCH 2/6] clang-format --- cpp/modmesh/toggle/RadixTree.hpp | 111 ++++++++++++++++++---------- gtests/test_nonpython_radixtree.cpp | 7 +- 2 files changed, 74 insertions(+), 44 deletions(-) diff --git a/cpp/modmesh/toggle/RadixTree.hpp b/cpp/modmesh/toggle/RadixTree.hpp index 5b7111a4..742bd643 100644 --- a/cpp/modmesh/toggle/RadixTree.hpp +++ b/cpp/modmesh/toggle/RadixTree.hpp @@ -40,18 +40,21 @@ namespace modmesh /** * Simple Timed Entry */ -class TimedEntry { +class TimedEntry +{ public: size_t count() const { return m_count; } double time() const { return m_time; } - TimedEntry& add_time(double time) { + TimedEntry & add_time(double time) + { ++m_count; m_time += time; return *this; } - friend std::ostream& operator<<(std::ostream& os, const TimedEntry& entry) { + friend std::ostream & operator<<(std::ostream & os, const TimedEntry & entry) + { os << "Count: " << entry.count() << " - Time: " << entry.time(); return os; } @@ -62,22 +65,30 @@ class TimedEntry { }; /* end class TimedEntry */ template -class RadixTreeNode { +class RadixTreeNode +{ private: using ChildList = std::list>; public: - RadixTreeNode() : prev(nullptr) {}; - RadixTreeNode(std::string name, int key) : m_name(std::move(name)), m_key(key), prev(nullptr) {} - - const int& getKey() const { return m_key; } - const std::string& getName() const { return m_name; } - T& getData() { return m_data; } - const T& getData() const { return m_data; } - const ChildList& getChildren() const { return m_children; } + RadixTreeNode() + : prev(nullptr){}; + RadixTreeNode(std::string name, int key) + : m_name(std::move(name)) + , m_key(key) + , prev(nullptr) + { + } + + const int & getKey() const { return m_key; } + const std::string & getName() const { return m_name; } + T & getData() { return m_data; } + const T & getData() const { return m_data; } + const ChildList & getChildren() const { return m_children; } // Add a child node - RadixTreeNode* addChild(std::string childName, int childKey) { + RadixTreeNode * addChild(std::string childName, int childKey) + { auto newChild = std::make_unique>(std::move(childName), std::move(childKey)); newChild->prev = this; m_children.push_back(std::move(newChild)); @@ -85,22 +96,25 @@ class RadixTreeNode { } // Get a child node with a given key - RadixTreeNode* getChild(int key) const { - auto it = std::find_if(m_children.begin(), m_children.end(), [&](const auto& child) { - return child->getKey() == key; - }); + RadixTreeNode * getChild(int key) const + { + auto it = std::find_if(m_children.begin(), m_children.end(), [&](const auto & child) + { return child->getKey() == key; }); return (it != m_children.end()) ? it->get() : nullptr; } - - // Get prev node in the tree - RadixTreeNode* getPrev() const { + + // Get prev node + RadixTreeNode * getPrev() const + { return prev; } // // print node information - std::string Info() const { + std::string Info() const + { std::ostringstream oss; - if (nullptr != prev) { + if (nullptr != prev) + { oss << getName() << " - "; oss << getData(); } @@ -112,7 +126,7 @@ class RadixTreeNode { std::string m_name; T m_data; ChildList m_children; - RadixTreeNode* prev; + RadixTreeNode * prev; }; /* end class RadixTreeNode */ /* @@ -121,61 +135,78 @@ class RadixTreeNode { https://www.algotree.org/algorithms/trie/ */ template -class RadixTree { +class RadixTree +{ public: - RadixTree() : m_root(std::make_unique>()), m_currentNode(m_root.get()) {} + RadixTree() + : m_root(std::make_unique>()) + , m_currentNode(m_root.get()) + { + } - T& entry(const std::string& name) { + T & entry(const std::string & name) + { int id = getId(name); - RadixTreeNode* child = m_currentNode->getChild(id); + RadixTreeNode * child = m_currentNode->getChild(id); - if (!child) { + if (!child) + { m_currentNode = m_currentNode->addChild(name, id); - } else { + } + else + { m_currentNode = child; } return m_currentNode->getData(); } - void moveCurrentToParent() { - if (m_currentNode != m_root.get()) { + void moveCurrentToParent() + { + if (m_currentNode != m_root.get()) + { m_currentNode = m_currentNode->getPrev(); } } - const RadixTreeNode* getCurrentNode() const { return m_currentNode; } - const int getUniqueNode() const { + const RadixTreeNode * getCurrentNode() const { return m_currentNode; } + const int getUniqueNode() const + { return m_unique_id; } - void print() const { + void print() const + { printRecursive(m_root.get(), 0); } private: - void printRecursive(const RadixTreeNode* node, int depth) const { - for (int i = 0; i < depth; ++i) { + void printRecursive(const RadixTreeNode * node, int depth) const + { + for (int i = 0; i < depth; ++i) + { std::cout << " "; } std::cout << node->Info(); - for (const auto& child : node->getChildren()) { + for (const auto & child : node->getChildren()) + { printRecursive(child.get(), depth + 1); } } - int getId(const std::string& name) { + int getId(const std::string & name) + { auto [it, inserted] = m_id_map.try_emplace(name, m_unique_id++); return it->second; } std::unique_ptr> m_root; - RadixTreeNode* m_currentNode; + RadixTreeNode * m_currentNode; std::unordered_map m_id_map; int m_unique_id = 0; }; /* end class RadixTree */ -} /* end namespace modmesh */ +} /* end namespace modmesh */ \ No newline at end of file diff --git a/gtests/test_nonpython_radixtree.cpp b/gtests/test_nonpython_radixtree.cpp index 4d253a70..ccebea32 100644 --- a/gtests/test_nonpython_radixtree.cpp +++ b/gtests/test_nonpython_radixtree.cpp @@ -1,7 +1,6 @@ #include #include - #ifdef Py_PYTHON_H #error "Python.h should not be included." #endif @@ -17,16 +16,16 @@ TEST(RadixTree, insertion) { using namespace modmesh; RadixTree radixTree; - TimedEntry& entry1 = radixTree.entry("a"); + TimedEntry & entry1 = radixTree.entry("a"); entry1.add_time(5.2); EXPECT_EQ(entry1.count(), 1); EXPECT_DOUBLE_EQ(entry1.time(), 5.2); EXPECT_EQ(radixTree.getUniqueNode(), 1); - const RadixTreeNode* const node = radixTree.getCurrentNode(); + const RadixTreeNode * const node = radixTree.getCurrentNode(); EXPECT_EQ(node->getName(), "a"); EXPECT_EQ(node->Info(), "a - Count: 1 - Time: 5.2"); } -// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: +// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: \ No newline at end of file From e9ac75d9fd385cc3d43c2917752c5ffda5ae61d1 Mon Sep 17 00:00:00 2001 From: q40603 Date: Sat, 11 Nov 2023 15:02:31 +0800 Subject: [PATCH 3/6] add algorithm header --- cpp/modmesh/toggle/RadixTree.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp/modmesh/toggle/RadixTree.hpp b/cpp/modmesh/toggle/RadixTree.hpp index 742bd643..165bb1c4 100644 --- a/cpp/modmesh/toggle/RadixTree.hpp +++ b/cpp/modmesh/toggle/RadixTree.hpp @@ -34,6 +34,7 @@ #include #include #include +#include namespace modmesh { From 4bf8a2374278119d8fb11b78b6008edb6e8f72cc Mon Sep 17 00:00:00 2001 From: q40603 Date: Sat, 11 Nov 2023 15:03:43 +0800 Subject: [PATCH 4/6] add new line --- cpp/modmesh/toggle/RadixTree.hpp | 2 +- gtests/test_nonpython_radixtree.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/modmesh/toggle/RadixTree.hpp b/cpp/modmesh/toggle/RadixTree.hpp index 165bb1c4..e4968de8 100644 --- a/cpp/modmesh/toggle/RadixTree.hpp +++ b/cpp/modmesh/toggle/RadixTree.hpp @@ -210,4 +210,4 @@ class RadixTree int m_unique_id = 0; }; /* end class RadixTree */ -} /* end namespace modmesh */ \ No newline at end of file +} /* end namespace modmesh */ diff --git a/gtests/test_nonpython_radixtree.cpp b/gtests/test_nonpython_radixtree.cpp index ccebea32..e40c8f3f 100644 --- a/gtests/test_nonpython_radixtree.cpp +++ b/gtests/test_nonpython_radixtree.cpp @@ -28,4 +28,4 @@ TEST(RadixTree, insertion) EXPECT_EQ(node->Info(), "a - Count: 1 - Time: 5.2"); } -// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: \ No newline at end of file +// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: From d42e22e3afb335e22ca9ae9d729e633972b1723b Mon Sep 17 00:00:00 2001 From: q40603 Date: Sat, 11 Nov 2023 15:05:02 +0800 Subject: [PATCH 5/6] add vim modeline --- cpp/modmesh/toggle/RadixTree.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp/modmesh/toggle/RadixTree.hpp b/cpp/modmesh/toggle/RadixTree.hpp index e4968de8..aca29db0 100644 --- a/cpp/modmesh/toggle/RadixTree.hpp +++ b/cpp/modmesh/toggle/RadixTree.hpp @@ -211,3 +211,4 @@ class RadixTree }; /* end class RadixTree */ } /* end namespace modmesh */ +// vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: From 02dc7bef2bc92c021164e349dffb5db4c99d9021 Mon Sep 17 00:00:00 2001 From: q40603 Date: Sat, 11 Nov 2023 20:21:08 +0800 Subject: [PATCH 6/6] add more tests in gtest --- gtests/test_nonpython_radixtree.cpp | 38 ++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/gtests/test_nonpython_radixtree.cpp b/gtests/test_nonpython_radixtree.cpp index e40c8f3f..dce840c9 100644 --- a/gtests/test_nonpython_radixtree.cpp +++ b/gtests/test_nonpython_radixtree.cpp @@ -12,7 +12,7 @@ TEST(RadixTree, construction) EXPECT_EQ(radixTree.getUniqueNode(), 0); } -TEST(RadixTree, insertion) +TEST(RadixTree, single_insertion) { using namespace modmesh; RadixTree radixTree; @@ -28,4 +28,40 @@ TEST(RadixTree, insertion) EXPECT_EQ(node->Info(), "a - Count: 1 - Time: 5.2"); } +TEST(RadixTree, multiple_insertion) +{ + using namespace modmesh; + RadixTree radixTree; + TimedEntry & entry1 = radixTree.entry("a"); + entry1.add_time(5.2); + const RadixTreeNode * const node1 = radixTree.getCurrentNode(); + + TimedEntry & entry2 = radixTree.entry("b"); + entry2.add_time(10.1); + + EXPECT_EQ(radixTree.getUniqueNode(), 2); + + const RadixTreeNode * const node2 = radixTree.getCurrentNode(); + EXPECT_EQ(node2->getPrev(), node1); +} + +TEST(RadixTree, move_current_pointer) +{ + using namespace modmesh; + RadixTree radixTree; + TimedEntry & entry1 = radixTree.entry("a"); + entry1.add_time(5.2); + const RadixTreeNode * const node1 = radixTree.getCurrentNode(); + + TimedEntry & entry2 = radixTree.entry("b"); + entry2.add_time(10.1); + + radixTree.moveCurrentToParent(); + const RadixTreeNode * const node2 = radixTree.getCurrentNode(); + + EXPECT_EQ(radixTree.getUniqueNode(), 2); + EXPECT_EQ(node2->getName(), "a"); + EXPECT_EQ(node2, node1); +} + // vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: