You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

366 lines
11 KiB

  1. // Copyright 2005-2024 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the 'License');
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an 'AS IS' BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. //
  15. // See www.openfst.org for extensive documentation on this weighted
  16. // finite-state transducer library.
  17. //
  18. // FST memory utilities.
  19. #ifndef FST_MEMORY_H_
  20. #define FST_MEMORY_H_
  21. #include <cstddef>
  22. #include <list>
  23. #include <memory>
  24. #include <utility>
  25. #include <vector>
  26. #include <fst/log.h>
  27. #include <fstream>
  28. namespace fst {
  29. // Default block allocation size.
  30. inline constexpr int kAllocSize = 64;
  31. // Minimum number of allocations per block.
  32. inline constexpr int kAllocFit = 4;
  33. // Base class for MemoryArena that allows (e.g.) MemoryArenaCollection to
  34. // easily manipulate collections of variously sized arenas.
  35. class MemoryArenaBase {
  36. public:
  37. virtual ~MemoryArenaBase() = default;
  38. virtual size_t Size() const = 0;
  39. };
  40. namespace internal {
  41. // Allocates 'size' unintialized memory chunks of size object_size from
  42. // underlying blocks of (at least) size 'block_size * object_size'.
  43. // All blocks are freed when this class is deleted. Result of allocate() will
  44. // be aligned to object_size.
  45. template <size_t object_size>
  46. class MemoryArenaImpl : public MemoryArenaBase {
  47. public:
  48. static constexpr size_t kObjectSize = object_size;
  49. explicit MemoryArenaImpl(size_t block_size = kAllocSize)
  50. : block_size_(block_size * kObjectSize), block_pos_(0) {
  51. blocks_.push_front(
  52. fst::make_unique_for_overwrite<std::byte[]>(block_size_));
  53. }
  54. void *Allocate(size_t size) {
  55. const auto byte_size = size * kObjectSize;
  56. if (byte_size * kAllocFit > block_size_) {
  57. // Large block; adds new large block.
  58. blocks_.push_back(
  59. fst::make_unique_for_overwrite<std::byte[]>(byte_size));
  60. return blocks_.back().get();
  61. }
  62. if (block_pos_ + byte_size > block_size_) {
  63. // Doesn't fit; adds new standard block.
  64. block_pos_ = 0;
  65. blocks_.push_front(
  66. fst::make_unique_for_overwrite<std::byte[]>(block_size_));
  67. }
  68. // Fits; uses current block.
  69. auto *ptr = &blocks_.front()[block_pos_];
  70. block_pos_ += byte_size;
  71. return ptr;
  72. }
  73. size_t Size() const override { return kObjectSize; }
  74. private:
  75. const size_t block_size_; // Default block size in bytes.
  76. size_t block_pos_; // Current position in block in bytes.
  77. std::list<std::unique_ptr<std::byte[]>> blocks_; // List of allocated blocks.
  78. };
  79. } // namespace internal
  80. template <typename T>
  81. using MemoryArena = internal::MemoryArenaImpl<sizeof(T)>;
  82. // Base class for MemoryPool that allows (e.g.) MemoryPoolCollection to easily
  83. // manipulate collections of variously sized pools.
  84. class MemoryPoolBase {
  85. public:
  86. virtual ~MemoryPoolBase() = default;
  87. virtual size_t Size() const = 0;
  88. };
  89. namespace internal {
  90. // Allocates and frees initially uninitialized memory chunks of size
  91. // object_size. Keeps an internal list of freed chunks that are reused (as is)
  92. // on the next allocation if available. Chunks are constructed in blocks of size
  93. // 'pool_size'.
  94. template <size_t object_size>
  95. class MemoryPoolImpl : public MemoryPoolBase {
  96. public:
  97. static constexpr size_t kObjectSize = object_size;
  98. struct Link {
  99. std::byte buf[kObjectSize];
  100. Link *next;
  101. };
  102. explicit MemoryPoolImpl(size_t pool_size)
  103. : mem_arena_(pool_size), free_list_(nullptr) {}
  104. void *Allocate() {
  105. Link *link;
  106. if (free_list_ == nullptr) {
  107. link = static_cast<Link *>(mem_arena_.Allocate(1));
  108. link->next = nullptr;
  109. } else {
  110. link = free_list_;
  111. free_list_ = link->next;
  112. }
  113. return fst::implicit_cast<std::byte *>(link->buf);
  114. }
  115. void Free(void *ptr) {
  116. if (ptr) {
  117. auto *link = static_cast<Link *>(ptr);
  118. link->next = free_list_;
  119. free_list_ = link;
  120. }
  121. }
  122. size_t Size() const override { return kObjectSize; }
  123. private:
  124. MemoryArena<Link> mem_arena_;
  125. Link *free_list_; // Not owned.
  126. MemoryPoolImpl(const MemoryPoolImpl &) = delete;
  127. MemoryPoolImpl &operator=(const MemoryPoolImpl &) = delete;
  128. };
  129. } // namespace internal
  130. // Allocates and frees initially uninitialized memory chunks of size sizeof(T).
  131. // All memory is freed when the class is deleted. The result of Allocate() will
  132. // be suitably memory-aligned. Combined with placement operator new and destroy
  133. // functions for the T class, this can be used to improve allocation efficiency.
  134. // See nlp/fst/lib/visit.h (global new) and nlp/fst/lib/dfs-visit.h (class new)
  135. // for examples.
  136. template <typename T>
  137. class MemoryPool : public internal::MemoryPoolImpl<sizeof(T)> {
  138. public:
  139. // 'pool_size' specifies the size of the initial pool and how it is extended.
  140. explicit MemoryPool(size_t pool_size = kAllocSize)
  141. : internal::MemoryPoolImpl<sizeof(T)>(pool_size) {}
  142. };
  143. // Stores a collection of memory arenas.
  144. class MemoryArenaCollection {
  145. public:
  146. // 'block_size' specifies the block size of the arenas.
  147. explicit MemoryArenaCollection(size_t block_size = kAllocSize)
  148. : block_size_(block_size) {}
  149. template <typename T>
  150. MemoryArena<T> *Arena() {
  151. if (sizeof(T) >= arenas_.size()) arenas_.resize(sizeof(T) + 1);
  152. auto &arena = arenas_[sizeof(T)];
  153. if (arena == nullptr) {
  154. arena = std::make_unique<MemoryArena<T>>(block_size_);
  155. }
  156. return down_cast<MemoryArena<T> *>(arena.get());
  157. }
  158. size_t BlockSize() const { return block_size_; }
  159. private:
  160. size_t block_size_;
  161. std::vector<std::unique_ptr<MemoryArenaBase>> arenas_;
  162. };
  163. // Stores a collection of memory pools
  164. class MemoryPoolCollection {
  165. public:
  166. // 'pool_size' specifies the size of initial pool and how it is extended.
  167. explicit MemoryPoolCollection(size_t pool_size = kAllocSize)
  168. : pool_size_(pool_size) {}
  169. template <typename T>
  170. MemoryPool<T> *Pool() {
  171. if (sizeof(T) >= pools_.size()) pools_.resize(sizeof(T) + 1);
  172. auto &pool = pools_[sizeof(T)];
  173. if (pool == nullptr) pool = std::make_unique<MemoryPool<T>>(pool_size_);
  174. return down_cast<MemoryPool<T> *>(pool.get());
  175. }
  176. size_t PoolSize() const { return pool_size_; }
  177. private:
  178. size_t pool_size_;
  179. std::vector<std::unique_ptr<MemoryPoolBase>> pools_;
  180. };
  181. // STL allocator using memory arenas. Memory is allocated from underlying
  182. // blocks of size 'block_size * sizeof(T)'. Memory is freed only when all
  183. // objects using this allocator are destroyed and there is otherwise no reuse
  184. // (unlike PoolAllocator).
  185. //
  186. // This allocator has object-local state so it should not be used with splicing
  187. // or swapping operations between objects created with different allocators nor
  188. // should it be used if copies must be thread-safe. The result of allocate()
  189. // will be suitably memory-aligned.
  190. template <typename T>
  191. class BlockAllocator {
  192. public:
  193. using Allocator = std::allocator<T>;
  194. using size_type = typename Allocator::size_type;
  195. using value_type = typename Allocator::value_type;
  196. explicit BlockAllocator(size_t block_size = kAllocSize)
  197. : arenas_(std::make_shared<MemoryArenaCollection>(block_size)) {}
  198. template <typename U>
  199. explicit BlockAllocator(const BlockAllocator<U> &arena_alloc)
  200. : arenas_(arena_alloc.Arenas()) {}
  201. T *allocate(size_type n, const void *hint = nullptr) {
  202. if (n * kAllocFit <= kAllocSize) {
  203. return static_cast<T *>(Arena()->Allocate(n));
  204. } else {
  205. auto allocator = Allocator();
  206. return std::allocator_traits<Allocator>::allocate(allocator, n, hint);
  207. }
  208. }
  209. void deallocate(T *p, size_type n) {
  210. if (n * kAllocFit > kAllocSize) Allocator().deallocate(p, n);
  211. }
  212. std::shared_ptr<MemoryArenaCollection> Arenas() const { return arenas_; }
  213. private:
  214. MemoryArena<T> *Arena() { return arenas_->Arena<T>(); }
  215. std::shared_ptr<MemoryArenaCollection> arenas_;
  216. };
  217. template <typename T, typename U>
  218. bool operator==(const BlockAllocator<T> &alloc1,
  219. const BlockAllocator<U> &alloc2) {
  220. return false;
  221. }
  222. template <typename T, typename U>
  223. bool operator!=(const BlockAllocator<T> &alloc1,
  224. const BlockAllocator<U> &alloc2) {
  225. return true;
  226. }
  227. // STL allocator using memory pools. Memory is allocated from underlying
  228. // blocks of size 'block_size * sizeof(T)'. Keeps an internal list of freed
  229. // chunks thare are reused on the next allocation.
  230. //
  231. // This allocator has object-local state so it should not be used with splicing
  232. // or swapping operations between objects created with different allocators nor
  233. // should it be used if copies must be thread-safe. The result of allocate()
  234. // will be suitably memory-aligned.
  235. template <typename T>
  236. class PoolAllocator {
  237. public:
  238. using Allocator = std::allocator<T>;
  239. using size_type = typename Allocator::size_type;
  240. using value_type = typename Allocator::value_type;
  241. explicit PoolAllocator(size_t pool_size = kAllocSize)
  242. : pools_(std::make_shared<MemoryPoolCollection>(pool_size)) {}
  243. template <typename U>
  244. explicit PoolAllocator(const PoolAllocator<U> &pool_alloc)
  245. : pools_(pool_alloc.Pools()) {}
  246. T *allocate(size_type n, const void *hint = nullptr) {
  247. if (n == 1) {
  248. return static_cast<T *>(Pool<1>()->Allocate());
  249. } else if (n == 2) {
  250. return static_cast<T *>(Pool<2>()->Allocate());
  251. } else if (n <= 4) {
  252. return static_cast<T *>(Pool<4>()->Allocate());
  253. } else if (n <= 8) {
  254. return static_cast<T *>(Pool<8>()->Allocate());
  255. } else if (n <= 16) {
  256. return static_cast<T *>(Pool<16>()->Allocate());
  257. } else if (n <= 32) {
  258. return static_cast<T *>(Pool<32>()->Allocate());
  259. } else if (n <= 64) {
  260. return static_cast<T *>(Pool<64>()->Allocate());
  261. } else {
  262. auto allocator = Allocator();
  263. return std::allocator_traits<Allocator>::allocate(allocator, n, hint);
  264. }
  265. }
  266. void deallocate(T *p, size_type n) {
  267. if (n == 1) {
  268. Pool<1>()->Free(p);
  269. } else if (n == 2) {
  270. Pool<2>()->Free(p);
  271. } else if (n <= 4) {
  272. Pool<4>()->Free(p);
  273. } else if (n <= 8) {
  274. Pool<8>()->Free(p);
  275. } else if (n <= 16) {
  276. Pool<16>()->Free(p);
  277. } else if (n <= 32) {
  278. Pool<32>()->Free(p);
  279. } else if (n <= 64) {
  280. Pool<64>()->Free(p);
  281. } else {
  282. Allocator().deallocate(p, n);
  283. }
  284. }
  285. std::shared_ptr<MemoryPoolCollection> Pools() const { return pools_; }
  286. private:
  287. template <int n>
  288. struct TN {
  289. T buf[n];
  290. };
  291. template <int n>
  292. MemoryPool<TN<n>> *Pool() {
  293. return pools_->Pool<TN<n>>();
  294. }
  295. std::shared_ptr<MemoryPoolCollection> pools_;
  296. };
  297. template <typename T, typename U>
  298. bool operator==(const PoolAllocator<T> &alloc1,
  299. const PoolAllocator<U> &alloc2) {
  300. return false;
  301. }
  302. template <typename T, typename U>
  303. bool operator!=(const PoolAllocator<T> &alloc1,
  304. const PoolAllocator<U> &alloc2) {
  305. return true;
  306. }
  307. } // namespace fst
  308. #endif // FST_MEMORY_H_