|
|
|
/**
|
|
|
|
* Copyright 2013-2022 Software Radio Systems Limited
|
|
|
|
*
|
|
|
|
* This file is part of srsRAN.
|
|
|
|
*
|
|
|
|
* srsRAN is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Affero General Public License as
|
|
|
|
* published by the Free Software Foundation, either version 3 of
|
|
|
|
* the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* srsRAN 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 Affero General Public License for more details.
|
|
|
|
*
|
|
|
|
* A copy of the GNU Affero General Public License can be found in
|
|
|
|
* the LICENSE file in the top-level directory of this distribution
|
|
|
|
* and at http://www.gnu.org/licenses/.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifndef SRSRAN_BATCH_MEM_POOL_H
|
|
|
|
#define SRSRAN_BATCH_MEM_POOL_H
|
|
|
|
|
|
|
|
#include "memblock_cache.h"
|
|
|
|
#include "pool_utils.h"
|
|
|
|
#include "srsran/common/thread_pool.h"
|
|
|
|
#include "srsran/support/srsran_assert.h"
|
|
|
|
#include <memory>
|
|
|
|
#include <mutex>
|
|
|
|
|
|
|
|
namespace srsran {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Non-thread-safe, node-based memory pool that allocates nodes in batches of "objs_per_batch" > 1, and caches
|
|
|
|
* allocated blocks on deallocation
|
|
|
|
*/
|
|
|
|
class growing_batch_mem_pool
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
explicit growing_batch_mem_pool(size_t objs_per_batch_,
|
|
|
|
size_t node_size_,
|
|
|
|
size_t node_alignment_,
|
|
|
|
int init_size = -1) :
|
|
|
|
objs_per_batch(objs_per_batch_),
|
|
|
|
memblock_size(std::max(node_size_, free_memblock_list::min_memblock_size())),
|
|
|
|
allocated(objs_per_batch * memblock_size, std::max(node_alignment_, free_memblock_list::min_memblock_align()))
|
|
|
|
{
|
|
|
|
size_t N = init_size < 0 ? objs_per_batch_ : init_size;
|
|
|
|
while (N > cache_size()) {
|
|
|
|
allocate_batch();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
~growing_batch_mem_pool()
|
|
|
|
{
|
|
|
|
srsran_assert(cache_size() == size(), "Not all nodes have been deallocated yet (%zd < %zd)", cache_size(), size());
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t get_node_max_size() const { return memblock_size; }
|
|
|
|
|
|
|
|
void clear()
|
|
|
|
{
|
|
|
|
free_list.clear();
|
|
|
|
allocated.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t cache_size() const { return free_list.size(); }
|
|
|
|
size_t size() const { return allocated.size() * objs_per_batch; }
|
|
|
|
|
|
|
|
void allocate_batch()
|
|
|
|
{
|
|
|
|
uint8_t* batch_payload = static_cast<uint8_t*>(allocated.allocate_block());
|
|
|
|
for (size_t i = 0; i < objs_per_batch; ++i) {
|
|
|
|
void* cache_node = batch_payload + i * memblock_size;
|
|
|
|
free_list.push(cache_node);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void* allocate_node()
|
|
|
|
{
|
|
|
|
if (free_list.empty()) {
|
|
|
|
allocate_batch();
|
|
|
|
}
|
|
|
|
return free_list.pop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void deallocate_node(void* ptr) { free_list.push(ptr); }
|
|
|
|
|
|
|
|
private:
|
|
|
|
const size_t objs_per_batch;
|
|
|
|
const size_t memblock_size;
|
|
|
|
|
|
|
|
memblock_stack allocated;
|
|
|
|
free_memblock_list free_list;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Thread-safe object pool specialized in allocating batches of objects in a preemptive way in a background thread
|
|
|
|
* to minimize latency.
|
|
|
|
* Note: The dispatched allocation jobs may outlive the pool. To handle this, the pool state is passed to jobs via a
|
|
|
|
* shared ptr.
|
|
|
|
*/
|
|
|
|
class background_mem_pool
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
const size_t batch_threshold;
|
|
|
|
|
|
|
|
explicit background_mem_pool(size_t nodes_per_batch_, size_t node_size_, size_t thres_, int initial_size = -1) :
|
|
|
|
batch_threshold(thres_),
|
|
|
|
state(std::make_shared<detached_pool_state>(this)),
|
|
|
|
grow_pool(nodes_per_batch_, node_size_, detail::max_alignment, initial_size)
|
|
|
|
{
|
|
|
|
srsran_assert(batch_threshold > 1, "Invalid arguments for background memory pool");
|
|
|
|
}
|
|
|
|
~background_mem_pool()
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(state->mutex);
|
|
|
|
state->pool = nullptr;
|
|
|
|
grow_pool.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// alloc new object space. If no memory is pre-reserved in the pool, malloc is called to allocate new batch.
|
|
|
|
void* allocate_node(size_t sz)
|
|
|
|
{
|
|
|
|
srsran_assert(sz <= grow_pool.get_node_max_size(),
|
|
|
|
"Mismatch of allocated node size=%zd and object size=%zd",
|
|
|
|
sz,
|
|
|
|
grow_pool.get_node_max_size());
|
|
|
|
std::lock_guard<std::mutex> lock(state->mutex);
|
|
|
|
void* node = grow_pool.allocate_node();
|
|
|
|
|
|
|
|
if (grow_pool.size() < batch_threshold) {
|
|
|
|
allocate_batch_in_background_nolock();
|
|
|
|
}
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
|
|
|
void deallocate_node(void* p)
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(state->mutex);
|
|
|
|
grow_pool.deallocate_node(p);
|
|
|
|
}
|
|
|
|
|
|
|
|
void allocate_batch()
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(state->mutex);
|
|
|
|
grow_pool.allocate_batch();
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t get_node_max_size() const { return grow_pool.get_node_max_size(); }
|
|
|
|
size_t cache_size() const
|
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock(state->mutex);
|
|
|
|
return grow_pool.cache_size();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
void allocate_batch_in_background_nolock()
|
|
|
|
{
|
|
|
|
if (state->dispatched) {
|
|
|
|
// new batch allocation already ongoing
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
state->dispatched = true;
|
|
|
|
std::shared_ptr<detached_pool_state> state_sptr = state;
|
|
|
|
get_background_workers().push_task([state_sptr]() {
|
|
|
|
std::lock_guard<std::mutex> lock(state_sptr->mutex);
|
|
|
|
// check if pool has not been destroyed
|
|
|
|
if (state_sptr->pool != nullptr) {
|
|
|
|
auto* pool = state_sptr->pool;
|
|
|
|
do {
|
|
|
|
pool->grow_pool.allocate_batch();
|
|
|
|
} while (pool->grow_pool.cache_size() < pool->batch_threshold);
|
|
|
|
}
|
|
|
|
state_sptr->dispatched = false;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// State is stored in a shared_ptr that may outlive the pool.
|
|
|
|
struct detached_pool_state {
|
|
|
|
std::mutex mutex;
|
|
|
|
background_mem_pool* pool;
|
|
|
|
bool dispatched = false;
|
|
|
|
explicit detached_pool_state(background_mem_pool* pool_) : pool(pool_) {}
|
|
|
|
};
|
|
|
|
std::shared_ptr<detached_pool_state> state;
|
|
|
|
|
|
|
|
growing_batch_mem_pool grow_pool;
|
|
|
|
};
|
|
|
|
|
|
|
|
} // namespace srsran
|
|
|
|
|
|
|
|
#endif // SRSRAN_BATCH_MEM_POOL_H
|