Arcane  4.1.12.0
Developer documentation
Loading...
Searching...
No Matches
MemoryPool.cc
1// -*- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature -*-
2//-----------------------------------------------------------------------------
3// Copyright 2000-2026 CEA (www.cea.fr) IFPEN (www.ifpenergiesnouvelles.com)
4// See the top-level COPYRIGHT file for details.
5// SPDX-License-Identifier: Apache-2.0
6//-----------------------------------------------------------------------------
7/*---------------------------------------------------------------------------*/
8/* MemoryPool.cc (C) 2000-2026 */
9/* */
10/* Class to manage a list of allocated zones. */
11/*---------------------------------------------------------------------------*/
12/*---------------------------------------------------------------------------*/
13
14#include "arccore/common/internal/MemoryPool.h"
15
16#include "arccore/base/FatalErrorException.h"
17#include "arccore/base/PlatformUtils.h"
18#include "arccore/base/Convert.h"
19
20#include "arccore/common/Array.h"
21
22#include <unordered_map>
23#include <map>
24#include <atomic>
25#include <mutex>
26
27/*---------------------------------------------------------------------------*/
28/*---------------------------------------------------------------------------*/
29
30namespace Arcane::Impl
31{
32
33/*---------------------------------------------------------------------------*/
34/*---------------------------------------------------------------------------*/
35
37{
39 class AllocatedMap
40 {
41 public:
42
43 using MapType = std::unordered_map<void*, size_t>;
44 using ValueType = MapType::value_type;
45 using MapIterator = MapType::iterator;
46
47 public:
48
49 explicit AllocatedMap(const String& name)
50 : m_name(name)
51 {}
52
53 public:
54
55 void removePointer(void* ptr, size_t size)
56 {
57 std::unique_lock<std::mutex> lg(m_mutex);
58 auto x = m_allocated_memory_map.find(ptr);
59 if (x == m_allocated_memory_map.end()) {
60 ++m_nb_error;
61 String str = String::format("MemoryPool '{0}': pointer {1} is not in the allocated map", m_name, ptr);
62 ARCCORE_FATAL_IF(m_is_throw_on_error, str);
63 std::cerr << "ERROR: " << str << "\n";
64 return;
65 }
66
67 size_t allocated_size = x->second;
68 if (size != allocated_size) {
69 ++m_nb_error;
70 String str = String::format("MemoryPool '{0}': Incoherent size saved_size={1} arg_size={2}",
71 m_name, allocated_size, size);
72 ARCCORE_FATAL_IF(m_is_throw_on_error, str);
73 std::cerr << "ERROR: " << str << "\n";
74 }
75
76 m_allocated_memory_map.erase(x);
77 }
78
79 void addPointer(void* ptr, size_t size)
80 {
81 std::unique_lock<std::mutex> lg(m_mutex);
82 auto x = m_allocated_memory_map.find(ptr);
83 ARCCORE_FATAL_IF((x != m_allocated_memory_map.end()),
84 "MemoryPool '{0}': pointer {1} (for size={2}) is already in the allocated map (with size={3})",
85 m_name, ptr, size, x->second);
86 m_allocated_memory_map.insert(std::make_pair(ptr, size));
87 }
88
89 size_t size() const
90 {
91 std::unique_lock<std::mutex> lg(m_mutex);
92 return m_allocated_memory_map.size();
93 }
94
95 bool isThrowOnError() const { return m_is_throw_on_error; }
96 void setIsThrowOnError(bool v) { m_is_throw_on_error = v; }
97 Int32 nbError() const { return m_nb_error; }
98
99 private:
100
101 MapType m_allocated_memory_map;
102 String m_name;
103 std::atomic<Int32> m_nb_error = 0;
104 bool m_is_throw_on_error = false;
105 mutable std::mutex m_mutex;
106 };
107
108 public:
109
111 class FreedMap
112 {
113 public:
114
115 using MapType = std::unordered_multimap<size_t, void*>;
116
117 public:
118
119 explicit FreedMap(const String& name)
120 : m_name(name)
121 {}
122
123 public:
124
131 void* getPointer(size_t size)
132 {
133 std::unique_lock<std::mutex> lg(m_mutex);
134 void* ptr = nullptr;
135 auto x = m_free_memory_map.find(size);
136 if (x != m_free_memory_map.end()) {
137 ptr = x->second;
138 m_free_memory_map.erase(x);
139 }
140 return ptr;
141 }
142
143 void addPointer(void* ptr, size_t size)
144 {
145 std::unique_lock<std::mutex> lg(m_mutex);
146 m_free_memory_map.insert(std::make_pair(size, ptr));
147 }
148
149 size_t size() const
150 {
151 std::unique_lock<std::mutex> lg(m_mutex);
152 return m_free_memory_map.size();
153 }
154
155 void dump(std::ostream& ostr)
156 {
157 std::map<size_t, Int32> nb_alloc_per_size;
158 {
159 std::unique_lock<std::mutex> lg(m_mutex);
160 for (const auto& [key, value] : m_free_memory_map) {
161 auto x = nb_alloc_per_size.find(key);
162 if (x == nb_alloc_per_size.end())
163 nb_alloc_per_size.insert(std::make_pair(key, 1));
164 else
165 ++x->second;
166 }
167 }
168 ostr << "FreedMap '" << m_name << "\n";
169 for (const auto& [key, value] : nb_alloc_per_size)
170 ostr << "Map size=" << key << " nb_allocated=" << value << " page_modulo=" << (key % 4096) << "\n";
171 }
172
174 void fillFreeMapAndClear(Array<std::pair<void*, size_t>>& values)
175 {
176 std::unique_lock<std::mutex> lg(m_mutex);
177 values.reserve(m_free_memory_map.size());
178 for (const auto& [size, ptr] : m_free_memory_map)
179 values.add(std::make_pair(ptr, size));
180 m_free_memory_map.clear();
181 }
182
183 private:
184
185 MapType m_free_memory_map;
186 String m_name;
187 mutable std::mutex m_mutex;
188 };
189
190 public:
191
192 explicit Impl(IMemoryPoolAllocator* allocator, const String& name)
193 : m_allocator(allocator)
194 , m_allocated_map(name)
195 , m_free_map(name)
196 , m_name(name)
197 {
198 if (auto v = Convert::Type<Int32>::tryParseFromEnvironment("ARCANE_MEMORY_POOL_THROW_ON_ERROR", true)) {
199 bool throw_on_error = (v.value() != 0);
200 m_allocated_map.setIsThrowOnError(throw_on_error);
201 }
202 }
203 ~Impl()
204 {
205 // Does not free memory in m_free_map
206 // because this destructor can be called at the end of execution
207 // and on an accelerator, functions of the runtime cannot be called
208 // at that moment.
209 }
210
211 public:
212
213 void* allocateMemory(size_t size);
214 void freeMemory(void* ptr, size_t size);
215 void dumpStats(std::ostream& ostr);
216 void dumpFreeMap(std::ostream& ostr);
217 void setMaxCachedBlockSize(Int32 v);
218 void freeCachedMemory();
219
220 public:
221
222 IMemoryPoolAllocator* m_allocator = nullptr;
223 // Contains a list of pairs (memory_size, pointer) of allocated memory.
224 AllocatedMap m_allocated_map;
227 std::atomic<Int64> m_total_allocated = 0;
228 std::atomic<Int64> m_total_free = 0;
229 std::atomic<Int32> m_nb_cached = 0;
230 std::atomic<Int32> m_nb_no_cached = 0;
231 size_t m_max_memory_size_to_pool = 1024 * 64 * 4 * 4;
232 String m_name;
233
234 private:
235
236 void _freeMemory(void* ptr);
237 void _addAllocated(void* ptr, size_t size);
238};
239
240/*---------------------------------------------------------------------------*/
241/*---------------------------------------------------------------------------*/
242
243void* MemoryPool::Impl::
244allocateMemory(size_t size)
245{
246 if (m_max_memory_size_to_pool != 0 && size > m_max_memory_size_to_pool) {
247 ++m_nb_no_cached;
248 return m_allocator->allocateMemory(size);
249 }
250
251 void* ptr = m_free_map.getPointer(size);
252 if (ptr) {
253 m_total_free -= size;
254 ++m_nb_cached;
255 }
256 else {
257 ptr = m_allocator->allocateMemory(size);
258 }
259 _addAllocated(ptr, size);
260 return ptr;
261}
262
263/*---------------------------------------------------------------------------*/
264/*---------------------------------------------------------------------------*/
265
266void MemoryPool::Impl::
267freeMemory(void* ptr, size_t size)
268{
269 if (m_max_memory_size_to_pool != 0 && size > m_max_memory_size_to_pool)
270 return m_allocator->freeMemory(ptr, size);
271
272 m_allocated_map.removePointer(ptr, size);
273
274 m_free_map.addPointer(ptr, size);
275 m_total_allocated -= size;
276 m_total_free += size;
277}
278
279/*---------------------------------------------------------------------------*/
280/*---------------------------------------------------------------------------*/
281
282void MemoryPool::Impl::
283_addAllocated(void* ptr, size_t size)
284{
285 m_allocated_map.addPointer(ptr, size);
286 m_total_allocated += size;
287}
288
289/*---------------------------------------------------------------------------*/
290/*---------------------------------------------------------------------------*/
291
292void MemoryPool::Impl::
293dumpStats(std::ostream& ostr)
294{
295 ostr << "Stats '" << m_name << "' max_block=" << m_max_memory_size_to_pool
296 << " TotalAllocated=" << m_total_allocated
297 << " TotalFree=" << m_total_free
298 << " nb_allocated=" << m_allocated_map.size()
299 << " nb_free=" << m_free_map.size()
300 << " nb_cached=" << m_nb_cached
301 << " nb_no_cached=" << m_nb_no_cached
302 << " nb_error=" << m_allocated_map.nbError()
303 << "\n";
304}
305
306/*---------------------------------------------------------------------------*/
307/*---------------------------------------------------------------------------*/
308
309void MemoryPool::Impl::
310dumpFreeMap(std::ostream& ostr)
311{
312 m_free_map.dump(ostr);
313}
314
315/*---------------------------------------------------------------------------*/
316/*---------------------------------------------------------------------------*/
317
318void MemoryPool::Impl::
319setMaxCachedBlockSize(Int32 v)
320{
321 if (m_allocated_map.size() != 0 || m_free_map.size() != 0)
322 ARCCORE_FATAL("Can not change maximum cached block size on non empty pool");
323 if (v < 0)
324 v = 0;
325 m_max_memory_size_to_pool = v;
326}
327
328/*---------------------------------------------------------------------------*/
329/*---------------------------------------------------------------------------*/
330
331void MemoryPool::Impl::
332freeCachedMemory()
333{
334 UniqueArray<std::pair<void*, size_t>> values_to_free;
335 m_free_map.fillFreeMapAndClear(values_to_free);
336 for (const auto& v : values_to_free) {
337 m_allocator->freeMemory(v.first, v.second);
338 }
339}
340
341/*---------------------------------------------------------------------------*/
342/*---------------------------------------------------------------------------*/
343
344/*---------------------------------------------------------------------------*/
345/*---------------------------------------------------------------------------*/
346
347MemoryPool::
348MemoryPool(IMemoryPoolAllocator* allocator, const String& name)
349: m_p(std::make_unique<Impl>(allocator, name))
350{
351}
352
353/*---------------------------------------------------------------------------*/
354/*---------------------------------------------------------------------------*/
355
356MemoryPool::
357~MemoryPool()
358{
359}
360
361/*---------------------------------------------------------------------------*/
362/*---------------------------------------------------------------------------*/
363
365{
366 return m_p->allocateMemory(size);
367}
368void MemoryPool::freeMemory(void* ptr, Int64 size)
369{
370 m_p->freeMemory(ptr, size);
371}
372void MemoryPool::dumpStats(std::ostream& ostr)
373{
374 m_p->dumpStats(ostr);
375}
376void MemoryPool::
377dumpFreeMap(std::ostream& ostr)
378{
379 m_p->dumpFreeMap(ostr);
380}
381String MemoryPool::name() const
382{
383 return m_p->m_name;
384}
387{
388 m_p->setMaxCachedBlockSize(v);
389}
392{
393 m_p->freeCachedMemory();
394}
396totalAllocated() const
397{
398 return m_p->m_total_allocated;
399}
401totalCached() const
402{
403 return m_p->m_total_free;
404}
405
406/*---------------------------------------------------------------------------*/
407/*---------------------------------------------------------------------------*/
408
409} // namespace Arcane::Impl
410
411/*---------------------------------------------------------------------------*/
412/*---------------------------------------------------------------------------*/
#define ARCCORE_FATAL(...)
Macro throwing a FatalErrorException.
#define ARCCORE_FATAL_IF(cond,...)
Macro throwing a FatalErrorException if cond is true.
Base class for 1D data vectors.
Template class for converting a type.
Interface for an allocator for a MemoryPool.
Definition MemoryPool.h:40
virtual void * allocateMemory(Int64 size)=0
Allocates a block for size bytes.
Associative array of free memory locations by size.
void fillFreeMapAndClear(Array< std::pair< void *, size_t > > &values)
Fills values with the values from m_free_memory_map and clears the latter.
void * getPointer(size_t size)
Retrieves a pointer for a size size.
FreedMap m_free_map
List of free allocations in the cache.
void setMaxCachedBlockSize(Int32 v) override
Implementation of IMemoryPool.
void freeMemory(void *ptr, Int64 size) override
Frees the block located at address address containing size bytes.
void * allocateMemory(Int64 size) override
Allocates a block for size bytes.
Int64 totalAllocated() const override
Total size (in bytes) allocated in the memory pool.
void freeCachedMemory() override
Frees the memory in the cache.
Int64 totalCached() const override
Total size (in bytes) in the cache.
std::int64_t Int64
Signed integer type of 64 bits.
std::int32_t Int32
Signed integer type of 32 bits.