You've already forked mariadb-columnstore-engine
mirror of
https://github.com/mariadb-corporation/mariadb-columnstore-engine.git
synced 2025-07-29 08:21:15 +03:00
feat(): propagated changes into SLTPoolAllocator and friends
This commit is contained in:
287
tests/poolallocator.cpp
Normal file
287
tests/poolallocator.cpp
Normal file
@ -0,0 +1,287 @@
|
||||
/* 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 = 10 * 1024 * 1024;
|
||||
|
||||
// Test Fixture for AtomicCounterAllocator
|
||||
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 / 100)
|
||||
{
|
||||
}
|
||||
|
||||
// Destructor
|
||||
~PoolallocatorTest() override = default;
|
||||
};
|
||||
|
||||
// Тест для проверки учёта потребления памяти в PoolAllocator.
|
||||
TEST_F(PoolallocatorTest, AllocationWithAccounting)
|
||||
{
|
||||
int bufSize = 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(bufSize);
|
||||
|
||||
EXPECT_NE(ptr, nullptr);
|
||||
EXPECT_LE(allocatedMemory.load(), MemoryAllowance - bufSize);
|
||||
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 - bufSize);
|
||||
EXPECT_LE(allocatedMemory.load(), MemoryAllowance - CUSTOM_SIZE);
|
||||
|
||||
bufSize = 64536;
|
||||
auto* ptr1 = pa.allocate(bufSize);
|
||||
|
||||
EXPECT_NE(ptr1, nullptr);
|
||||
EXPECT_LE(allocatedMemory.load(), MemoryAllowance - bufSize);
|
||||
|
||||
pa.deallocate(ptr1);
|
||||
EXPECT_LE(allocatedMemory.load(), MemoryAllowance - CUSTOM_SIZE);
|
||||
EXPECT_GE(allocatedMemory.load(), MemoryAllowance - bufSize);
|
||||
}
|
||||
|
||||
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) - 2 * CUSTOM_SIZE);
|
||||
}
|
Reference in New Issue
Block a user