The s3select library uses a cool custom arena allocator for a lot of its small allocations, so they can be mostly satisfied without locking and freed all at once at the end of a query. This allocator is only currently used for the AST nodes and functions, though, and doesn't cover allocations from std library types like string/map/vector. I was looking more closely at the std::pmr library from c++17 as a way to use this custom allocator with the std containers. I found that it also provides a https://en.cppreference.com/w/cpp/memory/monotonic_buffer_resource class with similar properties to this custom arena allocator: "The class std::pmr::monotonic_buffer_resource is a special-purpose memory resource class that releases the allocated memory only when the resource is destroyed. It is intended for very fast memory allocations in situations where memory is used to build up a few objects and then is released all at once. monotonic_buffer_resource can be constructed with an initial buffer. If there is no initial buffer, or if the buffer is exhausted, additional buffers are obtained from an upstream memory resource supplied at construction. The size of buffers obtained follows a geometric progression. monotonic_buffer_resource is not thread-safe." I think the ideal s3select interface would take a std::pmr::memory_resource* as input, so the application could pass in whatever allocation strategy it wanted. Radosgw could choose to use std::pmr::monotonic_buffer_resource directly, a derived memory_resource that wraps it, a custom memory_resource that uses the existing logic in class s3select_allocator, or even nullptr to use the default new/delete resource. Integration of std::pmr in s3select could come in two parts: one for use with std library types, and one for the general allocation of things like our AST nodes and functions. For the std containers, we can replace types like std::vector with their aliases in namespace std::pmr - i.e. std::pmr::vector<T> is just a std::vector<T, std::pmr::polymorphic_allocator<T>>. When constructing these container types, we'd just have to pass a pointer to its memory_resource as the allocator argument. That would also require passing the pointer to the constructors of any types that have std containers as members. For general allocations, we can use std::unique_ptr<> with a custom Deleter that frees its memory back to the std::pmr::memory_resource it came from, and a helper function like pmr_allocate_unique<T>() that takes a std::pmr::memory_resource pointer, allocates/constructs a T using the std::pmr::polymorphic_allocator<T>, and returns it as a unique_ptr. Use of unique_ptr means that the object lifetimes are managed automatically, instead of having to track all allocations in a list with a cleanup step that calls their destructors. Here's what the unique_ptr stuff looks like: #include <memory> #include <memory_resource> // a unique_ptr Deleter that frees memory from a polymorphic memory resource class pmr_deleter { std::pmr::memory_resource* r; public: pmr_deleter(std::pmr::memory_resource* r = nullptr) : r(r) {} template <typename T> void operator()(T* ptr) const { std::pmr::polymorphic_allocator<T> alloc{r}; alloc.destroy(ptr); alloc.deallocate(ptr, 1); } }; // a unique_ptr alias for pmr-allocated pointers template <typename T> using pmr_unique_ptr = std::unique_ptr<T, pmr_deleter>; template <typename T, typename ...Args> pmr_unique_ptr<T> pmr_allocate_unique(std::pmr::memory_resource* r, Args&& ...args) { std::pmr::polymorphic_allocator<T> alloc{r}; auto p = alloc.allocate(1); try { alloc.construct(p, std::forward<Args>(args)...); // may throw return {p, r}; } catch (const std::exception&) { alloc.deallocate(p, 1); throw; } } _______________________________________________ Dev mailing list -- dev@xxxxxxx To unsubscribe send an email to dev-leave@xxxxxxx