You've already forked mariadb-columnstore-engine
							
							
				mirror of
				https://github.com/mariadb-corporation/mariadb-columnstore-engine.git
				synced 2025-10-30 07:25:34 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			289 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			289 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* Copyright (C) 2024 MariaDB Corporation
 | ||
| 
 | ||
|    This program is free software; you can redistribute it and/or
 | ||
|    modify it under the terms of the GNU General Public License
 | ||
|    as published by the Free Software Foundation; version 2 of
 | ||
|    the License.
 | ||
| 
 | ||
|    This program is distributed in the hope that it will be useful,
 | ||
|    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||
|    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||
|    GNU General Public License for more details.
 | ||
| 
 | ||
|    You should have received a copy of the GNU General Public License
 | ||
|    along with this program; if not, write to the Free Software
 | ||
|    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 | ||
|    MA 02110-1301, USA. */
 | ||
| #include <gtest/gtest.h>
 | ||
| #include <sys/types.h>
 | ||
| #include <atomic>
 | ||
| #include <cstddef>
 | ||
| #include <memory>
 | ||
| #include <thread>
 | ||
| 
 | ||
| #include "countingallocator.h"
 | ||
| #include "poolallocator.h"
 | ||
| 
 | ||
| using namespace allocators;
 | ||
| using namespace utils;
 | ||
| 
 | ||
| /**
 | ||
|  * Тест явного задания windowSize при создании:
 | ||
|  */
 | ||
| TEST(PoolAllocatorTest, CustomWindowSize)
 | ||
