From ff47fd426a7f4840fdfc97fef8ed8149a404ddba Mon Sep 17 00:00:00 2001 From: Parichay Kapoor Date: Wed, 11 Aug 2021 18:09:17 +0900 Subject: [PATCH] [memorypool/test] Unittests for memory pool This patch adds unittests for memory pool for its public APIs. Further, memory validation is added for the requested memories and those tests will be added for each planner. This ensures that the memories returned by each planner do not overlap, and remain valid while usage. Fixes corresponding to the unittests are also added. Signed-off-by: Parichay Kapoor --- nntrainer/tensor/memory_pool.cpp | 13 +- nntrainer/tensor/memory_pool.h | 2 +- test/unittest/memory/memory_planner_validate.cpp | 3 +- test/unittest/memory/memory_planner_validate.h | 1 - test/unittest/memory/meson.build | 4 +- test/unittest/memory/unittest_memory_planner.cpp | 2 +- test/unittest/memory/unittest_memory_pool.cpp | 544 +++++++++++++++++++++++ 7 files changed, 562 insertions(+), 7 deletions(-) create mode 100644 test/unittest/memory/unittest_memory_pool.cpp diff --git a/nntrainer/tensor/memory_pool.cpp b/nntrainer/tensor/memory_pool.cpp index d804792..8a42d8c 100644 --- a/nntrainer/tensor/memory_pool.cpp +++ b/nntrainer/tensor/memory_pool.cpp @@ -24,6 +24,9 @@ namespace nntrainer { */ unsigned int MemoryPool::requestMemory(size_t bytes, unsigned int start_time, unsigned int end_time) { + if (bytes == 0) + throw std::invalid_argument("Requesting memory of 0 size"); + if (mem_pool != nullptr) throw std::invalid_argument( "Deallocate memory pool before requesting more memory"); @@ -79,9 +82,13 @@ void MemoryPool::allocate() { if (pool_size == 0) throw std::runtime_error("Allocating memory pool with size 0"); + if (mem_pool != nullptr) + throw std::runtime_error("Memory pool is already allocated"); + mem_pool = malloc(pool_size); if (mem_pool == nullptr) - throw std::runtime_error("Allocation memory failed"); + throw std::runtime_error( + "Failed to allocate memory: " + std::to_string(pool_size) + "bytes"); } /** @@ -102,6 +109,8 @@ void *MemoryPool::getMemory(unsigned int idx) { void MemoryPool::deallocate() { if (mem_pool != nullptr) free(mem_pool); + + mem_pool = nullptr; } /** @@ -165,7 +174,7 @@ template static bool overlap(T s1, T e1, T s2, T e2) { throw std::invalid_argument("Invalid range for intervals in MemoryPool"); #endif - return !(e1 <= s2 || e2 <= s1) + return !(e1 <= s2 || e2 <= s1); } /** diff --git a/nntrainer/tensor/memory_pool.h b/nntrainer/tensor/memory_pool.h index 8dc5c96..46ee8c1 100644 --- a/nntrainer/tensor/memory_pool.h +++ b/nntrainer/tensor/memory_pool.h @@ -9,7 +9,7 @@ * @bug No known bugs except for NYI items * @brief This is Memory Pool Class * - * @todo Support an external allocator for different backends + * @todo Support an external allocator for different backends and alignment * @todo Support releaseMemory(token) - this need not release actual memory * until deallocate * @todo Support maximum memory size for the memory pool as an argument diff --git a/test/unittest/memory/memory_planner_validate.cpp b/test/unittest/memory/memory_planner_validate.cpp index ddfdcb0..46a5c26 100644 --- a/test/unittest/memory/memory_planner_validate.cpp +++ b/test/unittest/memory/memory_planner_validate.cpp @@ -10,10 +10,11 @@ * @brief Tests for memory planning */ -#include #include #include +#include + #include #include diff --git a/test/unittest/memory/memory_planner_validate.h b/test/unittest/memory/memory_planner_validate.h index 28867aa..965204c 100644 --- a/test/unittest/memory/memory_planner_validate.h +++ b/test/unittest/memory/memory_planner_validate.h @@ -7,7 +7,6 @@ * @see https://github.com/nnstreamer/nntrainer * @author Parichay Kapoor * @bug No known bugs except for NYI items - memory_planner_validate.h */ #ifndef __MEMORY_PLANNER_VALIDATE_H__ diff --git a/test/unittest/memory/meson.build b/test/unittest/memory/meson.build index 4e902f3..8e9a72c 100644 --- a/test/unittest/memory/meson.build +++ b/test/unittest/memory/meson.build @@ -1,6 +1,6 @@ memory_test_inc = include_directories('./') -nntrainer_memory_planner_tests_lib = shared_library( +nntrainer_memory_planner_tests_lib = static_library( 'nntrainer_memory_planner_validation', 'memory_planner_validate.cpp', dependencies: [nntrainer_dep, gtest_dep], # nntrainer_devel_dep @@ -15,8 +15,10 @@ nntrainer_memory_tests_dep = declare_dependency( test_target = [ 'memory_planner_validate.cpp', 'unittest_memory_planner.cpp', + 'unittest_memory_pool.cpp' ] +# memory unittests exe = executable( 'unittest_memory', test_target, dependencies: [ diff --git a/test/unittest/memory/unittest_memory_planner.cpp b/test/unittest/memory/unittest_memory_planner.cpp index 10bb334..32eba9b 100644 --- a/test/unittest/memory/unittest_memory_planner.cpp +++ b/test/unittest/memory/unittest_memory_planner.cpp @@ -4,7 +4,7 @@ * * @file unittest_memory_planning.cpp * @date 11 August 2021 - * @brief Activation Layer Test + * @brief Memory Planner Test * @see https://github.com/nnstreamer/nntrainer * @author Parichay Kapoor * @bug No known bugs except for NYI items diff --git a/test/unittest/memory/unittest_memory_pool.cpp b/test/unittest/memory/unittest_memory_pool.cpp new file mode 100644 index 0000000..3c1f361 --- /dev/null +++ b/test/unittest/memory/unittest_memory_pool.cpp @@ -0,0 +1,544 @@ +// SPDX-License-Identifier: Apache-2.0 +/** + * Copyright (C) 2021 Parichay Kapoor + * + * @file unittest_memory_pool.cpp + * @date 11 August 2021 + * @brief Memory Pool Test + * @see https://github.com/nnstreamer/nntrainer + * @author Parichay Kapoor + * @bug No known bugs except for NYI items + */ + +#include +#include +#include + +#include + +#include +#include +#include + +constexpr unsigned int MEM_BYTES = 128; +constexpr unsigned int MEM_QUANT = 100; +constexpr unsigned int INTERVAL_SIZE = 5; + +/** + * @brief creation and destruction + */ +TEST(MemoryPool, create_destroy) { EXPECT_NO_THROW(nntrainer::MemoryPool()); } + +/** + * @brief request 0 sized memory + */ +TEST(MemoryPool, request_mem_01_n) { + nntrainer::MemoryPool pool; + + EXPECT_THROW(pool.requestMemory(0, 1, 2), std::invalid_argument); +} + +/** + * @brief request memory when starts after it ends + */ +TEST(MemoryPool, request_mem_02_n) { + nntrainer::MemoryPool pool; + + EXPECT_THROW(pool.requestMemory(1, 3, 2), std::invalid_argument); +} + +/** + * @brief request memory with 0 valid time + */ +TEST(MemoryPool, request_mem_03_n) { + nntrainer::MemoryPool pool; + + EXPECT_THROW(pool.requestMemory(1, 4, 4), std::invalid_argument); +} + +/** + * @brief request memory + */ +TEST(MemoryPool, request_mem_04_p) { + nntrainer::MemoryPool pool; + + EXPECT_NO_THROW(pool.requestMemory(1, 4, 5)); +} + +/** + * @brief plan layout + */ +TEST(MemoryPool, plan_layout_01_n) { + nntrainer::MemoryPool pool; + + EXPECT_THROW(pool.planLayout(nntrainer::BasicPlanner()), std::runtime_error); +} + +/** + * @brief plan layout + */ +TEST(MemoryPool, plan_layout_02_p) { + nntrainer::MemoryPool pool; + + pool.requestMemory(1, 4, 5); + EXPECT_NO_THROW(pool.planLayout(nntrainer::BasicPlanner())); +} + +/** + * @brief plan layout + */ +TEST(MemoryPool, plan_layout_03_p) { + nntrainer::MemoryPool pool; + + pool.requestMemory(1, 4, 5); + EXPECT_NO_THROW(pool.planLayout(nntrainer::BasicPlanner())); + EXPECT_EQ(1u, pool.size()); + + pool.requestMemory(1, 4, 5); + EXPECT_NO_THROW(pool.planLayout(nntrainer::BasicPlanner())); + EXPECT_EQ(2u, pool.size()); +} + +/** + * @brief deallocate + */ +TEST(MemoryPool, deallocate_01_p) { + nntrainer::MemoryPool pool; + + EXPECT_NO_THROW(pool.deallocate()); +} + +/** + * @brief allocate + */ +TEST(MemoryPool, allocate_01_n) { + nntrainer::MemoryPool pool; + + EXPECT_THROW(pool.allocate(), std::runtime_error); + + pool.requestMemory(1, 4, 5); + EXPECT_NO_THROW(pool.planLayout(nntrainer::BasicPlanner())); + EXPECT_EQ(1u, pool.size()); + + EXPECT_NO_THROW(pool.allocate()); + + EXPECT_NO_THROW(pool.deallocate()); +} + +/** + * @brief allocate + */ +TEST(MemoryPool, allocate_02_p) { + nntrainer::MemoryPool pool; + + pool.requestMemory(1, 4, 5); + EXPECT_NO_THROW(pool.planLayout(nntrainer::BasicPlanner())); + EXPECT_EQ(1u, pool.size()); + + EXPECT_NO_THROW(pool.allocate()); + + EXPECT_NO_THROW(pool.deallocate()); +} + +/** + * @brief allocate + */ +TEST(MemoryPool, allocate_03_n) { + nntrainer::MemoryPool pool; + + pool.requestMemory(1, 4, 5); + EXPECT_NO_THROW(pool.planLayout(nntrainer::BasicPlanner())); + EXPECT_EQ(1u, pool.size()); + + EXPECT_NO_THROW(pool.allocate()); + + EXPECT_THROW(pool.allocate(), std::runtime_error); + + EXPECT_NO_THROW(pool.deallocate()); +} + +/** + * @brief allocate + */ +TEST(MemoryPool, allocate_04_n) { + nntrainer::MemoryPool pool; + + pool.requestMemory(3, 4, 5); + EXPECT_NO_THROW(pool.planLayout(nntrainer::BasicPlanner())); + EXPECT_EQ(3u, pool.size()); + + EXPECT_NO_THROW(pool.allocate()); + + EXPECT_NO_THROW(pool.deallocate()); + + EXPECT_NO_THROW(pool.allocate()); + + EXPECT_NO_THROW(pool.deallocate()); +} + +/** + * @brief size of the pool + */ +TEST(MemoryPool, size_01_p) { + nntrainer::MemoryPool pool; + + EXPECT_EQ(pool.size(), 0u); +} + +/** + * @brief size of the pool + */ +TEST(MemoryPool, size_02_p) { + nntrainer::MemoryPool pool; + + pool.requestMemory(1, 4, 5); + EXPECT_EQ(pool.size(), 0u); +} + +/** + * @brief size of the pool + */ +TEST(MemoryPool, size_03_p) { + nntrainer::MemoryPool pool; + + pool.requestMemory(1, 4, 5); + EXPECT_EQ(pool.size(), 0u); + + pool.planLayout(nntrainer::BasicPlanner()); + EXPECT_EQ(pool.size(), 1u); + + pool.allocate(); + EXPECT_EQ(pool.size(), 1u); + + EXPECT_NO_THROW(pool.deallocate()); +} + +/** + * @brief min requirement + */ +TEST(MemoryPool, min_mem_req_01_p) { + nntrainer::MemoryPool pool; + + EXPECT_EQ(pool.minMemoryRequirement(), 0u); +} + +/** + * @brief min requirement + */ +TEST(MemoryPool, min_mem_req_02_p) { + nntrainer::MemoryPool pool; + + pool.requestMemory(1, 4, 5); + EXPECT_EQ(pool.minMemoryRequirement(), 1u); + + pool.planLayout(nntrainer::BasicPlanner()); + EXPECT_EQ(pool.minMemoryRequirement(), 1u); +} + +/** + * @brief min requirement + */ +TEST(MemoryPool, min_mem_req_03_p) { + nntrainer::MemoryPool pool; + + pool.requestMemory(1, 4, 5); + EXPECT_EQ(pool.minMemoryRequirement(), 1u); + + pool.planLayout(nntrainer::BasicPlanner()); + EXPECT_EQ(pool.minMemoryRequirement(), 1u); + + /** exact overlap */ + pool.requestMemory(2, 4, 5); + EXPECT_EQ(pool.minMemoryRequirement(), 3u); + + pool.planLayout(nntrainer::BasicPlanner()); + EXPECT_EQ(pool.minMemoryRequirement(), 3u); + + /** ending overlap */ + pool.requestMemory(3, 2, 5); + EXPECT_EQ(pool.minMemoryRequirement(), 6u); + + pool.planLayout(nntrainer::BasicPlanner()); + EXPECT_EQ(pool.minMemoryRequirement(), 6u); + + /** start overlap */ + pool.requestMemory(4, 4, 8); + + pool.planLayout(nntrainer::BasicPlanner()); + EXPECT_EQ(pool.minMemoryRequirement(), 10u); + + /** complete overlap */ + pool.requestMemory(5, 1, 10); + EXPECT_EQ(pool.minMemoryRequirement(), 15u); + + pool.planLayout(nntrainer::BasicPlanner()); + EXPECT_EQ(pool.minMemoryRequirement(), 15u); +} + +/** + * @brief min requirement + */ +TEST(MemoryPool, min_mem_req_04_p) { + nntrainer::MemoryPool pool; + + pool.requestMemory(1, 5, 10); + EXPECT_EQ(pool.minMemoryRequirement(), 1u); + + pool.planLayout(nntrainer::BasicPlanner()); + EXPECT_EQ(pool.minMemoryRequirement(), 1u); + + /** partial overlap */ + pool.requestMemory(2, 1, 8); + EXPECT_EQ(pool.minMemoryRequirement(), 3u); + + pool.planLayout(nntrainer::BasicPlanner()); + EXPECT_EQ(pool.minMemoryRequirement(), 3u); + + /** ending overlap */ + pool.requestMemory(3, 7, 12); + EXPECT_EQ(pool.minMemoryRequirement(), 6u); + + pool.planLayout(nntrainer::BasicPlanner()); + EXPECT_EQ(pool.minMemoryRequirement(), 6u); +} + +/** + * @brief min requirement + */ +TEST(MemoryPool, min_mem_req_05_p) { + nntrainer::MemoryPool pool; + + pool.requestMemory(1, 5, 10); + EXPECT_EQ(pool.minMemoryRequirement(), 1u); + + pool.planLayout(nntrainer::BasicPlanner()); + EXPECT_EQ(pool.minMemoryRequirement(), 1u); + + /** partial overlap */ + pool.requestMemory(2, 1, 8); + EXPECT_EQ(pool.minMemoryRequirement(), 3u); + + pool.planLayout(nntrainer::BasicPlanner()); + EXPECT_EQ(pool.minMemoryRequirement(), 3u); + + /** ending overlap with matching ends */ + pool.requestMemory(3, 8, 12); + EXPECT_EQ(pool.minMemoryRequirement(), 4u); + + pool.planLayout(nntrainer::BasicPlanner()); + EXPECT_EQ(pool.minMemoryRequirement(), 4u); +} + +/** + * @brief min requirement + */ +TEST(MemoryPool, min_mem_req_06_p) { + nntrainer::MemoryPool pool; + + pool.requestMemory(1, 5, 10); + EXPECT_EQ(pool.minMemoryRequirement(), 1u); + + pool.planLayout(nntrainer::BasicPlanner()); + EXPECT_EQ(pool.minMemoryRequirement(), 1u); + + /** partial overlap */ + pool.requestMemory(2, 1, 5); + EXPECT_EQ(pool.minMemoryRequirement(), 2u); + + pool.planLayout(nntrainer::BasicPlanner()); + EXPECT_EQ(pool.minMemoryRequirement(), 2u); + + /** ending overlap with matching ends */ + pool.requestMemory(3, 10, 12); + EXPECT_EQ(pool.minMemoryRequirement(), 3u); + + pool.planLayout(nntrainer::BasicPlanner()); + EXPECT_EQ(pool.minMemoryRequirement(), 3u); + + /** ending overlap with matching ends */ + pool.requestMemory(1, 12, 13); + EXPECT_EQ(pool.minMemoryRequirement(), 3u); + + pool.planLayout(nntrainer::BasicPlanner()); + EXPECT_EQ(pool.minMemoryRequirement(), 3u); +} + +/** + * @brief get memory + */ +TEST(MemoryPool, get_memory_01_n) { + nntrainer::MemoryPool pool; + + EXPECT_THROW(pool.getMemory(1), std::invalid_argument); +} + +/** + * @brief get memory + */ +TEST(MemoryPool, get_memory_02_n) { + nntrainer::MemoryPool pool; + + auto idx = pool.requestMemory(1, 4, 5); + EXPECT_THROW(pool.getMemory(idx), std::invalid_argument); +} + +/** + * @brief get memory + */ +TEST(MemoryPool, get_memory_03_n) { + nntrainer::MemoryPool pool; + + auto idx = pool.requestMemory(1, 4, 5); + EXPECT_NO_THROW(pool.planLayout(nntrainer::BasicPlanner())); + EXPECT_NO_THROW(pool.allocate()); + + EXPECT_ANY_THROW(pool.getMemory(idx + 1)); + + EXPECT_NO_THROW(pool.deallocate()); +} + +/** + * @brief get memory + */ +TEST(MemoryPool, get_memory_04_p) { + nntrainer::MemoryPool pool; + void *mem; + + auto idx = pool.requestMemory(1, 4, 5); + EXPECT_NO_THROW(pool.planLayout(nntrainer::BasicPlanner())); + EXPECT_NO_THROW(pool.allocate()); + + EXPECT_NO_THROW(mem = pool.getMemory(idx)); + EXPECT_NE(mem, nullptr); + + EXPECT_NO_THROW(pool.deallocate()); +} + +/** + * @brief validate memory full overlap + */ +TEST_P(MemoryPlannerValidate, validate_memory_full_overlap) { + nntrainer::MemoryPool pool; + std::mt19937 rng; + std::uniform_int_distribution dist(1, MEM_BYTES); + std::uniform_int_distribution dist_interval(1, INTERVAL_SIZE); + + std::vector tokens(MEM_QUANT); + std::vector memory_size(MEM_QUANT); + std::vector ptrs(MEM_QUANT); + + for (unsigned int idx = 0; idx < MEM_QUANT; idx++) { + memory_size[idx] = dist(rng); + unsigned int start = 1; + unsigned int end = start + dist_interval(rng); + EXPECT_NO_THROW(tokens[idx] = + pool.requestMemory(memory_size[idx], start, end)); + } + + EXPECT_NO_THROW(pool.planLayout(*planner.get())); + EXPECT_EQ(pool.size(), + std::accumulate(memory_size.begin(), memory_size.end(), 0u)); + EXPECT_NO_THROW(pool.allocate()); + + for (unsigned int idx = 0; idx < MEM_QUANT; idx++) + EXPECT_NO_THROW(ptrs[idx] = pool.getMemory(tokens[idx])); + + /** write data to memory */ + for (unsigned int idx = 0; idx < MEM_QUANT; idx++) + memset(ptrs[idx], idx, memory_size[idx]); + + /** verify data in memory */ + for (unsigned int idx = 0; idx < MEM_QUANT; idx++) { + std::vector golden(memory_size[idx], idx); + memcmp(ptrs[idx], &golden[0], memory_size[idx]); + } + + pool.deallocate(); +} + +/** + * @brief validate memory no overlap + */ +TEST_P(MemoryPlannerValidate, validate_memory_no_overlap) { + nntrainer::MemoryPool pool; + std::mt19937 rng; + std::uniform_int_distribution dist(1, MEM_BYTES); + std::uniform_int_distribution dist_interval(1, INTERVAL_SIZE); + + std::vector tokens(MEM_QUANT); + std::vector memory_size(MEM_QUANT); + std::vector ptrs(MEM_QUANT); + + unsigned int prev_idx = 0; + for (unsigned int idx = 0; idx < MEM_QUANT; idx++) { + memory_size[idx] = dist(rng); + unsigned int start = prev_idx; + unsigned int end = start + dist_interval(rng); + EXPECT_NO_THROW(tokens[idx] = + pool.requestMemory(memory_size[idx], start, end)); + prev_idx = end; + } + + EXPECT_NO_THROW(pool.planLayout(*planner.get())); + EXPECT_EQ(pool.size(), + std::accumulate(memory_size.begin(), memory_size.end(), 0u)); + EXPECT_NO_THROW(pool.allocate()); + + for (unsigned int idx = 0; idx < MEM_QUANT; idx++) + EXPECT_NO_THROW(ptrs[idx] = pool.getMemory(tokens[idx])); + + /** write data to memory */ + for (unsigned int idx = 0; idx < MEM_QUANT; idx++) + memset(ptrs[idx], idx, memory_size[idx]); + + /** verify data in memory */ + for (unsigned int idx = 0; idx < MEM_QUANT; idx++) { + std::vector golden(memory_size[idx], idx); + memcmp(ptrs[idx], &golden[0], memory_size[idx]); + } + + pool.deallocate(); +} + +/** + * @brief validate memory partial overlap + */ +TEST_P(MemoryPlannerValidate, validate_memory_partial_overlap) { + nntrainer::MemoryPool pool; + std::mt19937 rng; + std::uniform_int_distribution dist(1, MEM_BYTES); + std::uniform_int_distribution dist_interval(1, INTERVAL_SIZE); + std::uniform_int_distribution dist_interval_start(1, 100); + + std::vector tokens(MEM_QUANT); + std::vector memory_size(MEM_QUANT); + std::vector ptrs(MEM_QUANT); + + for (unsigned int idx = 0; idx < MEM_QUANT; idx++) { + memory_size[idx] = dist(rng); + unsigned int start = dist_interval_start(rng); + unsigned int end = start + dist_interval(rng); + EXPECT_NO_THROW(tokens[idx] = + pool.requestMemory(memory_size[idx], start, end)); + } + + EXPECT_NO_THROW(pool.planLayout(*planner.get())); + EXPECT_EQ(pool.size(), + std::accumulate(memory_size.begin(), memory_size.end(), 0u)); + EXPECT_NO_THROW(pool.allocate()); + + for (unsigned int idx = 0; idx < MEM_QUANT; idx++) + EXPECT_NO_THROW(ptrs[idx] = pool.getMemory(tokens[idx])); + + /** write data to memory */ + for (unsigned int idx = 0; idx < MEM_QUANT; idx++) + memset(ptrs[idx], idx, memory_size[idx]); + + /** verify data in memory */ + for (unsigned int idx = 0; idx < MEM_QUANT; idx++) { + std::vector golden(memory_size[idx], idx); + memcmp(ptrs[idx], &golden[0], memory_size[idx]); + } + + pool.deallocate(); +} -- 2.7.4