mariadb/storage/innobase/include/ut0pool.h
Marko Mäkelä 3794673111 MDEV-28836: Memory alignment cleanup
Table_cache_instance: Define the structure aligned at
the CPU cache line, and remove a pad[] data member.
Krunal Bauskar reported this to improve performance on ARMv8.

aligned_malloc(): Wrapper for the Microsoft _aligned_malloc()
and the ISO/IEC 9899:2011 <stdlib.h> aligned_alloc().
Note: The parameters are in the Microsoft order (size, alignment),
opposite of aligned_alloc(alignment, size).
Note: The standard defines that size must be an integer multiple
of alignment. It is enforced by AddressSanitizer but not by GNU libc
on Linux.

aligned_free(): Wrapper for the Microsoft _aligned_free() and
the standard free().

HAVE_ALIGNED_ALLOC: A new test. Unfortunately, support for
aligned_alloc() may still be missing on some platforms.
We will fall back to posix_memalign() for those cases.

HAVE_MEMALIGN: Remove, along with any use of the nonstandard memalign().

PFS_ALIGNEMENT (sic): Removed; we will use CPU_LEVEL1_DCACHE_LINESIZE.

PFS_ALIGNED: Defined using the C++11 keyword alignas.

buf_pool_t::page_hash_table::create(),
lock_sys_t::hash_table::create():
lock_sys_t::hash_table::resize(): Pad the allocation size to an
integer multiple of the alignment.

Reviewed by: Vladislav Vaintroub
2022-06-21 16:59:49 +03:00

368 lines
7.4 KiB
C++

