diff --git a/CMakeLists.txt b/CMakeLists.txt index 0238268..905f4af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,3 +1,5 @@ cmake_minimum_required (VERSION 2.8) +set(CMAKE_VERBOSE_MAKEFILE on) + add_subdirectory (src) diff --git a/src/tdigest/CMakeLists.txt b/src/tdigest/CMakeLists.txt index cab12ed..ffeffc0 100644 --- a/src/tdigest/CMakeLists.txt +++ b/src/tdigest/CMakeLists.txt @@ -4,9 +4,9 @@ add_library (tdigest tdigest.cpp ) -list(APPEND CMAKE_CXX_FLAGS "-std=c++11 -O0 -g -fprofile-arcs -ftest-coverage -DNDEBUG ${CMAKE_CXX_FLAGS}") -list(APPEND CMAKE_C_FLAGS " -O0 -g -fprofile-arcs -ftest-coverage -DNDEBUG ${CMAKE_C_FLAGS}") -list(APPEND CMAKE_EXE_LINKER_FLAGS "-O0 -g -fprofile-arcs -ftest-coverage -DNDEBUG ${CMAKE_EXE_LINKER_FLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -O0 -g -fprofile-arcs -ftest-coverage -DNDEBUG") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -g -fprofile-arcs -ftest-coverage -DNDEBUG") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O0 -g -fprofile-arcs -ftest-coverage -DNDEBUG") add_executable (demo main.cpp) target_link_libraries (demo tdigest) diff --git a/src/tdigest/avltree.cpp b/src/tdigest/avltree.cpp index 1397403..d6418cc 100644 --- a/src/tdigest/avltree.cpp +++ b/src/tdigest/avltree.cpp @@ -254,3 +254,71 @@ void AvlTree::rotateRight(int node) { updateAggregates(node); updateAggregates(parentNode(node)); } + + +struct SerializedTreeNode +{ + int parent; + int left; + int right; + char depth; + int count; + double values; + int aggregatedCount; +}; + +struct SerializedTree +{ + int root; + int n; + SerializedTreeNode nodes[0]; +}; + +void AvlTree::save(std::string& out) { + + size_t len = (sizeof(SerializedTree) + + ((_n + 1) * sizeof(SerializedTreeNode))); + char* buf = (char*)malloc(len); + + SerializedTree* header = (SerializedTree*)buf; + header->n = _n; + header->root = _root; + + for (int idx = 0; idx <= _n; idx ++) + { + header->nodes[idx].parent = _parent[idx]; + header->nodes[idx].left = _left[idx]; + header->nodes[idx].right = _right[idx]; + header->nodes[idx].depth = _depth[idx]; + header->nodes[idx].count = _count[idx]; + header->nodes[idx].values = _values[idx]; + header->nodes[idx].aggregatedCount = _aggregatedCount[idx]; + } + + out.append(buf, len); + free(buf); +} + +void AvlTree::load(const std::string& in) { + SerializedTree* header = (SerializedTree*)in.data(); + + assert(header->n > 0); + assert(header->n < MAXNODE); + + _root = header->root; + _n = header->n; + + SerializedTreeNode* nodes = (SerializedTreeNode*) + (in.data() + sizeof(SerializedTree)); + + for (int idx = 0; idx <= _n; idx ++) + { + _parent[idx] = nodes[idx].parent; + _left[idx] = nodes[idx].left; + _right[idx] = nodes[idx].right; + _depth[idx] = nodes[idx].depth; + _count[idx] = nodes[idx].count; + _values[idx] = nodes[idx].values; + _aggregatedCount[idx] = nodes[idx].aggregatedCount; + } +} diff --git a/src/tdigest/avltree.hpp b/src/tdigest/avltree.hpp index 8d4be38..e88ec6e 100644 --- a/src/tdigest/avltree.hpp +++ b/src/tdigest/avltree.hpp @@ -1,5 +1,4 @@ -#ifndef HEADER_AVLTREE -#define HEADER_AVLTREE +#pragma once #include #include @@ -12,21 +11,24 @@ using namespace std; class AvlTree { private: + + static constexpr size_t MAXNODE = 1000; + int _root; int _n = 0; // TODO We should reallocate tables (ie allow dynamic arrays) - int _parent[1000]; - int _left[1000]; - int _right[1000]; - char _depth[1000]; - int _count[1000]; - double _values[1000]; - int _aggregatedCount[1000]; + int _parent[MAXNODE]; + int _left[MAXNODE]; + int _right[MAXNODE]; + char _depth[MAXNODE]; + int _count[MAXNODE]; + double _values[MAXNODE]; + int _aggregatedCount[MAXNODE]; public: static const int NIL = 0; - AvlTree(); + explicit AvlTree(); // // Node comparison @@ -252,6 +254,8 @@ class AvlTree { print(_root); } + void save(std::string& out); + + void load(const std::string& in); }; -#endif diff --git a/src/tdigest/tdigest.cpp b/src/tdigest/tdigest.cpp index 9ca0d90..84ed31d 100644 --- a/src/tdigest/tdigest.cpp +++ b/src/tdigest/tdigest.cpp @@ -60,3 +60,29 @@ double TDigest::quantile(double q) { } +struct SerializedDigest +{ + double compression; + double count; +}; + +void TDigest::save(std::string& out) +{ + SerializedDigest d; + d.compression = _compression; + d.count = _count; + + out.append((char*)&d, sizeof(d)); + + _centroids->save(out); +} + +void TDigest::load(const std::string& in) +{ + SerializedDigest* d = (SerializedDigest*)in.data(); + _compression = d->compression; + _count = d->count; + + _centroids->load(in.substr(sizeof(*d), in.size() - sizeof(*d))); +} + diff --git a/src/tdigest/tdigest.hpp b/src/tdigest/tdigest.hpp index 5efe5a9..e08a226 100644 --- a/src/tdigest/tdigest.hpp +++ b/src/tdigest/tdigest.hpp @@ -1,7 +1,7 @@ -#ifndef HEADER_TDIGEST -#define HEADER_TDIGEST +#pragma once #include +#include #include "avltree.hpp" @@ -14,10 +14,14 @@ class TDigest { private: double _compression = 100; double _count = 0; - AvlTree* _centroids = new AvlTree(); + std::unique_ptr _centroids; public: - TDigest (double compression): _compression(compression) {} + TDigest (double compression) + : _compression(compression) + { + _centroids = std::unique_ptr(new AvlTree()); + } inline long size() const { return _count; @@ -104,7 +108,7 @@ class TDigest { } inline AvlTree* centroids() const { - return _centroids; + return _centroids.get(); } inline void merge(TDigest* digest) { @@ -117,6 +121,8 @@ class TDigest { void compress(); double quantile(double q); + void save(std::string& out); + + void load(const std::string& in); }; -#endif diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 3a566dd..7314a16 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -1,9 +1,9 @@ project(cpptdigest-tests) enable_testing() -list(APPEND CMAKE_CXX_FLAGS "-std=c++11 -O0 -g -fprofile-arcs -ftest-coverage -DNDEBUG ${CMAKE_CXX_FLAGS}") -list(APPEND CMAKE_C_FLAGS " -O0 -g -fprofile-arcs -ftest-coverage -DNDEBUG ${CMAKE_C_FLAGS}") -list(APPEND CMAKE_EXE_LINKER_FLAGS "-O0 -g -fprofile-arcs -ftest-coverage -DNDEBUG ${CMAKE_EXE_LINKER_FLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -O0 -g -fprofile-arcs -ftest-coverage -DNDEBUG") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -g -fprofile-arcs -ftest-coverage -DNDEBUG ") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -O0 -g -fprofile-arcs -ftest-coverage -DNDEBUG") find_package(GTest REQUIRED) include_directories(${GTEST_INCLUDE_DIRS}) @@ -13,6 +13,17 @@ add_executable (AvlTreeTest avltree.cpp) target_link_libraries (AvlTreeTest tdigest ${GTEST_BOTH_LIBRARIES} + pthread ) -add_test(TestAvlTree AvlTreeTest) +add_executable (TDigestTest tdigest_test.cpp) + +target_link_libraries (TDigestTest + tdigest + ${GTEST_BOTH_LIBRARIES} + pthread +) + +add_test(TestAvlTree + AvlTreeTest + TDigestTest) diff --git a/src/tests/avltree.cpp b/src/tests/avltree.cpp index 3f9bac0..3ea69ed 100644 --- a/src/tests/avltree.cpp +++ b/src/tests/avltree.cpp @@ -22,3 +22,34 @@ TEST(AvlTreeTest, GenericTest) { ASSERT_EQ(tree->checkAggregates(), true); ASSERT_EQ(tree->checkIntegrity(), true); } + +TEST(AvlTreeTest, LoadSave) { + AvlTree* tree = new AvlTree(); + + ASSERT_EQ(tree->root(), 0); + ASSERT_EQ(tree->size(), 0); + + tree->add(5., 3); + tree->add(8., 1); + tree->add(9., 2); + tree->add(4., 2); + tree->add(7., 2); + tree->add(6., 2); + tree->add(2., 2); + tree->add(1., 6); + tree->add(3., 6); + + std::string out_string; + tree->save(out_string); + + AvlTree* newtree = new AvlTree(); + newtree->load(out_string); + + EXPECT_EQ(tree->root(), newtree->root()); + EXPECT_EQ(tree->size(), newtree->size()); + + ASSERT_EQ(newtree->checkBalance(), true); + ASSERT_EQ(newtree->checkAggregates(), true); + ASSERT_EQ(newtree->checkIntegrity(), true); + +} diff --git a/src/tests/tdigest_test.cpp b/src/tests/tdigest_test.cpp new file mode 100644 index 0000000..40ff347 --- /dev/null +++ b/src/tests/tdigest_test.cpp @@ -0,0 +1,21 @@ +#include "../tdigest/tdigest.hpp" + +#include + +TEST(TDigestTest, LoadSave) { + TDigest* tdigest = new TDigest(100); + + for (int i = 0; i < 1000; i++) + { + tdigest->add(rand() % 1001); + } + + std::string str; + tdigest->save(str); + + TDigest* new_digest = new TDigest(100); + new_digest->load(str); + + EXPECT_EQ(new_digest->quantile(0.5), tdigest->quantile(0.5)); +} +