You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

194 lines
5.7 KiB
C

/**
* 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