Arcane  4.2.1.0
User 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{
39 //! Associative array of allocated pointers and associated size
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
111 //! Associative array of free memory locations by size
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
126 /*!
127 * \brief Retrieves a pointer for a size \a size.
128 *
129 * Returns nullptr if there is no value in the cache
130 * for this size. Otherwise, the returned pointer is removed from the cache.
131 */
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
174 //! Fills \a values with the values from \a m_free_memory_map and clears the latter
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;
226 //! List of free allocations in the cache
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
365void* MemoryPool::allocateMemory(Int64 size)
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}
386void MemoryPool::
387setMaxCachedBlockSize(Int32 v)
388{
389 m_p->setMaxCachedBlockSize(v);
390}
391void MemoryPool::
392freeCachedMemory()
393{
394 m_p->freeCachedMemory();
395}
396Int64 MemoryPool::
397totalAllocated() const
398{
399 return m_p->m_total_allocated;
400}
401Int64 MemoryPool::
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.
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.
std::int64_t Int64
Signed integer type of 64 bits.
std::int32_t Int32
Signed integer type of 32 bits.