Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2021 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 : #include "allocator.h"
23 :
24 : #include <algorithm>
25 : #include <cstdlib>
26 : #include <iostream>
27 : #include <set>
28 : #include <sstream>
29 :
30 : #include "global/stringutils.h"
31 : #include "log.h"
32 :
33 : using namespace muse;
34 :
35 : int ObjectAllocator::s_used = 0;
36 : size_t ObjectAllocator::DEFAULT_BLOCK_SIZE(1024 * 256); // 256 kB
37 :
38 13 : static inline size_t align(size_t n)
39 : {
40 13 : return (n + sizeof(intptr_t) - 1) & ~(sizeof(intptr_t) - 1);
41 : }
42 :
43 : // ============================================
44 : // ObjectAllocator
45 : // ============================================
46 0 : void ObjectAllocator::used()
47 : {
48 : #ifdef MUSE_ENABLE_CUSTOM_ALLOCATOR
49 : s_used++;
50 : #endif
51 0 : }
52 :
53 0 : void ObjectAllocator::unused()
54 : {
55 : #ifdef MUSE_ENABLE_CUSTOM_ALLOCATOR
56 : s_used--;
57 : #endif
58 0 : }
59 :
60 3 : ObjectAllocator::ObjectAllocator(const char* module, const char* name, destroyer_t dtor)
61 3 : : m_module(module), m_name(name), m_dtor(dtor)
62 : {
63 3 : AllocatorsRegister::instance()->reg(this);
64 3 : }
65 :
66 3 : ObjectAllocator::~ObjectAllocator()
67 : {
68 3 : AllocatorsRegister::instance()->unreg(this);
69 3 : }
70 :
71 0 : const char* ObjectAllocator::module() const
72 : {
73 0 : return m_module;
74 : }
75 :
76 0 : const char* ObjectAllocator::name() const
77 : {
78 0 : return m_name;
79 : }
80 :
81 13 : void* ObjectAllocator::alloc(size_t size)
82 : {
83 13 : size = align(size);
84 :
85 13 : if (!m_chunkSize) {
86 3 : m_chunkSize = size;
87 : }
88 :
89 13 : assert(m_chunkSize == size);
90 :
91 13 : if (!m_free) {
92 5 : Block b = allocateBlock(m_chunkSize);
93 5 : m_blocks.push_back(b);
94 5 : m_free = b.begin;
95 : }
96 :
97 : // The return value is the current position of
98 : // the allocation pointer:
99 13 : Chunk* freeChunk = m_free;
100 :
101 : // Advance (bump) the allocation pointer to the next chunk.
102 : //
103 : // When no chunks left, the `m_free` will be set to `nullptr`, and
104 : // this will cause allocation of a new block on the next request:
105 13 : m_free = m_free->next;
106 :
107 13 : m_statistic.totalAllocatedCount++;
108 :
109 13 : return freeChunk;
110 : }
111 :
112 2 : void ObjectAllocator::free(void* chunk)
113 : {
114 : // The freed chunk's next pointer points to the
115 : // current allocation pointer:
116 2 : reinterpret_cast<Chunk*>(chunk)->next = m_free;
117 :
118 : // And the allocation pointer is now set
119 : // to the returned (free) chunk:
120 2 : m_free = reinterpret_cast<Chunk*>(chunk);
121 :
122 2 : m_statistic.totalFreeCount++;
123 2 : }
124 :
125 2 : void ObjectAllocator::cleanup()
126 : {
127 2 : if (m_blocks.empty()) {
128 0 : return;
129 : }
130 :
131 2 : std::set<Chunk*> freeChunks;
132 : {
133 2 : Chunk* free = m_free;
134 4684 : while (free) {
135 4682 : freeChunks.insert(free);
136 4682 : free = free->next;
137 : }
138 : }
139 :
140 6 : for (size_t bi = 0; bi < m_blocks.size(); ++bi) {
141 4 : const Block& b = m_blocks.at(bi);
142 4 : Chunk* chunk = b.begin;
143 4693 : for (size_t i = 0; i < b.chunkCount - 1; ++i) {
144 : // if not free chunk, then destroy object
145 4689 : if (freeChunks.find(chunk) == freeChunks.cend()) {
146 9 : m_dtor(reinterpret_cast<void*>(chunk));
147 : }
148 :
149 4689 : chunk->next = reinterpret_cast<Chunk*>(reinterpret_cast<uint8_t*>(chunk) + b.chunkSize);
150 4689 : chunk = chunk->next;
151 : }
152 :
153 4 : if (freeChunks.find(chunk) == freeChunks.cend()) {
154 2 : m_dtor(reinterpret_cast<void*>(chunk));
155 : }
156 :
157 4 : if (bi < (m_blocks.size() - 1)) {
158 2 : chunk->next = m_blocks.at(bi + 1).begin;
159 : } else {
160 2 : chunk->next = nullptr;
161 : }
162 : }
163 :
164 2 : m_free = m_blocks.front().begin;
165 2 : }
166 :
167 5 : ObjectAllocator::Block ObjectAllocator::allocateBlock(size_t chunkSize) const
168 : {
169 5 : size_t blockSize = std::max(DEFAULT_BLOCK_SIZE, chunkSize);
170 5 : size_t chunkCount = blockSize / chunkSize;
171 :
172 5 : Block b;
173 5 : b.begin = reinterpret_cast<Chunk*>(malloc(blockSize));
174 5 : b.chunkCount = chunkCount;
175 5 : b.chunkSize = chunkSize;
176 :
177 : // Once the block is allocated, we need to chain all
178 : // the chunks in this block:
179 :
180 5 : Chunk* chunk = b.begin;
181 :
182 4694 : for (size_t i = 0; i < chunkCount - 1; ++i) {
183 4689 : chunk->next = reinterpret_cast<Chunk*>(reinterpret_cast<uint8_t*>(chunk) + chunkSize);
184 4689 : chunk = chunk->next;
185 : }
186 :
187 5 : chunk->next = nullptr;
188 :
189 5 : return b;
190 : }
191 :
192 0 : void* ObjectAllocator::not_supported(const char* info)
193 : {
194 0 : LOGE() << m_name << ": " << info << " not supported";
195 0 : std::abort();
196 : //return nullptr; // NOTREACHED
197 : }
198 :
199 6 : ObjectAllocator::Info ObjectAllocator::stateInfo() const
200 : {
201 6 : Info info;
202 6 : info.module = m_module;
203 6 : info.name = m_name;
204 6 : info.chunkSize = m_chunkSize;
205 6 : info.blockCount = m_blocks.size();
206 6 : info.totalAllocatedCount = m_statistic.totalAllocatedCount;
207 6 : info.totalFreeCount = m_statistic.totalFreeCount;
208 :
209 16 : for (const Block& b : m_blocks) {
210 10 : info.totalChunks += b.chunkCount;
211 : }
212 :
213 6 : Chunk* free = m_free;
214 9382 : while (free) {
215 9376 : ++info.freeChunks;
216 9376 : free = free->next;
217 : }
218 :
219 6 : return info;
220 0 : }
221 :
222 : // ============================================
223 : // AllocatorsRegister
224 : // ============================================
225 3 : void AllocatorsRegister::reg(ObjectAllocator* a)
226 : {
227 3 : m_allocators.push_back(a);
228 3 : }
229 :
230 3 : void AllocatorsRegister::unreg(ObjectAllocator* a)
231 : {
232 3 : m_allocators.remove(a);
233 3 : }
234 :
235 0 : void AllocatorsRegister::cleanupAll(const std::string& module)
236 : {
237 0 : for (ObjectAllocator* a : m_allocators) {
238 0 : if (a->module() == module) {
239 0 : a->cleanup();
240 : }
241 : }
242 0 : }
243 :
244 : #define FORMAT(str, width) muse::strings::leftJustified(str, width)
245 : #define TITLE(str) FORMAT(std::string(str), 20)
246 : #define VALUE(val) FORMAT(std::to_string(val), 20)
247 :
248 0 : void AllocatorsRegister::printStatistic(const std::string& title)
249 : {
250 0 : std::stringstream stream;
251 0 : stream << "\n\n";
252 0 : stream << title << "\n";
253 0 : stream << "allocators: " << m_allocators.size() << '\n';
254 0 : stream << TITLE("Object") << TITLE("Total alloc") << TITLE("Total free") << TITLE("Used (leak?)") << TITLE("Object size") << "\n";
255 :
256 0 : uint64_t totalBytes = 0;
257 0 : uint64_t totalAllocatedCount = 0;
258 0 : uint64_t totalFreeCount = 0;
259 0 : uint64_t totalUsedCount = 0;
260 0 : for (ObjectAllocator* a : m_allocators) {
261 0 : ObjectAllocator::Info info = a->stateInfo();
262 0 : stream << FORMAT(info.name, 20)
263 0 : << VALUE(info.totalAllocatedCount)
264 0 : << VALUE(info.totalFreeCount)
265 0 : << VALUE(info.usedChunks())
266 0 : << VALUE(info.chunkSize)
267 0 : << "\n";
268 :
269 0 : totalAllocatedCount += info.totalAllocatedCount;
270 0 : totalFreeCount += info.totalFreeCount;
271 0 : totalUsedCount += info.usedChunks();
272 0 : totalBytes += info.allocatedBytes();
273 0 : }
274 :
275 0 : stream << "--------------------------------------------------------------------------------------------\n";
276 0 : stream << FORMAT("Total", 20) << VALUE(totalAllocatedCount) << VALUE(totalFreeCount) << VALUE(totalUsedCount) << "\n";
277 0 : stream << "Total allocated: " << totalBytes << " bytes\n";
278 :
279 0 : LOGD() << stream.str() << '\n';
280 0 : }
281 :
282 0 : void AllocatorsRegister::printState(const std::string& title)
283 : {
284 0 : std::stringstream stream;
285 0 : stream << "\n\n";
286 0 : stream << title << "\n";
287 0 : stream << "allocators: " << m_allocators.size() << '\n';
288 0 : stream << TITLE("Object") << TITLE("blockCount") << TITLE("totalChunks") << TITLE("freeChunks") << TITLE("chunkSize")
289 0 : << TITLE("allocatedBytes") << "\n";
290 :
291 0 : uint64_t totalBytes = 0;
292 0 : for (ObjectAllocator* a : m_allocators) {
293 0 : ObjectAllocator::Info info = a->stateInfo();
294 0 : stream << FORMAT(info.name, 20)
295 0 : << VALUE(info.blockCount)
296 0 : << VALUE(info.totalChunks)
297 0 : << VALUE(info.freeChunks)
298 0 : << VALUE(info.chunkSize)
299 0 : << VALUE(info.allocatedBytes())
300 0 : << "\n";
301 :
302 0 : totalBytes += info.allocatedBytes();
303 0 : }
304 :
305 0 : stream << "-----------------------------------------------------\n";
306 0 : stream << "Total allocated: " << totalBytes << " bytes\n";
307 :
308 0 : LOGD() << stream.str() << '\n';
309 0 : }
|