| {
 | ||
|   const unsigned CUSTOM_SIZE = 1024;
 | ||
|   PoolAllocator pa(CUSTOM_SIZE);
 | ||
|   EXPECT_EQ(pa.getWindowSize(), CUSTOM_SIZE);
 | ||
|   EXPECT_EQ(pa.getMemUsage(), 0ULL);
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * Тест базового выделения небольшого блока памяти:
 | ||
|  *  - Выделяем блок меньше, чем windowSize.
 | ||
|  *  - Проверяем, что memUsage увеличился на размер выделенного блока.
 | ||
|  *  - Указатель не должен быть равен nullptr.
 | ||
|  */
 | ||
| TEST(PoolAllocatorTest, AllocateSmallBlock)
 | ||
| {
 | ||
|   PoolAllocator pa;
 | ||
|   uint64_t initialUsage = pa.getMemUsage();
 | ||
|   const uint64_t ALLOC_SIZE = 128;
 | ||
| 
 | ||
|   void* ptr = pa.allocate(ALLOC_SIZE);
 | ||
| 
 | ||
|   ASSERT_NE(ptr, nullptr);
 | ||
|   EXPECT_EQ(pa.getMemUsage(), initialUsage + ALLOC_SIZE);
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * Тест выделения блока памяти больше, чем windowSize (Out-Of-Band - OOB):
 | ||
|  *  - Проверяем, что memUsage увеличился на нужное количество байт.
 | ||
|  *  - Указатель не nullptr.
 | ||
|  */
 | ||
| TEST(PoolAllocatorTest, AllocateOOBBlock)
 | ||
| {
 | ||
|   // Выбираем размер гарантированно больше, чем окно по умолчанию
 | ||
|   const uint64_t BIG_BLOCK_SIZE = PoolAllocator::DEFAULT_WINDOW_SIZE + 1024;
 | ||
|   PoolAllocator pa;
 | ||
|   uint64_t initialUsage = pa.getMemUsage();
 | ||
| 
 | ||
|   void* ptr = pa.allocate(BIG_BLOCK_SIZE);
 | ||
|   ASSERT_NE(ptr, nullptr);
 | ||
|   EXPECT_EQ(pa.getMemUsage(), initialUsage + BIG_BLOCK_SIZE);
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * Тест деаллокации (deallocate) Out-Of-Band блока:
 | ||
|  *  - Убеждаемся, что после deallocate memUsage возвращается к исходному значению.
 | ||
|  */
 | ||
| TEST(PoolAllocatorTest, DeallocateOOBBlock)
 | ||
| {
 | ||
|   PoolAllocator pa;
 | ||
|   // Блок больше windowSize
 | ||
|   const uint64_t BIG_BLOCK_SIZE = pa.getWindowSize() + 1024;
 | ||
| 
 | ||
|   uint64_t initialUsage = pa.getMemUsage();
 | ||
|   void* ptr = pa.allocate(BIG_BLOCK_SIZE);
 | ||
|   ASSERT_NE(ptr, nullptr);
 | ||
|   EXPECT_EQ(pa.getMemUsage(), initialUsage + BIG_BLOCK_SIZE);
 | ||
| 
 | ||
|   pa.deallocate(ptr);
 | ||
|   EXPECT_EQ(pa.getMemUsage(), initialUsage);
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * Тест деаллокации блока, который был выделен внутри "windowSize".
 | ||
|  * По текущей логике PoolAllocator::deallocate для "маленьких" блоков ничего не делает.
 | ||
|  * Основная проверка – что код не падает и не меняет memUsage.
 | ||
|  */
 | ||
| TEST(PoolAllocatorTest, DeallocateSmallBlock)
 | ||
| {
 | ||
|   PoolAllocator pa;
 | ||
|   const uint64_t ALLOC_SIZE = 128;
 | ||
| 
 | ||
|   uint64_t initialUsage = pa.getMemUsage();
 | ||
|   void* ptr = pa.allocate(ALLOC_SIZE);
 | ||
|   ASSERT_NE(ptr, nullptr);
 | ||
|   EXPECT_EQ(pa.getMemUsage(), initialUsage + ALLOC_SIZE);
 | ||
| 
 | ||
|   // Попытка деаллокации "маленького" блока – в текущей реализации
 | ||
|   // код его не возвращает в пул, следовательно memUsage не уменьшится.
 | ||
|   pa.deallocate(ptr);
 | ||
|   EXPECT_EQ(pa.getMemUsage(), initialUsage + ALLOC_SIZE);
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * Тест полного освобождения памяти (deallocateAll):
 | ||
|  *  - Выделяем несколько блоков: и маленький, и большой.
 | ||
|  *  - После вызова deallocateAll всё должно освободиться, memUsage вернётся к 0.
 | ||
|  */
 | ||
| TEST(PoolAllocatorTest, DeallocateAll)
 | ||
| {
 | ||
|   PoolAllocator pa;
 | ||
|   // Блок в пределах windowSize
 | ||
|   const uint64_t SMALL_BLOCK = 256;
 | ||
|   // Блок Out-Of-Band
 | ||
|   const uint64_t LARGE_BLOCK = pa.getWindowSize() + 1024;
 | ||
| 
 | ||
|   pa.allocate(SMALL_BLOCK);
 | ||
|   pa.allocate(LARGE_BLOCK);
 | ||
|   // Убедимся, что memUsage > 0
 | ||
|   EXPECT_GT(pa.getMemUsage(), 0ULL);
 | ||
| 
 | ||
|   // Освобождаем всё
 | ||
|   pa.deallocateAll();
 | ||
|   EXPECT_EQ(pa.getMemUsage(), 0ULL);
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * Тест копирующего оператора присваивания:
 | ||
|  *  - Проверяем, что параметры (allocSize, tmpSpace, useLock) копируются.
 | ||
|  *  - Однако выделенная память не копируется (т.к. после operator= вызывается deallocateAll).
 | ||
|  */
 | ||
| TEST(PoolAllocatorTest, AssignmentOperator)
 | ||
| {
 | ||
|   PoolAllocator pa1(2048, true, true);  // windowSize=2048, tmpSpace=true, useLock=true
 | ||
|   // Выделяем немного памяти
 | ||
|   pa1.allocate(100);
 | ||
|   pa1.allocate(200);
 | ||
| 
 | ||
|   EXPECT_EQ(pa1.getWindowSize(), 2048U);
 | ||
|   EXPECT_TRUE(pa1.getMemUsage() > 0);
 | ||
| 
 | ||
|   // С помощью оператора присваивания: pa2 = pa1
 | ||
|   PoolAllocator pa2;
 | ||
|   pa2 = pa1;  // После этого deallocateAll() вызывается внутри operator= (в нашем коде)
 | ||
| 
 | ||
|   // Проверяем скопированные поля:
 | ||
|   EXPECT_EQ(pa2.getWindowSize(), 2048U);
 | ||
|   // tmpSpace и useLock также должны совпасть
 | ||
|   // (В данном коде напрямую нет геттеров для них,
 | ||
|   //  но, если нужно, можете добавить соответствующие методы или рефлексировать код.)
 | ||
| 
 | ||
|   // Проверяем, что у pa2 memUsage == 0 после deallocateAll
 | ||
|   EXPECT_EQ(pa2.getMemUsage(), 0ULL);
 | ||
|   // А у pa1 осталась прежняя статистика использования памяти,
 | ||
|   // т.к. operator= сделал deallocateAll только внутри pa2.
 | ||
|   EXPECT_TRUE(pa1.getMemUsage() > 0);
 | ||
| }
 | ||
| 
 | ||
| TEST(PoolAllocatorTest, MultithreadedAllocationWithLock)
 | ||
| {
 | ||
|   PoolAllocator pa(PoolAllocator::DEFAULT_WINDOW_SIZE, false, true);
 | ||
|   // useLock = true
 | ||
| 
 | ||
|   const int THREAD_COUNT = 4;
 | ||
|   const uint64_t ALLOC_PER_THREAD = 1024;
 | ||
|   std::vector<std::thread> threads;
 | ||
| 
 | ||
|   // Стартовое значение
 | ||
|   uint64_t initialUsage = pa.getMemUsage();
 | ||
| 
 | ||
|   // Запускаем несколько потоков, каждый сделает небольшое кол-во аллокаций
 | ||
|   for (int i = 0; i < THREAD_COUNT; i++)
 | ||
|   {
 | ||
|     threads.emplace_back(
 | ||
|         [&pa]()
 | ||
|         {
 | ||
|           for (int j = 0; j < 10; j++)
 | ||
|           {
 | ||
|             pa.allocate(ALLOC_PER_THREAD);
 | ||
|           }
 | ||
|         });
 | ||
|   }
 | ||
| 
 | ||
|   for (auto& th : threads)
 | ||
|     th.join();
 | ||
| 
 | ||
|   uint64_t expected = initialUsage + THREAD_COUNT * 10ULL * ALLOC_PER_THREAD;
 | ||
|   EXPECT_GE(pa.getMemUsage(), expected);
 | ||
| }
 | ||
| 
 | ||
| static const constexpr int64_t MemoryAllowance = 1 * 1024 * 1024;
 | ||
| 
 | ||
| // Test Fixture for CounterAllocator
 | ||
| class PoolallocatorTest : public ::testing::Test
 | ||
| {
 | ||
|  protected:
 | ||
|   // Atomic counter to track allocated memory
 | ||
|   std::atomic<int64_t> allocatedMemory{MemoryAllowance};
 | ||
| 
 | ||
|   // Custom allocator instance
 | ||
|   CountingAllocator<PoolAllocatorBufType> allocator;
 | ||
| 
 | ||
|   // Constructor
 | ||
|   PoolallocatorTest()
 | ||
|    : allocatedMemory(MemoryAllowance)
 | ||
|    , allocator(&allocatedMemory, MemoryAllowance / 1000, MemoryAllowance / 100)
 | ||
|   {
 | ||
|   }
 | ||
| 
 | ||
|   // Destructor
 | ||
|   ~PoolallocatorTest() override = default;
 | ||
| };
 | ||
| 
 | ||
| // Тест для проверки учёта потребления памяти в PoolAllocator.
 | ||
| TEST_F(PoolallocatorTest, AllocationWithAccounting)
 | ||
| {
 | ||
|   int bufSize1 = 512;
 | ||
|   const unsigned CUSTOM_SIZE = 1024;
 | ||
|   PoolAllocator pa(allocator, CUSTOM_SIZE, false, true);
 | ||
|   EXPECT_EQ(pa.getWindowSize(), CUSTOM_SIZE);
 | ||
|   EXPECT_EQ(pa.getMemUsage(), 0ULL);
 | ||
|   auto* ptr = pa.allocate(bufSize1);
 | ||
| 
 | ||
|   EXPECT_NE(ptr, nullptr);
 | ||
|   EXPECT_LE(allocatedMemory.load(), MemoryAllowance - bufSize1);
 | ||
|   EXPECT_LE(allocatedMemory.load(), MemoryAllowance - CUSTOM_SIZE);
 | ||
|   pa.deallocate(ptr);
 | ||
|   // B/c this PoolAllocator frees memory only when it's destroyed.
 | ||
|   EXPECT_LE(allocatedMemory.load(), MemoryAllowance - bufSize1);
 | ||
|   EXPECT_LE(allocatedMemory.load(), MemoryAllowance - CUSTOM_SIZE);
 | ||
| 
 | ||
|   int bufSize2 = 64536;
 | ||
|   auto* ptr1 = pa.allocate(bufSize2);
 | ||
| 
 | ||
|   EXPECT_NE(ptr1, nullptr);
 | ||
|   EXPECT_LE(allocatedMemory.load(), MemoryAllowance - bufSize2 - CUSTOM_SIZE);
 | ||
| 
 | ||
|   pa.deallocate(ptr1);
 | ||
|   EXPECT_LE(allocatedMemory.load(), MemoryAllowance - CUSTOM_SIZE);
 | ||
|   EXPECT_GE(allocatedMemory.load(), MemoryAllowance - bufSize2);
 | ||
| }
 | ||
| 
 | ||
| TEST_F(PoolallocatorTest, MultithreadedAccountedAllocationWithLock)
 | ||
| {
 | ||
|   const unsigned CUSTOM_SIZE = 1024;
 | ||
|   PoolAllocator pa(allocator, CUSTOM_SIZE, false, true);
 | ||
| 
 | ||
|   const int THREAD_COUNT = 4;
 | ||
|   const uint64_t ALLOC_PER_THREAD = 1024;
 | ||
|   const uint64_t NUM_ALLOCS_PER_THREAD = 10;
 | ||
|   std::vector<std::thread> threads;
 | ||
| 
 | ||
|   // Стартовое значение
 | ||
|   uint64_t initialUsage = pa.getMemUsage();
 | ||
| 
 | ||
|   // Запускаем несколько потоков, каждый сделает небольшое кол-во аллокаций
 | ||
|   for (int i = 0; i < THREAD_COUNT; i++)
 | ||
|   {
 | ||
|     threads.emplace_back(
 | ||
|         [&pa]()
 | ||
|         {
 | ||
|           for (uint64_t j = 0; j < NUM_ALLOCS_PER_THREAD; j++)
 | ||
|           {
 | ||
|             pa.allocate(ALLOC_PER_THREAD);
 | ||
|           }
 | ||
|         });
 | ||
|   }
 | ||
| 
 | ||
|   for (auto& th : threads)
 | ||
|     th.join();
 | ||
| 
 | ||
|   uint64_t expected = initialUsage + THREAD_COUNT * 10ULL * ALLOC_PER_THREAD;
 | ||
|   EXPECT_GE(pa.getMemUsage(), expected);
 | ||
|   // 2 * CUSTOM_SIZE semantics is structs allocation overhead.
 | ||
|   EXPECT_GE(allocatedMemory.load(),
 | ||
|             MemoryAllowance - (THREAD_COUNT * ALLOC_PER_THREAD * NUM_ALLOCS_PER_THREAD) - 3 * CUSTOM_SIZE);
 | ||
| } |