Implement a pool in FMT to avoid allocating heap memory when passing a char* to the backend, usually when formatting a %s argument.

Previously since a char* can have any length, this was managed by FMT by converting it into a std::string.
Now we store it into a configurable size node that can store a fixed size string, otherwise it falls back to std::string.
This commit is contained in:
faluco 2021-04-08 13:59:46 +02:00 committed by faluco
parent b5e879db47
commit 0465f6badd
2 changed files with 106 additions and 4 deletions

View File

@ -1276,6 +1276,10 @@ template <typename T> const T& unwrap(const std::reference_wrapper<T>& v) {
}
class dynamic_arg_list {
public:
static constexpr std::size_t max_pool_string_size = 140;
private:
// Workaround for clang's -Wweak-vtables. Unlike for regular classes, for
// templates it doesn't complain about inability to deduce single translation
// unit for placing vtable. So storage_node_base is made a fake template.
@ -1284,6 +1288,10 @@ class dynamic_arg_list {
std::unique_ptr<node<>> next;
};
// Pool storage allocation functions.
static void *allocate_from_pool(std::size_t sz);
static void free_from_pool(void *ptr);
template <typename T> struct typed_node : node<> {
T value;
@ -1295,9 +1303,35 @@ class dynamic_arg_list {
: value(arg.data(), arg.size()) {}
};
struct pooled_node : node<> {
std::array<char, max_pool_string_size> value;
static void* operator new(std::size_t sz) {
return allocate_from_pool(sz);
}
static void operator delete(void* ptr) {
free_from_pool(ptr);
}
pooled_node(const char *str, std::size_t sz) {
FMT_ASSERT(sz < value.size(), "String is too big");
std::copy(str, str + sz, value.begin());
}
};
std::unique_ptr<node<>> head_;
public:
static constexpr std::size_t max_pool_node_size = sizeof(pooled_node);
const char *push_small_string(const char *str, std::size_t sz) {
auto new_node = std::unique_ptr<pooled_node>(new pooled_node(str, sz));
auto& value = new_node->value;
new_node->next = std::move(head_);
head_ = std::move(new_node);
return value.data();
}
template <typename T, typename Arg> const T& push(const Arg& arg) {
auto new_node = std::unique_ptr<typed_node<T>>(new typed_node<T>(arg));
auto& value = new_node->value;
@ -1541,11 +1575,24 @@ class dynamic_format_arg_store
std::string result = fmt::vformat("{} and {} and {}", store);
\endrst
*/
template <typename T> void push_back(const T& arg) {
if (detail::const_check(need_copy<T>::value))
emplace_arg(dynamic_args_.push<stored_type<T>>(arg));
else
template <typename T,
typename std::enable_if<detail::is_string<typename std::decay<T>::type>::value, int>::type = 0>
void push_back(const T& arg) {
fmt::string_view view(arg);
if (view.size() + 1 < dynamic_args_.max_pool_string_size) {
emplace_arg(dynamic_args_.push_small_string(view.data(), view.size() + 1));
} else {
emplace_arg(dynamic_args_.push<stored_type<T> >(arg));
}
}
template <typename T,
typename std::enable_if<!detail::is_string<typename std::decay<T>::type>::value, int>::type = 0>
void push_back(const T& arg) {
if (detail::const_check(need_copy<T>::value)) {
emplace_arg(dynamic_args_.push<stored_type<T> >(arg));
} else {
emplace_arg(detail::unwrap(arg));
}
}
/**

View File

@ -6,6 +6,7 @@
// For the license information refer to format.h.
#include "fmt/format-inl.h"
#include <mutex>
FMT_BEGIN_NAMESPACE
namespace detail {
@ -23,6 +24,60 @@ int format_float(char* buf, std::size_t size, const char* format, int precision,
return precision < 0 ? snprintf_ptr(buf, size, format, value)
: snprintf_ptr(buf, size, format, precision, value);
}
#define NODE_POOL_SIZE (10000u)
class dyn_node_pool
{
using type = std::array<uint8_t, dynamic_arg_list::max_pool_node_size>;
public:
dyn_node_pool() {
pool.resize(NODE_POOL_SIZE);
free_list.reserve(NODE_POOL_SIZE);
for (auto& elem : pool) {
free_list.push_back(elem.data());
}
}
void* alloc(std::size_t sz) {
assert(sz <= dynamic_arg_list::max_pool_node_size && "Object is too large to fit in the pool");
std::lock_guard<std::mutex> lock(m);
if (free_list.empty()) {
return nullptr;
}
auto* p = free_list.back();
free_list.pop_back();
return p;
}
void dealloc(void* p) {
if (!p) {
return;
}
std::lock_guard<std::mutex> lock(m);
free_list.push_back(reinterpret_cast<uint8_t *>(p));
}
private:
std::vector<type> pool;
std::vector<uint8_t *> free_list;
mutable std::mutex m;
};
static dyn_node_pool node_pool;
void *dynamic_arg_list::allocate_from_pool(std::size_t sz) {
return node_pool.alloc(sz);
}
void dynamic_arg_list::free_from_pool(void *ptr) {
return node_pool.dealloc(ptr);
}
} // namespace detail
template struct FMT_INSTANTIATION_DEF_API detail::basic_data<void>;