From 47780781797e766fb53df84a7a3524b150f29492 Mon Sep 17 00:00:00 2001 From: Eduardo Dantas Date: Sat, 4 Jan 2025 21:01:56 -0300 Subject: [PATCH] perf: optimize condition creation with ObjectPool (#3212) This introduces an ObjectPool for managing Condition objects, replacing frequent calls to std::make_shared with a lock-free pooling allocator. This optimization reduces memory allocation overhead, improves performance, and enhances thread safety in scenarios where Conditions are created and destroyed frequently, such as in onThink events or condition updates. By reusing objects from the pool instead of allocating and deallocating memory repeatedly, this change significantly reduces the strain on the memory management system and improves runtime efficiency. The pool is designed to handle up to 1024 objects per Condition type and supports safe, high-performance multithreaded operations. --- src/creatures/combat/condition.cpp | 33 ++++++------ src/utils/object_pool.hpp | 84 ++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 15 deletions(-) create mode 100644 src/utils/object_pool.hpp diff --git a/src/creatures/combat/condition.cpp b/src/creatures/combat/condition.cpp index eac70f8ba74..e5219126025 100644 --- a/src/creatures/combat/condition.cpp +++ b/src/creatures/combat/condition.cpp @@ -21,6 +21,7 @@ #include "creatures/creature.hpp" #include "creatures/players/player.hpp" #include "server/network/protocol/protocolgame.hpp" +#include "utils/object_pool.hpp" /** * Condition @@ -215,41 +216,41 @@ std::shared_ptr Condition::createCondition(ConditionId_t id, Conditio case CONDITION_DAZZLED: case CONDITION_CURSED: case CONDITION_BLEEDING: - return std::make_shared(id, type, buff, subId); + return ObjectPool::allocateShared(id, type, buff, subId); case CONDITION_HASTE: case CONDITION_PARALYZE: - return std::make_shared(id, type, ticks, buff, subId, param); + return ObjectPool::allocateShared(id, type, ticks, buff, subId, param); case CONDITION_INVISIBLE: - return std::make_shared(id, type, ticks, buff, subId); + return ObjectPool::allocateShared(id, type, ticks, buff, subId); case CONDITION_OUTFIT: - return std::make_shared(id, type, ticks, buff, subId); + return ObjectPool::allocateShared(id, type, ticks, buff, subId); case CONDITION_LIGHT: - return std::make_shared(id, type, ticks, buff, subId, param & 0xFF, (param & 0xFF00) >> 8); + return ObjectPool::allocateShared(id, type, ticks, buff, subId, param & 0xFF, (param & 0xFF00) >> 8); case CONDITION_REGENERATION: - return std::make_shared(id, type, ticks, buff, subId); + return ObjectPool::allocateShared(id, type, ticks, buff, subId); case CONDITION_SOUL: - return std::make_shared(id, type, ticks, buff, subId); + return ObjectPool::allocateShared(id, type, ticks, buff, subId); case CONDITION_ATTRIBUTES: - return std::make_shared(id, type, ticks, buff, subId); + return ObjectPool::allocateShared(id, type, ticks, buff, subId); case CONDITION_SPELLCOOLDOWN: - return std::make_shared(id, type, ticks, buff, subId); + return ObjectPool::allocateShared(id, type, ticks, buff, subId); case CONDITION_SPELLGROUPCOOLDOWN: - return std::make_shared(id, type, ticks, buff, subId); + return ObjectPool::allocateShared(id, type, ticks, buff, subId); case CONDITION_MANASHIELD: - return std::make_shared(id, type, ticks, buff, subId); + return ObjectPool::allocateShared(id, type, ticks, buff, subId); case CONDITION_FEARED: - return std::make_shared(id, type, ticks, buff, subId); + return ObjectPool::allocateShared(id, type, ticks, buff, subId); case CONDITION_ROOTED: case CONDITION_INFIGHT: @@ -261,11 +262,13 @@ std::shared_ptr Condition::createCondition(ConditionId_t id, Conditio case CONDITION_CHANNELMUTEDTICKS: case CONDITION_YELLTICKS: case CONDITION_PACIFIED: - return std::make_shared(id, type, ticks, buff, subId); + return ObjectPool::allocateShared(id, type, ticks, buff, subId); + case CONDITION_BAKRAGORE: - return std::make_shared(id, type, ticks, buff, subId, isPersistent); + return ObjectPool::allocateShared(id, type, ticks, buff, subId, isPersistent); + case CONDITION_GOSHNARTAINT: - return std::make_shared(id, type, ticks, buff, subId); + return ObjectPool::allocateShared(id, type, ticks, buff, subId); default: return nullptr; diff --git a/src/utils/object_pool.hpp b/src/utils/object_pool.hpp new file mode 100644 index 00000000000..c37d1d2efeb --- /dev/null +++ b/src/utils/object_pool.hpp @@ -0,0 +1,84 @@ +/** + * Canary - A free and open-source MMORPG server emulator + * Copyright (©) 2019-2024 OpenTibiaBR + * Repository: https://github.com/opentibiabr/canary + * License: https://github.com/opentibiabr/canary/blob/main/LICENSE + * Contributors: https://github.com/opentibiabr/canary/graphs/contributors + * Website: https://docs.opentibiabr.com/ + */ + +#pragma once + +#include "utils/lockfree.hpp" + +/** + * @brief A lock-free object pool for efficient memory allocation and reuse. + * + * This class provides an efficient mechanism for managing the allocation + * and deallocation of objects, reducing the overhead associated with + * frequent memory operations. It uses a lock-free structure to ensure + * thread safety and high performance in multithreaded environments. + * + * @tparam T The type of objects managed by the pool. + * @tparam CAPACITY The maximum number of objects that can be held in the pool. + */ +template +class ObjectPool { +public: + /** + * @brief The allocator type used for managing object memory. + */ + using Allocator = LockfreePoolingAllocator; + + /** + * @brief Allocates an object from the pool and returns it as a `std::shared_ptr`. + * + * The object is constructed in place using the provided arguments. + * The `std::shared_ptr` includes a custom deleter that returns the object + * to the pool when it is no longer needed. + * + * @tparam Args The types of the arguments used to construct the object.* @param args The arguments forwarded to the constructor of the object. + * @return A `std::shared_ptr` managing the allocated object, or `nullptr` if the pool is empty. + */ + template + static std::shared_ptr allocateShared(Args &&... args) { + T* obj = allocator.allocate(1); + if (obj) { + // Construct the object in place + std::construct_at(obj, std::forward(args)...); + + // Return a shared_ptr with a custom deleter + return std::shared_ptr(obj, [](T* ptr) { + std::destroy_at(ptr); // Destroy the object + allocator.deallocate(ptr, 1); // Return to the pool + }); + } + // Return nullptr if the pool is empty + return nullptr; + } + + static void clear() { + allocator.clear(); + } + + /** + * @brief Preallocates a specified number of objects in the pool. + * + * This method allows you to populate the pool with preallocated objects + * to improve performance by reducing the need for dynamic allocations at runtime. + * + * @param count The number of objects to preallocate. + */ + static void preallocate(size_t count) { + LockfreeFreeList::preallocate(count); + } + +private: + /** + * @brief The allocator instance used to manage object memory. + */ + static Allocator allocator; +}; + +template +typename ObjectPool::Allocator ObjectPool::allocator;