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