Start refactoring memory management, adding memory_resource

This commit is contained in:
mutouyun 2025-01-08 20:06:14 +08:00 committed by 木头云
parent 73ce564ab2
commit a4361d2b97
6 changed files with 268 additions and 14 deletions

View File

@ -31,6 +31,7 @@ enum : std::uint32_t {
}; };
enum : std::size_t { enum : std::size_t {
central_cache_default_size = 1024 * 1024, ///< 1MB
data_length = 64, data_length = 64,
large_msg_limit = data_length, large_msg_limit = data_length,
large_msg_align = 1024, large_msg_align = 1024,

View File

@ -0,0 +1,66 @@
/**
* \file libipc/memory_resource.h
* \author mutouyun (orz@orzz.org)
* \brief Implement memory allocation strategies that can be used by pmr::allocator.
*/
#pragma once
#include <type_traits>
#include <cstddef> // std::size_t, std::max_align_t
#include "libipc/imp/export.h"
#include "libipc/def.h"
namespace ipc {
namespace mem {
/// \brief Helper trait for memory resource.
template <typename T, typename = void>
struct has_allocate : std::false_type {};
template <typename T>
struct has_allocate<T,
typename std::enable_if<std::is_convertible<
decltype(std::declval<T &>().allocate(std::declval<std::size_t>(),
std::declval<std::size_t>())), void *
>::value>::type> : std::true_type {};
template <typename T, typename = void>
struct has_deallocate : std::false_type {};
template <typename T>
struct has_deallocate<T,
decltype(std::declval<T &>().deallocate(std::declval<void *>(),
std::declval<std::size_t>(),
std::declval<std::size_t>()))
> : std::true_type {};
template <typename T>
using verify_memory_resource =
std::enable_if_t<has_allocate <T>::value &&
has_deallocate<T>::value, bool>;
/**
* \class LIBIPC_EXPORT new_delete_resource
* \brief A memory resource that uses the
* standard memory allocation and deallocation interface to allocate memory.
* \see https://en.cppreference.com/w/cpp/memory/new_delete_resource
*/
class LIBIPC_EXPORT new_delete_resource {
public:
/// \brief Returns a pointer to a `new_delete_resource`.
static new_delete_resource *get() noexcept;
/// \brief Allocates storage with a size of at least bytes bytes, aligned to the specified alignment.
/// \remark Returns nullptr if storage of the requested size and alignment cannot be obtained.
/// \see https://en.cppreference.com/w/cpp/memory/memory_resource/do_allocate
void *allocate(std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept;
/// \brief Deallocates the storage pointed to by p.
/// \see https://en.cppreference.com/w/cpp/memory/memory_resource/deallocate
void deallocate(void *p, std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept;
};
} // namespace mem
} // namespace ipc

View File

@ -0,0 +1,101 @@
#include <cstdlib> // std::aligned_alloc
#include "libipc/imp/detect_plat.h"
#include "libipc/imp/aligned.h"
#include "libipc/imp/system.h"
#include "libipc/imp/log.h"
#include "libipc/mem/memory_resource.h"
#include "libipc/mem/verify_args.h"
namespace ipc {
namespace mem {
/**
* \brief Returns a pointer to a new_delete_resource.
*
* \return new_delete_resource*
*/
new_delete_resource *new_delete_resource::get() noexcept {
static new_delete_resource mem_res;
return &mem_res;
}
/**
* \brief Allocates storage with a size of at least bytes bytes, aligned to the specified alignment.
* Alignment shall be a power of two.
*
* \see https://en.cppreference.com/w/cpp/memory/memory_resource/do_allocate
* https://www.cppstories.com/2019/08/newnew-align/
*
* \return void * - nullptr if storage of the requested size and alignment cannot be obtained.
*/
void *new_delete_resource::allocate(std::size_t bytes, std::size_t alignment) noexcept {
LIBIPC_LOG();
if (!verify_args(bytes, alignment)) {
log.error("invalid bytes = ", bytes, ", alignment = ", alignment);
return nullptr;
}
#if defined(LIBIPC_CPP_17)
/// \see https://en.cppreference.com/w/cpp/memory/c/aligned_alloc
/// \remark The size parameter must be an integral multiple of alignment.
return std::aligned_alloc(alignment, ipc::round_up(bytes, alignment));
#else
if (alignment <= alignof(std::max_align_t)) {
/// \see https://en.cppreference.com/w/cpp/memory/c/malloc
return std::malloc(bytes);
}
#if defined(LIBIPC_OS_WIN)
/// \see https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/aligned-malloc
return ::_aligned_malloc(bytes, alignment);
#else // try posix
/// \see https://pubs.opengroup.org/onlinepubs/9699919799/functions/posix_memalign.html
void *p = nullptr;
int ret = ::posix_memalign(&p, alignment, bytes);
if (ret != 0) {
log.error("failed: posix_memalign(alignment = ", alignment,
", bytes = ", bytes,
"). error = ", sys::error(ret));
return nullptr;
}
return p;
#endif
#endif // defined(LIBIPC_CPP_17)
}
/**
* \brief Deallocates the storage pointed to by p.
* The storage it points to must not yet have been deallocated, otherwise the behavior is undefined.
*
* \see https://en.cppreference.com/w/cpp/memory/memory_resource/do_deallocate
*
* \param p must have been returned by a prior call to new_delete_resource::do_allocate(bytes, alignment).
*/
void new_delete_resource::deallocate(void *p, std::size_t bytes, std::size_t alignment) noexcept {
LIBIPC_LOG();
if (p == nullptr) {
return;
}
if (!verify_args(bytes, alignment)) {
log.error("invalid bytes = ", bytes, ", alignment = ", alignment);
return;
}
#if defined(LIBIPC_CPP_17)
/// \see https://en.cppreference.com/w/cpp/memory/c/free
std::free(p);
#else
if (alignment <= alignof(std::max_align_t)) {
std::free(p);
return;
}
#if defined(LIBIPC_OS_WIN)
/// \see https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/aligned-free
::_aligned_free(p);
#else // try posix
::free(p);
#endif
#endif // defined(LIBIPC_CPP_17)
}
} // namespace mem
} // namespace ipc

View File

@ -0,0 +1,22 @@
#pragma once
#include <cstddef>
namespace ipc {
namespace mem {
/**
* \brief Check that bytes is not 0 and that the alignment is a power of two.
*/
inline constexpr bool verify_args(std::size_t bytes, std::size_t alignment) noexcept {
if (bytes == 0) {
return false;
}
if ((alignment == 0) || (alignment & (alignment - 1)) != 0) {
return false;
}
return true;
}
} // namespace mem
} // namespace ipc

View File

@ -19,6 +19,7 @@ include_directories(
file(GLOB SRC_FILES file(GLOB SRC_FILES
${LIBIPC_PROJECT_DIR}/test/test_*.cpp ${LIBIPC_PROJECT_DIR}/test/test_*.cpp
${LIBIPC_PROJECT_DIR}/test/imp/*.cpp ${LIBIPC_PROJECT_DIR}/test/imp/*.cpp
${LIBIPC_PROJECT_DIR}/test/mem/*.cpp
# ${LIBIPC_PROJECT_DIR}/test/profiler/*.cpp # ${LIBIPC_PROJECT_DIR}/test/profiler/*.cpp
) )
file(GLOB HEAD_FILES ${LIBIPC_PROJECT_DIR}/test/test_*.h) file(GLOB HEAD_FILES ${LIBIPC_PROJECT_DIR}/test/test_*.h)

View File

@ -0,0 +1,63 @@
#include "test.h"
#include <utility>
#if defined(LIBIPC_CPP_17) && defined(__cpp_lib_memory_resource)
# include <memory_resource>
#endif
#include "libipc/mem/memory_resource.h"
namespace {
template <typename T>
void *test_mr(T &&mr, std::size_t bytes, std::size_t alignment) {
auto p = std::forward<T>(mr).allocate(bytes, alignment);
if (alignment == 0) {
EXPECT_EQ(p, nullptr);
} else if (p != nullptr) {
EXPECT_EQ((std::size_t)p % alignment, 0);
}
std::forward<T>(mr).deallocate(p, bytes, alignment);
return p;
}
} // namespace
TEST(memory_resource, traits) {
EXPECT_FALSE(ipc::mem::has_allocate<void>::value);
EXPECT_FALSE(ipc::mem::has_allocate<int>::value);
EXPECT_FALSE(ipc::mem::has_allocate<std::vector<int>>::value);
EXPECT_FALSE(ipc::mem::has_allocate<std::allocator<int>>::value);
#if defined(LIBIMP_CPP_17) && defined(__cpp_lib_memory_resource)
EXPECT_TRUE (ipc::mem::has_allocate<std::ipc::mem::memory_resource>::value);
EXPECT_TRUE (ipc::mem::has_allocate<std::ipc::mem::polymorphic_allocator<int>>::value);
#endif
EXPECT_FALSE(ipc::mem::has_deallocate<void>::value);
EXPECT_FALSE(ipc::mem::has_deallocate<int>::value);
EXPECT_FALSE(ipc::mem::has_deallocate<std::vector<int>>::value);
EXPECT_FALSE(ipc::mem::has_deallocate<std::allocator<int>>::value);
#if defined(LIBIMP_CPP_17) && defined(__cpp_lib_memory_resource)
EXPECT_TRUE (ipc::mem::has_deallocate<std::ipc::mem::memory_resource>::value);
EXPECT_FALSE(ipc::mem::has_deallocate<std::ipc::mem::polymorphic_allocator<int>>::value);
#endif
}
TEST(memory_resource, new_delete_resource) {
ipc::mem::new_delete_resource mem_res;
EXPECT_EQ(test_mr(mem_res, 0, 0), nullptr);
EXPECT_EQ(test_mr(mem_res, 0, 1), nullptr);
EXPECT_EQ(test_mr(mem_res, 0, 2), nullptr);
EXPECT_EQ(test_mr(mem_res, 0, 3), nullptr);
EXPECT_EQ(test_mr(mem_res, 0, 8), nullptr);
EXPECT_EQ(test_mr(mem_res, 0, 64), nullptr);
EXPECT_EQ(test_mr(mem_res, 1, 0), nullptr);
EXPECT_NE(test_mr(mem_res, 1, 1), nullptr);
EXPECT_NE(test_mr(mem_res, 1, 2), nullptr);
EXPECT_EQ(test_mr(mem_res, 1, 3), nullptr);
EXPECT_NE(test_mr(mem_res, 1, 8), nullptr);
EXPECT_NE(test_mr(mem_res, 1, 64), nullptr);
}