Blender V4.3
memory_usage.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include <algorithm>
6#include <atomic>
7#include <cassert>
8#include <iostream>
9#include <memory>
10#include <mutex>
11#include <vector>
12
13#include "MEM_guardedalloc.h"
14#include "mallocn_intern.hh"
15
16#include "../../source/blender/blenlib/BLI_strict_flags.h"
17
18namespace {
19
20struct Local;
21struct Global;
22
26struct alignas(128) Local {
30 std::shared_ptr<Global> global;
31
33 bool destructed = false;
39 bool is_main = false;
45 std::atomic<int64_t> mem_in_use = 0;
49 std::atomic<int64_t> blocks_num = 0;
56 std::atomic<int64_t> mem_in_use_during_peak_update = 0;
57
58 Local();
59 ~Local();
60};
61
66struct Global {
70 std::mutex locals_mutex;
75 std::vector<Local *> locals;
88 std::atomic<int64_t> mem_in_use_outside_locals = 0;
92 std::atomic<int64_t> blocks_num_outside_locals = 0;
96 std::atomic<size_t> peak = 0;
97};
98
99} // namespace
100
105static std::atomic<bool> use_local_counters = true;
111static constexpr int64_t peak_update_threshold = 1024 * 1024;
112
113static std::shared_ptr<Global> &get_global_ptr()
114{
115 static std::shared_ptr<Global> global = std::make_shared<Global>();
116 return global;
117}
118
120{
121 return *get_global_ptr();
122}
123
124static Local &get_local_data()
125{
126 static thread_local Local local;
127 assert(!local.destructed);
128 return local;
129}
130
131Local::Local()
132{
133 this->global = get_global_ptr();
134
135 std::lock_guard lock{this->global->locals_mutex};
136 if (this->global->locals.empty()) {
137 /* This is the first thread creating #Local, it is therefore the main thread because it's
138 * created through #memory_usage_init. */
139 this->is_main = true;
140 }
141 /* Register self in the global list. */
142 this->global->locals.push_back(this);
143}
144
145Local::~Local()
146{
147 std::lock_guard lock{this->global->locals_mutex};
148
149 /* Unregister self from the global list. */
150 this->global->locals.erase(
151 std::find(this->global->locals.begin(), this->global->locals.end(), this));
152 /* Don't forget the memory counts stored locally. */
153 this->global->blocks_num_outside_locals.fetch_add(this->blocks_num, std::memory_order_relaxed);
154 this->global->mem_in_use_outside_locals.fetch_add(this->mem_in_use, std::memory_order_relaxed);
155
156 if (this->is_main) {
157 /* The main thread started shutting down. Use global counters from now on to avoid accessing
158 * thread-locals after they have been destructed. */
159 use_local_counters.store(false, std::memory_order_relaxed);
160 }
161 /* Helps to detect when thread locals are accidentally accessed after destruction. */
162 this->destructed = true;
163}
164
167{
168 Global &global = get_global();
169 /* Update peak. */
170 global.peak = std::max<size_t>(global.peak, memory_usage_current());
171
172 std::lock_guard lock{global.locals_mutex};
173
174 for (Local *local : global.locals) {
175 assert(!local->destructed);
176 /* Updating this makes sure that the peak is not updated too often, which would degrade
177 * performance. */
178 local->mem_in_use_during_peak_update = local->mem_in_use.load(std::memory_order_relaxed);
179 }
180}
181
183{
184 /* Makes sure that the static and thread-local variables on the main thread are initialized. */
186}
187
188void memory_usage_block_alloc(const size_t size)
189{
190 if (LIKELY(use_local_counters.load(std::memory_order_relaxed))) {
191 Local &local = get_local_data();
192 /* Increase local memory counts. This does not cause thread synchronization in the majority of
193 * cases, because each thread has these counters on a separate cache line. It may only cause
194 * synchronization if another thread is computing the total current memory usage at the same
195 * time, which is very rare compared to doing allocations. */
196 local.blocks_num.fetch_add(1, std::memory_order_relaxed);
197 local.mem_in_use.fetch_add(int64_t(size), std::memory_order_relaxed);
198
199 /* If a certain amount of new memory has been allocated, update the peak. */
200 if (local.mem_in_use - local.mem_in_use_during_peak_update > peak_update_threshold) {
202 }
203 }
204 else {
205 Global &global = get_global();
206 /* Increase global memory counts. */
207 global.blocks_num_outside_locals.fetch_add(1, std::memory_order_relaxed);
208 global.mem_in_use_outside_locals.fetch_add(int64_t(size), std::memory_order_relaxed);
209 }
210}
211
212void memory_usage_block_free(const size_t size)
213{
215 /* Decrease local memory counts. See comment in #memory_usage_block_alloc for details regarding
216 * thread synchronization. */
217 Local &local = get_local_data();
218 local.mem_in_use.fetch_sub(int64_t(size), std::memory_order_relaxed);
219 local.blocks_num.fetch_sub(1, std::memory_order_relaxed);
220 }
221 else {
222 Global &global = get_global();
223 /* Decrease global memory counts. */
224 global.blocks_num_outside_locals.fetch_sub(1, std::memory_order_relaxed);
225 global.mem_in_use_outside_locals.fetch_sub(int64_t(size), std::memory_order_relaxed);
226 }
227}
228
230{
231 Global &global = get_global();
232 std::lock_guard lock{global.locals_mutex};
233
234 /* Count the number of active blocks. */
235 int64_t blocks_num = global.blocks_num_outside_locals;
236 for (const Local *local : global.locals) {
237 blocks_num += local->blocks_num;
238 }
239 return size_t(blocks_num);
240}
241
243{
244 Global &global = get_global();
245 std::lock_guard lock{global.locals_mutex};
246
247 /* Count the memory that's currently in use. */
248 int64_t mem_in_use = global.mem_in_use_outside_locals;
249 for (const Local *local : global.locals) {
250 mem_in_use += local->mem_in_use;
251 }
252 return size_t(mem_in_use);
253}
254
265{
267 Global &global = get_global();
268 return global.peak;
269}
270
272{
273 Global &global = get_global();
274 global.peak = memory_usage_current();
275}
#define LIKELY(x)
Read Guarded memory(de)allocation.
volatile int lock
static size_t mem_in_use
size_t memory_usage_current(void)
void memory_usage_block_alloc(size_t size)
size_t memory_usage_block_num(void)
void memory_usage_init(void)
void memory_usage_block_free(size_t size)
size_t memory_usage_peak(void)
void memory_usage_peak_reset(void)
static void update_global_peak()
static constexpr int64_t peak_update_threshold
static std::shared_ptr< Global > & get_global_ptr()
static std::atomic< bool > use_local_counters
static Global & get_global()
static Local & get_local_data()
__int64 int64_t
Definition stdint.h:89