/*****************************************************************************
Copyright (c) 2013, 2014, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2018, 2022, MariaDB Corporation.
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
*****************************************************************************/
/******************************************************************//**
@file include/ut0pool.h
Object pool.
Created 2012-Feb-26 Sunny Bains
***********************************************************************/
#ifndef ut0pool_h
#define ut0pool_h
#include <vector>
#include <queue>
#include <functional>
#include <my_global.h>
/** Allocate the memory for the object in blocks. We keep the objects sorted
on pointer so that they are closer together in case they have to be iterated
over in a list. */
template <typename Type, typename Factory, typename LockStrategy>
struct Pool {
typedef Type value_type;
struct Element {
Pool* m_pool;
value_type m_type;
};
/** Constructor
@param size size of the memory block */
Pool(size_t size)
:
m_end(),
m_start(),
m_size(size),
m_last()
{
ut_ad(ut_is_2pow(size));
ut_a(size >= sizeof(Element));
static_assert(!(sizeof(Element) % CPU_LEVEL1_DCACHE_LINESIZE),
"alignment");
m_lock_strategy.create();
ut_a(m_start == 0);
m_start = static_cast<Element*>(
aligned_malloc(m_size, CPU_LEVEL1_DCACHE_LINESIZE));
memset_aligned<CPU_LEVEL1_DCACHE_LINESIZE>(
m_start, 0, m_size);
m_last = m_start;
m_end = &m_start[m_size / sizeof *m_start];
/* Note: Initialise only a small subset, even though we have
allocated all the memory. This is required only because PFS
(MTR) results change if we instantiate too many mutexes up
front. */
init(ut_min(size_t(16), size_t(m_end - m_start)));
ut_ad(m_pqueue.size() <= size_t(m_last - m_start));
}
/** Destructor */
~Pool()
{
m_lock_strategy.destroy();
for (Element* elem = m_start; elem != m_last; ++elem) {
ut_ad(elem->m_pool == this);
Factory::destroy(&elem->m_type);
}
IF_WIN(_aligned_free,free)(m_start);
m_end = m_last = m_start = 0;
m_size = 0;
}
/** Get an object from the pool.
@retrun a free instance or NULL if exhausted. */
Type* get()
{
Element* elem;
m_lock_strategy.enter();
if (!m_pqueue.empty()) {
elem = m_pqueue.top();
m_pqueue.pop();
} else if (m_last < m_end) {
/* Initialise the remaining elements. */
init(size_t(m_end - m_last));
ut_ad(!m_pqueue.empty());
elem = m_pqueue.top();
m_pqueue.pop();
} else {
elem = NULL;
}
m_lock_strategy.exit();
return elem ? &elem->m_type : NULL;
}
/** Add the object to the pool.
@param ptr object to free */
static void mem_free(value_type* ptr)
{
Element* elem;
byte* p = reinterpret_cast<byte*>(ptr + 1);
elem = reinterpret_cast<Element*>(p - sizeof(*elem));
elem->m_pool->m_lock_strategy.enter();
elem->m_pool->putl(elem);
elem->m_pool->m_lock_strategy.exit();
}
protected:
// Disable copying
Pool(const Pool&);
Pool& operator=(const Pool&);
private:
/* We only need to compare on pointer address. */
typedef std::priority_queue<
Element*,
std::vector<Element*, ut_allocator<Element*> >,
std::greater<Element*> > pqueue_t;
/** Release the object to the free pool
@param elem element to free */
void putl(Element* elem)
{
ut_ad(elem >= m_start && elem < m_last);
m_pqueue.push(elem);
}
/** Initialise the elements.
@param n_elems Number of elements to initialise */
void init(size_t n_elems)
{
ut_ad(size_t(m_end - m_last) >= n_elems);
for (size_t i = 0; i < n_elems; ++i, ++m_last) {
m_last->m_pool = this;
Factory::init(&m_last->m_type);
m_pqueue.push(m_last);
}
ut_ad(m_last <= m_end);
}
private:
/** Pointer to the last element */
Element* m_end;
/** Pointer to the first element */
Element* m_start;
/** Size of the block in bytes */
size_t m_size;
/** Upper limit of used space */
Element* m_last;
/** Priority queue ordered on the pointer addresse. */
pqueue_t m_pqueue;
/** Lock strategy to use */
LockStrategy m_lock_strategy;
};
template <typename Pool, typename LockStrategy>
struct PoolManager {
typedef Pool PoolType;
typedef typename PoolType::value_type value_type;
PoolManager(size_t size)
:
m_size(size)
{
create();
}
~PoolManager()
{
destroy();
ut_a(m_pools.empty());
}
/** Get an element from one of the pools.
@return instance or NULL if pool is empty. */
value_type* get()
{
size_t index = 0;
size_t delay = 1;
value_type* ptr = NULL;
do {
m_lock_strategy.enter();
ut_ad(!m_pools.empty());
size_t n_pools = m_pools.size();
PoolType* pool = m_pools[index % n_pools];
m_lock_strategy.exit();
ptr = pool->get();
if (ptr == 0 && (index / n_pools) > 2) {
if (!add_pool(n_pools)) {
ib::error() << "Failed to allocate"
" memory for a pool of size "
<< m_size << " bytes. Will"
" wait for " << delay
<< " seconds for a thread to"
" free a resource";
/* There is nothing much we can do
except crash and burn, however lets
be a little optimistic and wait for
a resource to be freed. */
std::this_thread::sleep_for(
std::chrono::seconds(delay));
if (delay < 32) {
delay <<= 1;
}
} else {
delay = 1;
}
}
++index;
} while (ptr == NULL);
return(ptr);
}
static void mem_free(value_type* ptr)
{
PoolType::mem_free(ptr);
}
private:
/** Add a new pool
@param n_pools Number of pools that existed when the add pool was
called.
@return true on success */
bool add_pool(size_t n_pools)
{
bool added = false;
m_lock_strategy.enter();
if (n_pools < m_pools.size()) {
/* Some other thread already added a pool. */
added = true;
} else {
PoolType* pool;
ut_ad(n_pools == m_pools.size());
pool = UT_NEW_NOKEY(PoolType(m_size));
if (pool != NULL) {
ut_ad(n_pools <= m_pools.size());
m_pools.push_back(pool);
ib::info() << "Number of pools: "
<< m_pools.size();
added = true;
}
}
ut_ad(n_pools < m_pools.size() || !added);
m_lock_strategy.exit();
return(added);
}
/** Create the pool manager. */
void create()
{
ut_a(m_size > sizeof(value_type));
m_lock_strategy.create();
add_pool(0);
}
/** Release the resources. */
void destroy()
{
typename Pools::iterator it;
typename Pools::iterator end = m_pools.end();
for (it = m_pools.begin(); it != end; ++it) {
PoolType* pool = *it;
UT_DELETE(pool);
}
m_pools.clear();
m_lock_strategy.destroy();
}
private:
// Disable copying
PoolManager(const PoolManager&);
PoolManager& operator=(const PoolManager&);
typedef std::vector<PoolType*, ut_allocator<PoolType*> > Pools;
/** Size of each block */
size_t m_size;
/** Pools managed this manager */
Pools m_pools;
/** Lock strategy to use */
LockStrategy m_lock_strategy;
};
#endif /* ut0pool_h */