mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-28 10:31:57 +00:00
99 lines
3.9 KiB
C++
99 lines
3.9 KiB
C++
#pragma once
|
||
|
||
#include <DB/Common/Arena.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 : _bit_scan_reverse(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();
|
||
}
|
||
};
|
||
|
||
|
||
}
|