ClickHouse/dbms/include/DB/Common/ArenaWithFreeLists.h

100 lines
4.0 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#pragma once
#include <DB/Common/Arena.h>
#include <DB/Common/BitHelpers.h>
namespace DB
{
/** В отличие от Arena, позволяет освобождать (для последующего повторного использования)
* выделенные ранее (не обязательно только что) куски памяти.
* Для этого, запрашиваемый размер округляется вверх до степени двух
* (или до 8, если меньше; или используется выделение памяти вне Arena, если размер больше 65536).
* При освобождении памяти, для каждого размера (всего 14 вариантов: 8, 16... 65536),
* поддерживается односвязный список свободных блоков.
* При аллокации, мы берём голову списка свободных блоков,
* либо, если список пуст - выделяем новый блок, используя Arena.
*/
class ArenaWithFreeLists : private Allocator<false>, private boost::noncopyable
{
private:
/// Если блок свободен, то в его начале хранится указатель на следующий свободный блок, либо nullptr, если свободных блоков больше нет.
/// Если блок используется, то в нём хранятся какие-то данные.
union Block
{
Block * next;
char data[0];
};
/// Максимальный размер куска памяти, который выделяется с помощью Arena. Иначе используем Allocator напрямую.
static constexpr size_t max_fixed_block_size = 65536;
/// Получить индекс в массиве freelist-ов для заданного размера.
static size_t findFreeListIndex(const size_t size)
{
return size <= 8 ? 2 : bitScanReverse(size - 1);
}
/// Для выделения блоков не слишком большого размера используется Arena.
Arena pool;
/// Списки свободных блоков. Каждый элемент указывает на голову соответствующего списка, либо равен nullptr.
/// Первые два элемента не используются, а предназначены для упрощения арифметики.
Block * free_lists[16] {};
public:
ArenaWithFreeLists(
const size_t initial_size = 4096, const size_t growth_factor = 2,
const size_t linear_growth_threshold = 128 * 1024 * 1024)
: pool{initial_size, growth_factor, linear_growth_threshold}
{
}
char * alloc(const size_t size)
{
if (size > max_fixed_block_size)
return static_cast<char *>(Allocator::alloc(size));
/// find list of required size
const auto list_idx = findFreeListIndex(size);
/// Если есть свободный блок.
if (auto & free_block_ptr = free_lists[list_idx])
{
/// Возьмём его. И поменяем голову списка на следующий элемент списка.
const auto res = free_block_ptr->data;
free_block_ptr = free_block_ptr->next;
return res;
}
/// no block of corresponding size, allocate a new one
return pool.alloc(1 << (list_idx + 1));
}
void free(char * ptr, const size_t size)
{
if (size > max_fixed_block_size)
return Allocator::free(ptr, size);
/// find list of required size
const auto list_idx = findFreeListIndex(size);
/// Вставим освобождённый блок в голову списка.
auto & free_block_ptr = free_lists[list_idx];
const auto old_head = free_block_ptr;
free_block_ptr = reinterpret_cast<Block *>(ptr);
free_block_ptr->next = old_head;
}
/// Размер выделенного пула в байтах
size_t size() const
{
return pool.size();
}
};
}