mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-29 11:02:08 +00:00
dbms: unification; using huge pages (experimental) [#METR-2944].
This commit is contained in:
parent
38fa9c8982
commit
f0a5ec4736
124
dbms/include/DB/Common/Allocator.h
Normal file
124
dbms/include/DB/Common/Allocator.h
Normal file
@ -0,0 +1,124 @@
|
||||
#pragma once
|
||||
|
||||
#include <malloc.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <DB/Common/MemoryTracker.h>
|
||||
#include <DB/Core/Exception.h>
|
||||
#include <DB/Core/ErrorCodes.h>
|
||||
|
||||
|
||||
/** Отвечает за выделение/освобождение памяти. Используется, например, в PODArray, Arena.
|
||||
* Интерфейс отличается от std::allocator
|
||||
* - наличием метода realloc, который для больших кусков памяти использует mremap;
|
||||
* - передачей размера в метод free;
|
||||
* - наличием аргумента alignment;
|
||||
*/
|
||||
class Allocator
|
||||
{
|
||||
private:
|
||||
/** См. комментарий в HashTableAllocator.h
|
||||
*/
|
||||
static constexpr size_t MMAP_THRESHOLD = 64 * (1 << 20);
|
||||
static constexpr size_t HUGE_PAGE_SIZE = 2 * (1 << 20);
|
||||
static constexpr size_t MMAP_MIN_ALIGNMENT = 4096;
|
||||
static constexpr size_t MALLOC_MIN_ALIGNMENT = 8;
|
||||
|
||||
public:
|
||||
/// Выделить кусок памяти.
|
||||
void * alloc(size_t size, size_t alignment = 0)
|
||||
{
|
||||
if (current_memory_tracker)
|
||||
current_memory_tracker->alloc(size);
|
||||
|
||||
void * buf;
|
||||
|
||||
if (size >= MMAP_THRESHOLD)
|
||||
{
|
||||
if (alignment > MMAP_MIN_ALIGNMENT)
|
||||
throw DB::Exception("Too large alignment: more than page size.", DB::ErrorCodes::BAD_ARGUMENTS);
|
||||
|
||||
buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
if (MAP_FAILED == buf)
|
||||
DB::throwFromErrno("Allocator: Cannot mmap.", DB::ErrorCodes::CANNOT_ALLOCATE_MEMORY);
|
||||
|
||||
/// См. комментарий в HashTableAllocator.h
|
||||
if (size >= HUGE_PAGE_SIZE && 0 != madvise(buf, size, MADV_HUGEPAGE))
|
||||
DB::throwFromErrno("HashTableAllocator: Cannot madvise with MADV_HUGEPAGE.", DB::ErrorCodes::CANNOT_ALLOCATE_MEMORY);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (alignment <= MALLOC_MIN_ALIGNMENT)
|
||||
{
|
||||
buf = ::malloc(size);
|
||||
|
||||
if (nullptr == buf)
|
||||
DB::throwFromErrno("Allocator: Cannot malloc.", DB::ErrorCodes::CANNOT_ALLOCATE_MEMORY);
|
||||
}
|
||||
else
|
||||
{
|
||||
buf = nullptr;
|
||||
int res = posix_memalign(&buf, alignment, size);
|
||||
|
||||
if (0 != res)
|
||||
DB::throwFromErrno("Cannot allocate memory (posix_memalign)", DB::ErrorCodes::CANNOT_ALLOCATE_MEMORY, res);
|
||||
}
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
/// Освободить память.
|
||||
void free(void * buf, size_t size)
|
||||
{
|
||||
if (size >= MMAP_THRESHOLD)
|
||||
{
|
||||
if (0 != munmap(buf, size))
|
||||
DB::throwFromErrno("Allocator: Cannot munmap.", DB::ErrorCodes::CANNOT_MUNMAP);
|
||||
}
|
||||
else
|
||||
{
|
||||
::free(buf);
|
||||
}
|
||||
|
||||
if (current_memory_tracker)
|
||||
current_memory_tracker->free(size);
|
||||
}
|
||||
|
||||
/** Увеличить размер куска памяти.
|
||||
* Содержимое старого куска памяти переезжает в начало нового.
|
||||
* Положение куска памяти может измениться.
|
||||
*/
|
||||
void * realloc(void * buf, size_t old_size, size_t new_size, size_t alignment = 0)
|
||||
{
|
||||
if (old_size < MMAP_THRESHOLD && new_size < MMAP_THRESHOLD && alignment <= MALLOC_MIN_ALIGNMENT)
|
||||
{
|
||||
if (current_memory_tracker)
|
||||
current_memory_tracker->realloc(old_size, new_size);
|
||||
|
||||
buf = ::realloc(buf, new_size);
|
||||
|
||||
if (nullptr == buf)
|
||||
DB::throwFromErrno("Allocator: Cannot realloc.", DB::ErrorCodes::CANNOT_ALLOCATE_MEMORY);
|
||||
}
|
||||
else if (old_size >= MMAP_THRESHOLD && new_size >= MMAP_THRESHOLD)
|
||||
{
|
||||
if (current_memory_tracker)
|
||||
current_memory_tracker->realloc(old_size, new_size);
|
||||
|
||||
buf = mremap(buf, old_size, new_size, MREMAP_MAYMOVE);
|
||||
if (MAP_FAILED == buf)
|
||||
DB::throwFromErrno("Allocator: Cannot mremap.", DB::ErrorCodes::CANNOT_MREMAP);
|
||||
}
|
||||
else
|
||||
{
|
||||
void * new_buf = alloc(new_size, alignment);
|
||||
memcpy(new_buf, buf, old_size);
|
||||
free(buf, old_size);
|
||||
buf = new_buf;
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
};
|
@ -6,7 +6,7 @@
|
||||
#include <Poco/SharedPtr.h>
|
||||
#include <Yandex/likely.h>
|
||||
#include <DB/Common/ProfileEvents.h>
|
||||
#include <DB/Common/MemoryTracker.h>
|
||||
#include <DB/Common/Allocator.h>
|
||||
|
||||
|
||||
namespace DB
|
||||
@ -25,7 +25,7 @@ class Arena
|
||||
{
|
||||
private:
|
||||
/// Непрерывный кусок памяти и указатель на свободное место в нём. Односвязный список.
|
||||
struct Chunk : private std::allocator<char> /// empty base optimization
|
||||
struct Chunk : private Allocator /// empty base optimization
|
||||
{
|
||||
char * begin;
|
||||
char * pos;
|
||||
@ -38,10 +38,7 @@ private:
|
||||
ProfileEvents::increment(ProfileEvents::ArenaAllocChunks);
|
||||
ProfileEvents::increment(ProfileEvents::ArenaAllocBytes, size_);
|
||||
|
||||
if (current_memory_tracker)
|
||||
current_memory_tracker->alloc(size_);
|
||||
|
||||
begin = allocate(size_);
|
||||
begin = reinterpret_cast<char *>(Allocator::alloc(size_));
|
||||
pos = begin;
|
||||
end = begin + size_;
|
||||
prev = prev_;
|
||||
@ -49,10 +46,7 @@ private:
|
||||
|
||||
~Chunk()
|
||||
{
|
||||
deallocate(begin, size());
|
||||
|
||||
if (current_memory_tracker)
|
||||
current_memory_tracker->free(size());
|
||||
Allocator::free(begin, size());
|
||||
|
||||
if (prev)
|
||||
delete prev;
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
|
||||
/** Общая часть разных хэш-таблиц, отвечающая за выделение/освобождение памяти.
|
||||
* Отличается от Allocator тем, что зануляет память.
|
||||
* Используется в качестве параметра шаблона (есть несколько реализаций с таким же интерфейсом).
|
||||
*/
|
||||
class HashTableAllocator
|
||||
@ -33,9 +34,9 @@ private:
|
||||
* Рассчитываем, что набор операций mmap/что-то сделать/mremap может выполняться всего лишь около 1000 раз в секунду.
|
||||
*
|
||||
* PS. Также это требуется, потому что tcmalloc не может выделить кусок памяти больше 16 GB.
|
||||
* NOTE Можно попробовать MAP_HUGETLB, но придётся самостоятельно управлять количеством доступных страниц.
|
||||
*/
|
||||
static constexpr size_t MMAP_THRESHOLD = 64 * (1 << 20);
|
||||
static constexpr size_t HUGE_PAGE_SIZE = 2 * (1 << 20);
|
||||
|
||||
public:
|
||||
/// Выделить кусок памяти и заполнить его нулями.
|
||||
@ -52,6 +53,14 @@ public:
|
||||
if (MAP_FAILED == buf)
|
||||
DB::throwFromErrno("HashTableAllocator: Cannot mmap.", DB::ErrorCodes::CANNOT_ALLOCATE_MEMORY);
|
||||
|
||||
/** Использование huge pages позволяет увеличить производительность более чем в три раза
|
||||
* в запросе SELECT number % 1000000 AS k, count() FROM system.numbers GROUP BY k,
|
||||
* (хэш-таблица на 1 000 000 элементов)
|
||||
* и примерно на 15% в случае хэш-таблицы на 100 000 000 элементов.
|
||||
*/
|
||||
if (size >= HUGE_PAGE_SIZE && 0 != madvise(buf, size, MADV_HUGEPAGE))
|
||||
DB::throwFromErrno("HashTableAllocator: Cannot madvise with MADV_HUGEPAGE.", DB::ErrorCodes::CANNOT_ALLOCATE_MEMORY);
|
||||
|
||||
/// Заполнение нулями не нужно - mmap сам это делает.
|
||||
}
|
||||
else
|
||||
@ -108,6 +117,10 @@ public:
|
||||
if (MAP_FAILED == buf)
|
||||
DB::throwFromErrno("HashTableAllocator: Cannot mremap.", DB::ErrorCodes::CANNOT_MREMAP);
|
||||
|
||||
/** Здесь не получается сделать madvise с MADV_HUGEPAGE.
|
||||
* Похоже, что при mremap, huge pages сами расширяются на новую область.
|
||||
*/
|
||||
|
||||
/// Заполнение нулями не нужно.
|
||||
}
|
||||
else
|
||||
|
@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <string.h>
|
||||
#include <malloc.h>
|
||||
#include <cstddef>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
@ -12,7 +11,7 @@
|
||||
#include <Yandex/likely.h>
|
||||
#include <Yandex/strong_typedef.h>
|
||||
|
||||
#include <DB/Common/MemoryTracker.h>
|
||||
#include <DB/Common/Allocator.h>
|
||||
#include <DB/Core/Exception.h>
|
||||
#include <DB/Core/ErrorCodes.h>
|
||||
|
||||
@ -32,28 +31,18 @@ namespace DB
|
||||
* Конструктор по-умолчанию создаёт пустой объект, который не выделяет память.
|
||||
* Затем выделяется память минимум под POD_ARRAY_INITIAL_SIZE элементов.
|
||||
*
|
||||
* При первом выделении памяти использует std::allocator.
|
||||
* В реализации из libstdc++ он кэширует куски памяти несколько больше, чем обычный malloc.
|
||||
*
|
||||
* При изменении размера, использует realloc, который может (но не обязан) использовать mremap для больших кусков памяти.
|
||||
* По факту, mremap используется при использовании аллокатора из glibc, но не используется, например, в tcmalloc.
|
||||
*
|
||||
* Если вставлять элементы push_back-ом, не делая reserve, то PODArray примерно в 2.5 раза быстрее std::vector.
|
||||
*/
|
||||
#define POD_ARRAY_INITIAL_SIZE 4096UL
|
||||
|
||||
template <typename T>
|
||||
class PODArray : private boost::noncopyable, private std::allocator<char> /// empty base optimization
|
||||
class PODArray : private boost::noncopyable, private Allocator /// empty base optimization
|
||||
{
|
||||
private:
|
||||
typedef std::allocator<char> Allocator;
|
||||
|
||||
char * c_start;
|
||||
char * c_end;
|
||||
char * c_end_of_storage;
|
||||
|
||||
bool use_libc_realloc = false;
|
||||
|
||||
T * t_start() { return reinterpret_cast<T *>(c_start); }
|
||||
T * t_end() { return reinterpret_cast<T *>(c_end); }
|
||||
T * t_end_of_storage() { return reinterpret_cast<T *>(c_end_of_storage); }
|
||||
@ -90,10 +79,7 @@ private:
|
||||
|
||||
size_t bytes_to_alloc = to_size(n);
|
||||
|
||||
if (current_memory_tracker)
|
||||
current_memory_tracker->alloc(bytes_to_alloc);
|
||||
|
||||
c_start = c_end = Allocator::allocate(bytes_to_alloc);
|
||||
c_start = c_end = reinterpret_cast<char *>(Allocator::alloc(bytes_to_alloc));
|
||||
c_end_of_storage = c_start + bytes_to_alloc;
|
||||
}
|
||||
|
||||
@ -102,13 +88,7 @@ private:
|
||||
if (c_start == nullptr)
|
||||
return;
|
||||
|
||||
if (use_libc_realloc)
|
||||
::free(c_start);
|
||||
else
|
||||
Allocator::deallocate(c_start, storage_size());
|
||||
|
||||
if (current_memory_tracker)
|
||||
current_memory_tracker->free(storage_size());
|
||||
Allocator::free(c_start, storage_size());
|
||||
}
|
||||
|
||||
void realloc(size_t n)
|
||||
@ -122,38 +102,10 @@ private:
|
||||
ptrdiff_t end_diff = c_end - c_start;
|
||||
size_t bytes_to_alloc = to_size(n);
|
||||
|
||||
char * old_c_start = c_start;
|
||||
char * old_c_end_of_storage = c_end_of_storage;
|
||||
|
||||
if (current_memory_tracker)
|
||||
current_memory_tracker->realloc(storage_size(), bytes_to_alloc);
|
||||
|
||||
if (use_libc_realloc)
|
||||
{
|
||||
auto new_c_start = reinterpret_cast<char *>(::realloc(c_start, bytes_to_alloc));
|
||||
|
||||
if (nullptr == new_c_start)
|
||||
throwFromErrno("PODArray: cannot realloc", ErrorCodes::CANNOT_ALLOCATE_MEMORY);
|
||||
|
||||
c_start = new_c_start;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto new_c_start = reinterpret_cast<char *>(malloc(bytes_to_alloc));
|
||||
|
||||
if (nullptr == new_c_start)
|
||||
throwFromErrno("PODArray: cannot realloc", ErrorCodes::CANNOT_ALLOCATE_MEMORY);
|
||||
|
||||
c_start = new_c_start;
|
||||
|
||||
memcpy(c_start, old_c_start, std::min(bytes_to_alloc, static_cast<size_t>(end_diff)));
|
||||
Allocator::deallocate(old_c_start, old_c_end_of_storage - old_c_start);
|
||||
}
|
||||
c_start = reinterpret_cast<char *>(Allocator::realloc(c_start, storage_size(), bytes_to_alloc));
|
||||
|
||||
c_end = c_start + end_diff;
|
||||
c_end_of_storage = c_start + bytes_to_alloc;
|
||||
|
||||
use_libc_realloc = true;
|
||||
}
|
||||
|
||||
public:
|
||||
@ -187,7 +139,6 @@ public:
|
||||
std::swap(c_start, other.c_start);
|
||||
std::swap(c_end, other.c_end);
|
||||
std::swap(c_end_of_storage, other.c_end_of_storage);
|
||||
std::swap(use_libc_realloc, other.use_libc_realloc);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
#include <DB/Common/ProfileEvents.h>
|
||||
#include <DB/Common/MemoryTracker.h>
|
||||
#include <DB/Common/Allocator.h>
|
||||
|
||||
#include <DB/Core/Exception.h>
|
||||
#include <DB/Core/ErrorCodes.h>
|
||||
@ -18,7 +18,7 @@ namespace DB
|
||||
* Отличается тем, что не делает лишний memset. (И почти ничего не делает.)
|
||||
* Также можно попросить выделять выровненный кусок памяти.
|
||||
*/
|
||||
struct Memory : boost::noncopyable
|
||||
struct Memory : boost::noncopyable, Allocator
|
||||
{
|
||||
size_t m_capacity = 0;
|
||||
size_t m_size = 0;
|
||||
@ -66,16 +66,22 @@ struct Memory : boost::noncopyable
|
||||
}
|
||||
else
|
||||
{
|
||||
dealloc();
|
||||
|
||||
new_size = align(new_size);
|
||||
m_data = reinterpret_cast<char *>(Allocator::realloc(m_data, m_capacity, new_size, alignment));
|
||||
m_capacity = new_size;
|
||||
m_size = m_capacity;
|
||||
|
||||
alloc();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
size_t align(size_t value) const
|
||||
{
|
||||
if (!alignment)
|
||||
return value;
|
||||
|
||||
return (value + alignment - 1) / alignment * alignment;
|
||||
}
|
||||
|
||||
void alloc()
|
||||
{
|
||||
if (!m_capacity)
|
||||
@ -87,33 +93,10 @@ private:
|
||||
ProfileEvents::increment(ProfileEvents::IOBufferAllocs);
|
||||
ProfileEvents::increment(ProfileEvents::IOBufferAllocBytes, m_capacity);
|
||||
|
||||
if (current_memory_tracker)
|
||||
current_memory_tracker->alloc(m_capacity);
|
||||
|
||||
char * new_m_data = nullptr;
|
||||
|
||||
if (!alignment)
|
||||
{
|
||||
new_m_data = reinterpret_cast<char *>(malloc(m_capacity));
|
||||
|
||||
if (!new_m_data)
|
||||
throw Exception("Cannot allocate memory (malloc)", ErrorCodes::CANNOT_ALLOCATE_MEMORY);
|
||||
|
||||
m_data = new_m_data;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
size_t aligned_capacity = (m_capacity + alignment - 1) / alignment * alignment;
|
||||
m_capacity = aligned_capacity;
|
||||
size_t new_capacity = align(m_capacity);
|
||||
m_data = reinterpret_cast<char *>(Allocator::alloc(new_capacity, alignment));
|
||||
m_capacity = new_capacity;
|
||||
m_size = m_capacity;
|
||||
|
||||
int res = posix_memalign(reinterpret_cast<void **>(&new_m_data), alignment, m_capacity);
|
||||
|
||||
if (0 != res)
|
||||
DB::throwFromErrno("Cannot allocate memory (posix_memalign)", ErrorCodes::CANNOT_ALLOCATE_MEMORY, res);
|
||||
|
||||
m_data = new_m_data;
|
||||
}
|
||||
|
||||
void dealloc()
|
||||
@ -121,11 +104,8 @@ private:
|
||||
if (!m_data)
|
||||
return;
|
||||
|
||||
free(reinterpret_cast<void *>(m_data));
|
||||
Allocator::free(reinterpret_cast<void *>(m_data), m_capacity);
|
||||
m_data = nullptr; /// Чтобы избежать double free, если последующий вызов alloc кинет исключение.
|
||||
|
||||
if (current_memory_tracker)
|
||||
current_memory_tracker->free(m_capacity);
|
||||
}
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user