2014-03-17 02:01:03 +00:00
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
|
|
#include <malloc.h>
|
|
|
|
|
#include <string.h>
|
2014-04-29 00:24:13 +00:00
|
|
|
|
#include <sys/mman.h>
|
2014-03-17 02:01:03 +00:00
|
|
|
|
|
2014-05-03 22:57:43 +00:00
|
|
|
|
#include <DB/Common/MemoryTracker.h>
|
2014-03-17 02:01:03 +00:00
|
|
|
|
#include <DB/Core/Exception.h>
|
|
|
|
|
#include <DB/Core/ErrorCodes.h>
|
|
|
|
|
|
2015-01-24 01:55:29 +00:00
|
|
|
|
/** При использовании HashTableAllocatorWithStackMemory, размещённом на стеке,
|
|
|
|
|
* GCC 4.9 ошибочно делает предположение, что мы можем вызывать free от указателя на стек.
|
|
|
|
|
* На самом деле, комбинация условий внутри HashTableAllocatorWithStackMemory этого не допускает.
|
|
|
|
|
*/
|
|
|
|
|
#if !__clang__
|
|
|
|
|
#pragma GCC diagnostic push
|
|
|
|
|
#pragma GCC diagnostic ignored "-Wfree-nonheap-object"
|
|
|
|
|
#endif
|
|
|
|
|
|
2014-03-17 02:01:03 +00:00
|
|
|
|
|
|
|
|
|
/** Общая часть разных хэш-таблиц, отвечающая за выделение/освобождение памяти.
|
|
|
|
|
* Используется в качестве параметра шаблона (есть несколько реализаций с таким же интерфейсом).
|
|
|
|
|
*/
|
|
|
|
|
class HashTableAllocator
|
|
|
|
|
{
|
2014-04-29 00:24:13 +00:00
|
|
|
|
private:
|
|
|
|
|
/** Многие современные аллокаторы (например, tcmalloc) не умеют делать mremap для realloc,
|
|
|
|
|
* даже в случае достаточно больших кусков памяти.
|
|
|
|
|
* Хотя это позволяет увеличить производительность и уменьшить потребление памяти во время realloc-а.
|
|
|
|
|
* Чтобы это исправить, делаем mremap самостоятельно, если кусок памяти достаточно большой.
|
2014-04-29 04:13:26 +00:00
|
|
|
|
* Порог (64 МБ) выбран достаточно большим, так как изменение адресного пространства
|
2014-04-29 00:24:13 +00:00
|
|
|
|
* довольно сильно тормозит, особенно в случае наличия большого количества потоков.
|
|
|
|
|
* Рассчитываем, что набор операций mmap/что-то сделать/mremap может выполняться всего лишь около 1000 раз в секунду.
|
|
|
|
|
*
|
|
|
|
|
* PS. Также это требуется, потому что tcmalloc не может выделить кусок памяти больше 16 GB.
|
|
|
|
|
* NOTE Можно попробовать MAP_HUGETLB, но придётся самостоятельно управлять количеством доступных страниц.
|
|
|
|
|
*/
|
2014-04-29 04:13:26 +00:00
|
|
|
|
static constexpr size_t MMAP_THRESHOLD = 64 * (1 << 20);
|
2014-04-29 00:24:13 +00:00
|
|
|
|
|
2014-03-17 02:01:03 +00:00
|
|
|
|
public:
|
|
|
|
|
/// Выделить кусок памяти и заполнить его нулями.
|
|
|
|
|
void * alloc(size_t size)
|
|
|
|
|
{
|
2014-05-03 22:57:43 +00:00
|
|
|
|
if (current_memory_tracker)
|
|
|
|
|
current_memory_tracker->alloc(size);
|
|
|
|
|
|
2014-04-29 00:24:13 +00:00
|
|
|
|
void * buf;
|
|
|
|
|
|
|
|
|
|
if (size >= MMAP_THRESHOLD)
|
|
|
|
|
{
|
|
|
|
|
buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
|
|
|
|
if (MAP_FAILED == buf)
|
|
|
|
|
DB::throwFromErrno("HashTableAllocator: Cannot mmap.", DB::ErrorCodes::CANNOT_ALLOCATE_MEMORY);
|
|
|
|
|
|
|
|
|
|
/// Заполнение нулями не нужно - mmap сам это делает.
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
buf = ::calloc(size, 1);
|
|
|
|
|
if (nullptr == buf)
|
|
|
|
|
DB::throwFromErrno("HashTableAllocator: Cannot calloc.", DB::ErrorCodes::CANNOT_ALLOCATE_MEMORY);
|
|
|
|
|
}
|
2014-03-17 02:01:03 +00:00
|
|
|
|
|
|
|
|
|
return buf;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Освободить память.
|
|
|
|
|
void free(void * buf, size_t size)
|
|
|
|
|
{
|
2014-04-29 00:24:13 +00:00
|
|
|
|
if (size >= MMAP_THRESHOLD)
|
|
|
|
|
{
|
|
|
|
|
if (0 != munmap(buf, size))
|
|
|
|
|
DB::throwFromErrno("HashTableAllocator: Cannot munmap.", DB::ErrorCodes::CANNOT_MUNMAP);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
::free(buf);
|
|
|
|
|
}
|
2014-05-03 22:57:43 +00:00
|
|
|
|
|
|
|
|
|
if (current_memory_tracker)
|
|
|
|
|
current_memory_tracker->free(size);
|
2014-03-17 02:01:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Увеличить размер куска памяти.
|
|
|
|
|
* Содержимое старого куска памяти переезжает в начало нового.
|
|
|
|
|
* Оставшаяся часть заполняется нулями.
|
|
|
|
|
* Положение куска памяти может измениться.
|
|
|
|
|
*/
|
|
|
|
|
void * realloc(void * buf, size_t old_size, size_t new_size)
|
|
|
|
|
{
|
2014-04-29 00:24:13 +00:00
|
|
|
|
if (old_size < MMAP_THRESHOLD && new_size < MMAP_THRESHOLD)
|
|
|
|
|
{
|
2014-05-21 23:38:40 +00:00
|
|
|
|
if (current_memory_tracker)
|
|
|
|
|
current_memory_tracker->realloc(old_size, new_size);
|
|
|
|
|
|
2014-04-29 00:24:13 +00:00
|
|
|
|
buf = ::realloc(buf, new_size);
|
|
|
|
|
if (nullptr == buf)
|
|
|
|
|
DB::throwFromErrno("HashTableAllocator: Cannot realloc.", DB::ErrorCodes::CANNOT_ALLOCATE_MEMORY);
|
|
|
|
|
|
|
|
|
|
memset(reinterpret_cast<char *>(buf) + old_size, 0, new_size - old_size);
|
|
|
|
|
}
|
|
|
|
|
else if (old_size >= MMAP_THRESHOLD && new_size >= MMAP_THRESHOLD)
|
|
|
|
|
{
|
2014-05-21 23:38:40 +00:00
|
|
|
|
if (current_memory_tracker)
|
|
|
|
|
current_memory_tracker->realloc(old_size, new_size);
|
|
|
|
|
|
2014-04-29 00:24:13 +00:00
|
|
|
|
buf = mremap(buf, old_size, new_size, MREMAP_MAYMOVE);
|
|
|
|
|
if (MAP_FAILED == buf)
|
|
|
|
|
DB::throwFromErrno("HashTableAllocator: Cannot mremap.", DB::ErrorCodes::CANNOT_MREMAP);
|
|
|
|
|
|
|
|
|
|
/// Заполнение нулями не нужно.
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2014-04-29 03:33:10 +00:00
|
|
|
|
void * new_buf = alloc(new_size);
|
|
|
|
|
memcpy(new_buf, buf, old_size);
|
2014-04-29 00:24:13 +00:00
|
|
|
|
free(buf, old_size);
|
2014-04-29 03:33:10 +00:00
|
|
|
|
buf = new_buf;
|
2014-04-29 00:24:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
2014-03-17 02:01:03 +00:00
|
|
|
|
return buf;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Аллокатор с оптимизацией для маленьких кусков памяти.
|
|
|
|
|
*/
|
|
|
|
|
template <size_t N = 64>
|
|
|
|
|
class HashTableAllocatorWithStackMemory : private HashTableAllocator
|
|
|
|
|
{
|
|
|
|
|
private:
|
2014-07-10 16:00:36 +00:00
|
|
|
|
char stack_memory[N];
|
2014-03-17 02:01:03 +00:00
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
void * alloc(size_t size)
|
|
|
|
|
{
|
|
|
|
|
if (size <= N)
|
2014-07-10 15:55:07 +00:00
|
|
|
|
{
|
|
|
|
|
memset(stack_memory, 0, N);
|
2014-03-17 02:01:03 +00:00
|
|
|
|
return stack_memory;
|
2014-07-10 15:55:07 +00:00
|
|
|
|
}
|
2014-03-17 02:01:03 +00:00
|
|
|
|
|
|
|
|
|
return HashTableAllocator::alloc(size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void free(void * buf, size_t size)
|
|
|
|
|
{
|
|
|
|
|
if (size > N)
|
|
|
|
|
HashTableAllocator::free(buf, size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void * realloc(void * buf, size_t old_size, size_t new_size)
|
|
|
|
|
{
|
|
|
|
|
if (new_size <= N)
|
|
|
|
|
return buf;
|
|
|
|
|
|
|
|
|
|
if (old_size > N)
|
|
|
|
|
return HashTableAllocator::realloc(buf, old_size, new_size);
|
|
|
|
|
|
|
|
|
|
buf = ::malloc(new_size);
|
|
|
|
|
if (nullptr == buf)
|
|
|
|
|
DB::throwFromErrno("HashTableAllocator: Cannot malloc.", DB::ErrorCodes::CANNOT_ALLOCATE_MEMORY);
|
|
|
|
|
|
|
|
|
|
memcpy(buf, stack_memory, old_size);
|
|
|
|
|
memset(reinterpret_cast<char *>(buf) + old_size, 0, new_size - old_size);
|
|
|
|
|
|
|
|
|
|
return buf;
|
|
|
|
|
}
|
|
|
|
|
};
|
2015-01-24 01:55:29 +00:00
|
|
|
|
|
|
|
|
|
#if !__clang__
|
|
|
|
|
#pragma GCC diagnostic pop
|
|
|
|
|
#endif
|