diff --git a/.gitmodules b/.gitmodules index 7bafd8b..4d67868 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,9 @@ [submodule "Externals/implot"] path = Externals/implot url = https://github.com/epezent/implot.git +[submodule "Externals/googletest"] + path = Externals/googletest + url = https://github.com/google/googletest.git +[submodule "Externals/benchmark"] + path = Externals/benchmark + url = https://github.com/google/benchmark.git diff --git a/Containers/CMakeLists.txt b/Containers/CMakeLists.txt index 577d802..146ae01 100644 --- a/Containers/CMakeLists.txt +++ b/Containers/CMakeLists.txt @@ -1,5 +1,7 @@ project(Containers) +include(GoogleTest) + ### ---------------------- Static Library --------------------- ### file(GLOB SOURCES "./private/*.cpp") file(GLOB HEADERS "./public/*.hpp") @@ -25,5 +27,10 @@ target_link_libraries(Tests${PROJECT_NAME} ${PROJECT_NAME} UnitTest++) add_test(NAME Tests${PROJECT_NAME} COMMAND Tests${PROJECT_NAME}) +add_executable(testLinearRingBuffer ./tests/testLinearRingBuffer.cpp) +target_link_libraries(testLinearRingBuffer ${PROJECT_NAME} gtest) +gtest_discover_tests(testLinearRingBuffer) + + add_executable(AVLTreeSpeedTest ./tests/AVLTreeProfiling.cpp) target_link_libraries(AVLTreeSpeedTest ${PROJECT_NAME}) diff --git a/Containers/private/linear_ring.cpp b/Containers/private/linear_ring.cpp new file mode 100644 index 0000000..3b6091c --- /dev/null +++ b/Containers/private/linear_ring.cpp @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace lrbm { + static int create_memfd(const char* name) { + int fd = syscall(SYS_memfd_create, name, 0); + if (fd == -1) throw std::runtime_error("memfd_create failed"); + return fd; + } + + std::pair create_linear_ring(size_t size) { + const size_t page = sysconf(_SC_PAGESIZE); + + size_t aligned = (size + page - 1) & ~(page - 1); + + // Create RAM-backed file descriptor + int fd = create_memfd("linear_ring"); + if (ftruncate(fd, aligned) != 0) throw std::runtime_error("ftruncate failed"); + + // Reserve 2× VA space + uint8_t* base = (uint8_t*) mmap(nullptr, aligned * 2, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (base == MAP_FAILED) throw std::runtime_error("mmap reserve failed"); + + // First mapping + void* p1 = mmap(base, aligned, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED, fd, 0); + if (p1 != base) throw std::runtime_error("first mmap failed"); + + // Mirror mapping + void* p2 = mmap(base + aligned, aligned, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED, fd, 0); + if (p2 != base + aligned) throw std::runtime_error("mirror mmap failed"); + + close(fd); + return { base, aligned }; // linear region [buf][buf] + } + + void destroy_linear_ring(void* base, size_t size) { munmap(base, size); } + + // void test() { + // const size_t RING_SIZE = 4096; + // uint8_t* ring = (uint8_t*) create_linear_ring(RING_SIZE); + + // printf("Linear ring created at %p (size = %zu)\n", ring, RING_SIZE); + + // const char* msg = "HELLO_LINEAR_RING"; + // size_t msg_len = strlen(msg); + + // size_t head = RING_SIZE - 4; // force boundary crossing + // std::memcpy(ring + head, msg, msg_len); + + // char out[64]; + // std::memcpy(out, ring + head, msg_len); + // out[msg_len] = 0; + + // printf("Expected: %s\n", msg); + // printf("Readback: %s\n", out); + + // if (std::strcmp(msg, out) == 0) printf("SUCCESS: Linear-address ring works!\n"); + // else printf("FAILURE: Ring broken!\n"); + // } +} diff --git a/Containers/public/LinearRingBuffer.hpp b/Containers/public/LinearRingBuffer.hpp index e69de29..ddccca2 100644 --- a/Containers/public/LinearRingBuffer.hpp +++ b/Containers/public/LinearRingBuffer.hpp @@ -0,0 +1,54 @@ + +#include +#include +#include + +#include "liniar_ring.hpp" + +class LinearRingBuffer { +public: + LinearRingBuffer(size_t size, size_t history) { + const auto& [mem, aligned_size] = lrbm::create_linear_ring(size); + + mem_ = (uint8_t*) mem; + memSize_ = aligned_size; + bufferSize_ = size; + historySize_ = history; + + write_ptr_ = mem_; + read_ptr_ = mem_; + } + + ~LinearRingBuffer() { lrbm::destroy_linear_ring((void*) mem_, memSize_ * 2); } + + uint8_t* write_data() { return write_ptr_; } + uint8_t* read_data() { return read_ptr_; } + + void write_advance(size_t bytes) { write_ptr_ = advancePointer(write_ptr_, bytes); } + void read_advance(size_t bytes) { read_ptr_ = advancePointer(read_ptr_, bytes); } + + void read_reset_with_history() { + const auto offset = size_t(write_ptr_ - mem_); + + if (historySize_ < offset) { + read_ptr_ = write_ptr_ - historySize_; + return; + } + + read_ptr_ = mem_ + (memSize_ + offset - historySize_); + } + +private: + inline uint8_t* advancePointer(const uint8_t* ptr, size_t bytes) { + return mem_ + (size_t(ptr - mem_) + bytes) % memSize_; + } + + size_t bufferSize_ = 0; + size_t historySize_ = 0; + size_t memSize_ = 0; + + uint8_t* mem_ = nullptr; + + uint8_t* read_ptr_ = 0; + uint8_t* write_ptr_ = 0; +}; diff --git a/Containers/public/liniar_ring.hpp b/Containers/public/liniar_ring.hpp new file mode 100644 index 0000000..ae7f565 --- /dev/null +++ b/Containers/public/liniar_ring.hpp @@ -0,0 +1,9 @@ + +#include +#include + +namespace lrbm { + std::pair create_linear_ring(size_t size); + void destroy_linear_ring(void* base, size_t size); + void test(); +} diff --git a/Containers/tests/benchLinearRingBuffer.cpp b/Containers/tests/benchLinearRingBuffer.cpp new file mode 100644 index 0000000..a9293f3 --- /dev/null +++ b/Containers/tests/benchLinearRingBuffer.cpp @@ -0,0 +1,188 @@ +#include "LinearRingBuffer.hpp" + +#include + +#include +#include +#include + +static void fill(void* ptr, size_t n, uint8_t value) { std::memset(ptr, value, n); } + +static bool check(const void* ptr, size_t n, uint8_t value) { + const uint8_t* p = static_cast(ptr); + for (size_t i = 0; i < n; ++i) { + if (p[i] != value) return false; + } + return true; +} + +static bool expect_mem_eq(const void* ptr, size_t n, uint8_t value) { + const uint8_t* p = static_cast(ptr); + for (size_t i = 0; i < n; ++i) { + if (p[i] != value) return false; + } + return true; +} + +using Data = std::vector; +using DataStorage = std::vector; + +typedef ::testing::Types< + std::integral_constant, + std::integral_constant, + std::integral_constant, + std::integral_constant, + std::integral_constant> + BufferSizes; + +template +class LinearRingBufferTest : public ::testing::Test { +protected: + LinearRingBuffer rb; + + LinearRingBufferTest() : + historySize_(10), + rb(size_t(TypeParam::value), size_t(historySize_)) {} + + static constexpr auto ITERATIONS = TypeParam::value * 10; + + size_t messagesPushed_ = 0; + size_t historySize_ = 0; + + auto iterations() { return ITERATIONS; } + + auto historySize() { return historySize_; } + + size_t randomSize() { + static std::random_device rnd; + std::uniform_int_distribution dist(0, 1000); + return dist(rnd); + } + + auto write_and_advance(size_t messageSize, DataStorage* storage) { + Data pushedSample(messageSize); + + for (auto idx = 0; idx < messageSize; idx++) { + pushedSample[idx] = (idx + messagesPushed_++ * 53) % 321; + } + + std::memcpy(this->rb.write_data(), pushedSample.data(), pushedSample.size()); + this->rb.write_advance(pushedSample.size()); + + if (storage) { + storage->push_back(pushedSample); + } + }; + + auto read_and_advance(size_t messageSize, DataStorage* storage) { + Data poppedSample(messageSize); + + std::memcpy(poppedSample.data(), this->rb.read_data(), poppedSample.size()); + + this->rb.read_advance(poppedSample.size()); + + if (storage) { + storage->push_back(poppedSample); + } + }; + + auto reset_with_history() { this->rb.read_reset_with_history(); }; + + auto read_with_history(size_t messageSize, DataStorage* storage) { + Data poppedSample(messageSize); + + std::memcpy(poppedSample.data(), this->rb.read_data(), poppedSample.size()); + + if (storage) { + storage->push_back(poppedSample); + } + }; +}; + +TYPED_TEST_SUITE(LinearRingBufferTest, BufferSizes); + +TYPED_TEST(LinearRingBufferTest, LinearWriteAcrossBoundary) { + constexpr size_t N = TypeParam::value; + + if (N % 4096 != 0) { + return; + } + + auto* base = this->rb.write_data(); + + size_t half = N - 16; + fill(base, half, 0xAA); + fill(base + half, 32, 0xBB); + + EXPECT_EQ(expect_mem_eq(base, 16, 0xBB), true); + EXPECT_EQ(expect_mem_eq(base + 16, half - 16, 0xAA), true); + EXPECT_EQ(expect_mem_eq(base + half, 32, 0xBB), true); +} + +TYPED_TEST(LinearRingBufferTest, Chase) { + + DataStorage pushed; + DataStorage popped; + + static constexpr auto MSG_SIZE = 31; + + for (auto iteration = 0; iteration < this->iterations(); iteration++) { + this->write_and_advance(MSG_SIZE, &pushed); + this->read_and_advance(MSG_SIZE, &popped); + } + + ASSERT_EQ(pushed.size(), popped.size()); + + for (auto idx = 0; idx < popped.size(); idx++) { + ASSERT_EQ(pushed[idx], popped[idx]); + } +} + +Data makeMessageWithHistory(const Data& prev, const Data& current, size_t historySize) { + if (historySize > prev.size()) historySize = prev.size(); + + std::vector result; + + result.reserve(historySize + current.size()); + + result.insert(result.end(), prev.end() - historySize, prev.end()); + result.insert(result.end(), current.begin(), current.end()); + + return result; +} + +TYPED_TEST(LinearRingBufferTest, History) { + + DataStorage pushed; + DataStorage popped; + + pushed.push_back(Data(this->historySize())); + + static constexpr auto MAX_MSG_SIZE = 111; + + for (auto iteration = 0; iteration < this->iterations(); iteration++) { + + const auto MSG_SIZE = this->historySize() + this->randomSize() % MAX_MSG_SIZE; + + auto prevPushed = pushed.back(); + pushed.clear(); + + this->reset_with_history(); + + this->write_and_advance(MSG_SIZE, &pushed); + auto currentPushed = pushed.back(); + + this->read_with_history(this->historySize() + MSG_SIZE, &popped); + auto poppedWithHistory = popped.back(); + popped.clear(); + + auto correctResult = makeMessageWithHistory(prevPushed, currentPushed, this->historySize()); + + ASSERT_EQ(correctResult, poppedWithHistory); + } +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/Containers/tests/testLinearRingBuffer.cpp b/Containers/tests/testLinearRingBuffer.cpp new file mode 100644 index 0000000..a9293f3 --- /dev/null +++ b/Containers/tests/testLinearRingBuffer.cpp @@ -0,0 +1,188 @@ +#include "LinearRingBuffer.hpp" + +#include + +#include +#include +#include + +static void fill(void* ptr, size_t n, uint8_t value) { std::memset(ptr, value, n); } + +static bool check(const void* ptr, size_t n, uint8_t value) { + const uint8_t* p = static_cast(ptr); + for (size_t i = 0; i < n; ++i) { + if (p[i] != value) return false; + } + return true; +} + +static bool expect_mem_eq(const void* ptr, size_t n, uint8_t value) { + const uint8_t* p = static_cast(ptr); + for (size_t i = 0; i < n; ++i) { + if (p[i] != value) return false; + } + return true; +} + +using Data = std::vector; +using DataStorage = std::vector; + +typedef ::testing::Types< + std::integral_constant, + std::integral_constant, + std::integral_constant, + std::integral_constant, + std::integral_constant> + BufferSizes; + +template +class LinearRingBufferTest : public ::testing::Test { +protected: + LinearRingBuffer rb; + + LinearRingBufferTest() : + historySize_(10), + rb(size_t(TypeParam::value), size_t(historySize_)) {} + + static constexpr auto ITERATIONS = TypeParam::value * 10; + + size_t messagesPushed_ = 0; + size_t historySize_ = 0; + + auto iterations() { return ITERATIONS; } + + auto historySize() { return historySize_; } + + size_t randomSize() { + static std::random_device rnd; + std::uniform_int_distribution dist(0, 1000); + return dist(rnd); + } + + auto write_and_advance(size_t messageSize, DataStorage* storage) { + Data pushedSample(messageSize); + + for (auto idx = 0; idx < messageSize; idx++) { + pushedSample[idx] = (idx + messagesPushed_++ * 53) % 321; + } + + std::memcpy(this->rb.write_data(), pushedSample.data(), pushedSample.size()); + this->rb.write_advance(pushedSample.size()); + + if (storage) { + storage->push_back(pushedSample); + } + }; + + auto read_and_advance(size_t messageSize, DataStorage* storage) { + Data poppedSample(messageSize); + + std::memcpy(poppedSample.data(), this->rb.read_data(), poppedSample.size()); + + this->rb.read_advance(poppedSample.size()); + + if (storage) { + storage->push_back(poppedSample); + } + }; + + auto reset_with_history() { this->rb.read_reset_with_history(); }; + + auto read_with_history(size_t messageSize, DataStorage* storage) { + Data poppedSample(messageSize); + + std::memcpy(poppedSample.data(), this->rb.read_data(), poppedSample.size()); + + if (storage) { + storage->push_back(poppedSample); + } + }; +}; + +TYPED_TEST_SUITE(LinearRingBufferTest, BufferSizes); + +TYPED_TEST(LinearRingBufferTest, LinearWriteAcrossBoundary) { + constexpr size_t N = TypeParam::value; + + if (N % 4096 != 0) { + return; + } + + auto* base = this->rb.write_data(); + + size_t half = N - 16; + fill(base, half, 0xAA); + fill(base + half, 32, 0xBB); + + EXPECT_EQ(expect_mem_eq(base, 16, 0xBB), true); + EXPECT_EQ(expect_mem_eq(base + 16, half - 16, 0xAA), true); + EXPECT_EQ(expect_mem_eq(base + half, 32, 0xBB), true); +} + +TYPED_TEST(LinearRingBufferTest, Chase) { + + DataStorage pushed; + DataStorage popped; + + static constexpr auto MSG_SIZE = 31; + + for (auto iteration = 0; iteration < this->iterations(); iteration++) { + this->write_and_advance(MSG_SIZE, &pushed); + this->read_and_advance(MSG_SIZE, &popped); + } + + ASSERT_EQ(pushed.size(), popped.size()); + + for (auto idx = 0; idx < popped.size(); idx++) { + ASSERT_EQ(pushed[idx], popped[idx]); + } +} + +Data makeMessageWithHistory(const Data& prev, const Data& current, size_t historySize) { + if (historySize > prev.size()) historySize = prev.size(); + + std::vector result; + + result.reserve(historySize + current.size()); + + result.insert(result.end(), prev.end() - historySize, prev.end()); + result.insert(result.end(), current.begin(), current.end()); + + return result; +} + +TYPED_TEST(LinearRingBufferTest, History) { + + DataStorage pushed; + DataStorage popped; + + pushed.push_back(Data(this->historySize())); + + static constexpr auto MAX_MSG_SIZE = 111; + + for (auto iteration = 0; iteration < this->iterations(); iteration++) { + + const auto MSG_SIZE = this->historySize() + this->randomSize() % MAX_MSG_SIZE; + + auto prevPushed = pushed.back(); + pushed.clear(); + + this->reset_with_history(); + + this->write_and_advance(MSG_SIZE, &pushed); + auto currentPushed = pushed.back(); + + this->read_with_history(this->historySize() + MSG_SIZE, &popped); + auto poppedWithHistory = popped.back(); + popped.clear(); + + auto correctResult = makeMessageWithHistory(prevPushed, currentPushed, this->historySize()); + + ASSERT_EQ(correctResult, poppedWithHistory); + } +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/Externals/CMakeLists.txt b/Externals/CMakeLists.txt index 4f40aa3..ec85993 100644 --- a/Externals/CMakeLists.txt +++ b/Externals/CMakeLists.txt @@ -53,4 +53,6 @@ project(ImageIO) add_library(${PROJECT_NAME} INTERFACE) target_include_directories(${PROJECT_NAME} INTERFACE ./imageIO/) -add_subdirectory(lalr) \ No newline at end of file +add_subdirectory(lalr) + +add_subdirectory(googletest) diff --git a/Externals/benchmark b/Externals/benchmark new file mode 160000 index 0000000..c3f8657 --- /dev/null +++ b/Externals/benchmark @@ -0,0 +1 @@ +Subproject commit c3f86578bb2081b52cee9d51615912ca4aa52fe4 diff --git a/Externals/googletest b/Externals/googletest new file mode 160000 index 0000000..1b96fa1 --- /dev/null +++ b/Externals/googletest @@ -0,0 +1 @@ +Subproject commit 1b96fa13f549387b7549cc89e1a785cf143a1a50