#include <chrono>
이런 식으로 chrono헤더를 포함시키고,
std::chrono::steady_clock::time_point Start{ std::chrono::steady_clock::now() };
원하는 시점에서 chrono::steady_clock::now()를 이용해 현재 timepoint를 측정한다.
auto End{ std::chrono::steady_clock::now() };
이런 식으로 원하는 시점에 또 한번 timepoint를 측정해서
auto Diff{ End - Start };
이런식으로 두 timepoint의 차를 통해 시간을 측정한다.
double DiffCount = std::chrono::duration<double, std::milli>(Diff).count();
시간차는 이런식으로 std::milli를 통해 ms로 나타낼 수 있다.
간단한 메모리 풀을 구현 후,
malloc/free , new/ delete연산과 얼마나 차이나는지 측정하는데 사용했다.
class FMemoryPool
{
public:
FMemoryPool(const size_t InChunkSize, const size_t InChunkCount) noexcept
: ChunkSize(InChunkSize)
, ChunkCount(InChunkCount)
{
const size_t Align = 8;
const size_t AlignedChunkSize = ((InChunkSize + (Align - 1)) & ~(Align - 1));
const size_t TotalMemorySize = AlignedChunkSize * ChunkCount;
StartAddress = ::_aligned_malloc(TotalMemorySize, Align);
ActiveMemoryBlock.reserve(InChunkCount);
for (size_t i = 0; i < ChunkCount; ++i)
{
uint8_t* Memory = (uint8_t*)StartAddress + i * AlignedChunkSize;
ActiveMemoryBlock.emplace_back(Memory);
}
}
FMemoryPool(const FMemoryPool&) = delete;
FMemoryPool& operator=(const FMemoryPool&) = delete;
~FMemoryPool()
{
_ASSERT(StartAddress);
::_aligned_free(StartAddress);
}
void* malloc()
{
if (ActiveMemoryBlock.empty())
{
// 남은 Memory가 pool에 없다
_ASSERT(false);
return nullptr;
}
void* Memory = ActiveMemoryBlock.back();
ActiveMemoryBlock.pop_back();
return Memory;
}
void free(void* InMemory)
{
ActiveMemoryBlock.emplace_back(InMemory);
}
private:
const size_t ChunkSize;
const size_t ChunkCount;
void* StartAddress = nullptr;
std::vector<void*> ActiveMemoryBlock;
};
구현한 메모리풀은 void형 포인터를 담은 벡터다.
간단히 설명하면 원하는 크기만큼 메모리를 미리 할당해서 void* 형으로 가지고 있다가
void*형으로 반환하는데 해당 포인터를 타입캐스팅해서 사용하면 된다.
생성자에서 들어온 chunksize를 Align을 통해 메모리를 할당하는 크기를 맞춘다.
지금 코드처럼 8로 설정했다면 8의 배수로 맞춰지게끔 메모리를 할당한다.
const size_t AlignedChunkSize = ((InChunkSize + (Align - 1)) & ~(Align - 1));
이 부분은 아래 스택오버플로우글에 정리 되어있는 코드이다.
https://stackoverflow.com/questions/45213511/formula-for-memory-alignment
해당 InChunkSize에 Align-1 더해주고 align-1의 비트들을 &~ 연산을 통해 없앤다.
ex)
InChunkSize가 11이고, Align이 8이라면
(InChunkSize + (Align - 1) = 18
Align-1 = 7 = 0000 0111
~(Align-1) = 1111 1000
여기에 &연산을 하게되면 2^3 밑의 비트들은 다 0이 되고 그 위는 그대로 유지된다.
18 = 0001 0010
&연산시 0001 0000 = 16
이런식으로 청크의 크기를 설정한 align의 배수로 맞춰준 후 alignedchunksize에 저장한다.
totalmemorysize는 alignedchunksize * chunkcount가 되고,
start address에 _aligned_malloc을 통해 totalmemorysize와 align인자를 넣어
할당된 메모리블록의 시작 주소를 할당해준다.
ActiveMemoryBlock.reserve(InChunkCount);
reserve를 통해 capacity를 InChunkCount로 설정해준다.
그리고 반복문을 통해 ChunkCount갯수만큼
for (size_t i = 0; i < ChunkCount; ++i)
{
uint8_t* Memory = (uint8_t*)StartAddress + i * AlignedChunkSize;
ActiveMemoryBlock.emplace_back(Memory);
}
ActiveMemoryBlock에 emplace_back함수를 통해 각 청크의 시작 주소를 넣어준다.
FMemoryPool(const FMemoryPool&) = delete;
FMemoryPool& operator=(const FMemoryPool&) = delete;
복사생성자와 대입연산자는 delete연산자를 통해 막아둔다.
void* malloc()
{
if (ActiveMemoryBlock.empty())
{
// 남은 Memory가 pool에 없다
_ASSERT(false);
return nullptr;
}
void* Memory = ActiveMemoryBlock.back();
ActiveMemoryBlock.pop_back();
return Memory;
}
따로 구현한 malloc에서는 만약 미리할당한 ActiveMemoryBlock이 empty상태라면
남은 메모리가 없을 때 할당 요청이 들어온 것이므로 nullptr을 반환한다.
empty상태가 아니라면 ActiveMemoryBlock의 마지막 포인터 원소를 전달하고
해당 메모리를 pop_back 시켜준다.
void free(void* InMemory)
{
ActiveMemoryBlock.emplace_back(InMemory);
}
free는 간단하다.
free는 사용하던 메모리를 해제하는 과정이므로
메모리풀 입장에선 해당 메모리를 다시 벡터에 넣어주면 된다.
class FClass
{
public:
FClass()
{
//std::cout << __FUNCTION__ << std::endl;
}
FClass(int InValue)
: Data2(InValue)
{
}
~FClass()
{
//std::cout << __FUNCTION__ << std::endl;
}
private:
char Data[1024] = {};
int Data2 = {};
};
이렇게 생긴 간단한 FClass 클래스를 100만번 malloc/free했을 때, 시간 측정을 해 비교해보기로 했다.
FClass** Arr = new FClass * [MaxCount];
std::chrono::steady_clock::time_point Start{ std::chrono::steady_clock::now() };
{
for (size_t i = 0; i < MaxCount; ++i)
{
FClass* Test = (FClass*)malloc(sizeof(FClass));
Arr[i] = Test;
}
for (size_t i = 0; i < MaxCount; ++i)
{
free(Arr[i]);
}
}
auto End{ std::chrono::steady_clock::now() };
auto Diff{ End - Start };
delete[] Arr;
double DiffCount = std::chrono::duration<double, std::milli>(Diff).count();
#if _DEBUG
std::cout << std::format("Debug: ");
#else
std::cout << std::format("Release: ");
#endif
std::cout << std::format("[FClass] malloc, free: {}ms\n", DiffCount);


이정도 시간이 나온다.
FClass** Arr = new FClass * [MaxCount];
std::chrono::steady_clock::time_point Start{ std::chrono::steady_clock::now() };
{
for (size_t i = 0; i < MaxCount; ++i)
{
FClass* Test = new FClass();
Arr[i] = Test;
}
for (size_t i = 0; i < MaxCount; ++i)
{
delete Arr[i];
}
}
auto End{ std::chrono::steady_clock::now() };
auto Diff{ End - Start };
delete[] Arr;
double DiffCount = std::chrono::duration<double, std::milli>(Diff).count();
#if _DEBUG
std::cout << std::format("Debug: ");
#else
std::cout << std::format("Release: ");
#endif
std::cout << std::format("[FClass] new, delete: {}ms\n", DiffCount);


malloc, free보다는 좀 더 느리다.
FClass** Arr = new FClass * [MaxCount];
FMemoryPool MemoryPool = FMemoryPool(sizeof(FClass), MaxCount);
std::chrono::steady_clock::time_point Start{ std::chrono::steady_clock::now() };
{
for (size_t i = 0; i < MaxCount; ++i)
{
FClass* Test = (FClass*)MemoryPool.malloc();
Arr[i] = Test;
}
for (size_t i = 0; i < MaxCount; ++i)
{
MemoryPool.free(Arr[i]);
}
}
auto End{ std::chrono::steady_clock::now() };
auto Diff{ End - Start };
delete[] Arr;
double DiffCount = std::chrono::duration<double, std::milli>(Diff).count();
#if _DEBUG
std::cout << std::format("Debug: ");
#else
std::cout << std::format("Release: ");
#endif
std::cout << std::format("[FClass] Custom MemoryPool: {}ms\n", DiffCount);


FClass** Arr = new FClass * [MaxCount];
boost::pool<> MemoryPool(sizeof(FClass), MaxCount);
int* TryAlloc = (int*)MemoryPool.malloc();
MemoryPool.free(TryAlloc);
std::chrono::steady_clock::time_point Start{ std::chrono::steady_clock::now() };
{
for (size_t i = 0; i < MaxCount; ++i)
{
FClass* Test = (FClass*)MemoryPool.malloc();
Arr[i] = Test;
}
for (size_t i = 0; i < MaxCount; ++i)
{
MemoryPool.free(Arr[i]);
}
}
auto End{ std::chrono::steady_clock::now() };
auto Diff{ End - Start };
delete[] Arr;
double DiffCount = std::chrono::duration<double, std::milli>(Diff).count();
#if _DEBUG
std::cout << std::format("Debug: ");
#else
std::cout << std::format("Release: ");
#endif
std::cout << std::format("[FClass] boost MemoryPool: {}ms\n", DiffCount);
boost라이브러리를 설치한 후 , pool을 사용해봤다.

디버깅에서는 custom memorypool이 훨씬 느리지만
릴리즈 모드에서는 오히려 boost라이브러리의 memory pool이 느리게 나왔다.
디버그모드에서 벡터가 느리기도 하고 애초에 단순 할당 해제만 비교해서 그런것 같다.