diff --git a/native_client/BUILD b/native_client/BUILD index 61bfb19f..55c2dfc1 100644 --- a/native_client/BUILD +++ b/native_client/BUILD @@ -100,6 +100,7 @@ cc_library( includes = [ ".", "ctcdecode/third_party/ThreadPool", + "ctcdecode/third_party/object_pool", ] + OPENFST_INCLUDES_PLATFORM, deps = [":kenlm"], linkopts = [ diff --git a/native_client/ctcdecode/build_archive.py b/native_client/ctcdecode/build_archive.py index 8a689ac0..f94d1adf 100644 --- a/native_client/ctcdecode/build_archive.py +++ b/native_client/ctcdecode/build_archive.py @@ -26,7 +26,8 @@ INCLUDES = [ '..', '../kenlm', OPENFST_DIR + '/src/include', - 'third_party/ThreadPool' + 'third_party/ThreadPool', + 'third_party/object_pool' ] KENLM_FILES = (glob.glob('../kenlm/util/*.cc') diff --git a/native_client/ctcdecode/path_trie.h b/native_client/ctcdecode/path_trie.h index 8c3ae0a7..20060a3e 100644 --- a/native_client/ctcdecode/path_trie.h +++ b/native_client/ctcdecode/path_trie.h @@ -9,6 +9,7 @@ #include "fst/fstlib.h" #include "alphabet.h" +#include "object_pool.h" /* Tree structure with parent and children information * It is used to store the timesteps data for the PathTrie below @@ -108,7 +109,8 @@ private: // TreeNode implementation template std::shared_ptr> add_child(std::shared_ptr> const& node, ChildDataT&& data_){ - node->children.push_back(std::make_shared>(node, std::forward(data_))); + static godefv::memory::object_pool_t> tree_node_pool; + node->children.emplace_back(tree_node_pool.make_unique(node, std::forward(data_))); return node->children.back(); } diff --git a/native_client/ctcdecode/third_party/object_pool/object_pool.h b/native_client/ctcdecode/third_party/object_pool/object_pool.h new file mode 100755 index 00000000..0c0a3287 --- /dev/null +++ b/native_client/ctcdecode/third_party/object_pool/object_pool.h @@ -0,0 +1,97 @@ +#ifndef GODEFV_MEMORY_OBJECT_POOL_H +#define GODEFV_MEMORY_OBJECT_POOL_H + +#include "unique_ptr.h" +#include +#include +#include + +namespace godefv{ namespace memory{ + +//! Allocates instances of Object efficiently (constant time and log((maximum number of Objects used at the same time)/ChunkSize) calls to malloc in the whole lifetime of the object pool). +//! When an instance returned by the object pool is destroyed, its allocated memory is recycled by the object pool. Defragmenting the object pool to free memory is not possible. +template class Allocator = std::allocator, std::size_t ChunkSize = 1024> +class object_pool_t{ + //! An object slot is an uninitialized memory space of the same size as Object. + //! It is initially "free". It can then be "used" to construct an Object in place and the pointer to it is returned by the object pool. When the pointer is destroyed, the object slot is "recycled" and can be used again but it is not "free" anymore because "free" object slots are contiguous in memory. + using object_slot_t=std::array; + + //! To minimize calls to malloc, the object slots are allocated in chunks. + //! For example, if ChunkSize=8, a chunk may look like this : |used|recycled|used|used|recycled|free|free|free|. In this example, if more than 5 new Object are now asked from the object pool, at least one new chunk of 8 object slots will be allocated. + using chunk_t=std::array; + Allocator chunk_allocator; //!< This allocator can be used to have aligned memory if required. + std::vector> memory_chunks; + + //! Recycled object slots are tracked using a stack of pointers to them. When an object slot is recycled, a pointer to it is pushed in constant time. When a new object is constructed, a recycled object slot can be found and poped in constant time. + std::vector recycled_object_slots; + + object_slot_t* free_object_slots_begin; + object_slot_t* free_object_slots_end; + + //! Custom deleter to recycle the deleted pointers. + struct deleter_t{ + private: + object_pool_t* object_pool_ptr; + public: + explicit deleter_t(decltype(object_pool_ptr) input_object_pool_ptr) : + object_pool_ptr(input_object_pool_ptr) + {} + + //! When a pointer provided by the ObjectPool is deleted, its memory is converted to an object slot to be recycled. + void operator()(Object* object_ptr) + { + object_ptr->~Object(); + object_pool_ptr->recycled_object_slots.push_back(reinterpret_cast(object_ptr)); + } + }; + +public: + using object_t = Object; + using object_unique_ptr_t = std::unique_ptr; //!< The type returned by the object pool. + + object_pool_t(Allocator const& allocator = Allocator{}) : + free_object_slots_begin{ free_object_slots_end }, // At the begining, set the 2 iterators at the same value to simulate a full pool. + chunk_allocator{ allocator } + {} + + //! Returns a unique pointer to an object_t using an unused object slot from the object pool. + template object_unique_ptr_t make_unique(Args&&... vars){ + auto construct_object_unique_ptr=[&](object_slot_t* object_slot){ + return object_unique_ptr_t{ new (reinterpret_cast(object_slot)) object_t{ std::forward(vars)... } , deleter_t{ this } }; + }; + + // If a recycled object slot is available, use it. + if (!recycled_object_slots.empty()) + { + auto object_slot = recycled_object_slots.back(); + recycled_object_slots.pop_back(); + return construct_object_unique_ptr(object_slot); + } + + // If the pool is full: add a new chunk. + if (free_object_slots_begin == free_object_slots_end) + { + memory_chunks.emplace_back(chunk_allocator); + auto& new_chunk = memory_chunks.back(); + free_object_slots_begin=new_chunk->data(); + free_object_slots_end =free_object_slots_begin+new_chunk->size(); + } + + // We know that there is now at least one free object slot, use it. + return construct_object_unique_ptr(free_object_slots_begin++); + } + + //! Returns the total number of object slots (free, recycled, or used). + std::size_t capacity() const{ + return memory_chunks.size()*ChunkSize; + } + + //! Returns the number of currently used object slots. + std::size_t size() const{ + return capacity() - static_cast(free_object_slots_end-free_object_slots_begin) - recycled_object_slots.size(); + } +}; + +}} /* namespace godefv::memory */ + +#endif /* GODEFV_MEMORY_OBJECT_POOL_H */ diff --git a/native_client/ctcdecode/third_party/object_pool/unique_ptr.h b/native_client/ctcdecode/third_party/object_pool/unique_ptr.h new file mode 100755 index 00000000..b1f00950 --- /dev/null +++ b/native_client/ctcdecode/third_party/object_pool/unique_ptr.h @@ -0,0 +1,39 @@ +#ifndef GODEFV_MEMORY_ALLOCATED_UNIQUE_PTR_H +#define GODEFV_MEMORY_ALLOCATED_UNIQUE_PTR_H + +#include + +namespace godefv{ namespace memory{ + +//! A deleter to deallocate memory which have been allocated by the given allocator. +template struct allocator_deleter_t +{ + allocator_deleter_t(Allocator const& allocator) : + mAllocator{ allocator } + {} + + void operator()(typename Allocator::value_type* ptr) + { + mAllocator.deallocate(ptr, 1); + } + +private: + Allocator mAllocator; +}; + +//! A smart pointer like std::unique_ptr but templated on an allocator instead of a deleter. +//! The deleter is deduced from the given allocator. +template > +class unique_ptr_t : public std::unique_ptr> +{ + using base_t = std::unique_ptr>; + +public: + unique_ptr_t(Allocator allocator = Allocator{}) : + base_t{ allocator.allocate(1), allocator_deleter_t{ allocator } } + {} +}; + +}} // namespace godefv::memory + +#endif // GODEFV_MEMORY_ALLOCATED_UNIQUE_PTR_H