/work/svt-av1/Source/Lib/Codec/svt_malloc.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright(c) 2019 Intel Corporation |
3 | | * |
4 | | * This source code is subject to the terms of the BSD 2 Clause License and |
5 | | * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License |
6 | | * was not distributed with this source code in the LICENSE file, you can |
7 | | * obtain it at https://www.aomedia.org/license/software-license. If the Alliance for Open |
8 | | * Media Patent License 1.0 was not distributed with this source code in the |
9 | | * PATENTS file, you can obtain it at https://www.aomedia.org/license/patent-license. |
10 | | */ |
11 | | #include "svt_malloc.h" |
12 | | |
13 | 0 | #define LOG_TAG "SvtMalloc" |
14 | | #include "svt_log.h" |
15 | | |
16 | 0 | void svt_print_alloc_fail_impl(const char* file, int line) { |
17 | 0 | SVT_FATAL("allocate memory failed, at %s:%d\n", file, line); |
18 | 0 | } |
19 | | |
20 | | #ifdef DEBUG_MEMORY_USAGE |
21 | | #include <stdint.h> |
22 | | #include <limits.h> |
23 | | #include "definitions.h" |
24 | | #include "svt_threads.h" |
25 | | |
26 | | static EbHandle g_malloc_mutex; |
27 | | |
28 | | static void malloc_mutex_cleanup(void) { |
29 | | svt_destroy_mutex(g_malloc_mutex); |
30 | | } |
31 | | |
32 | | static ONCE_ROUTINE(create_malloc_mutex) { |
33 | | g_malloc_mutex = svt_create_mutex(); |
34 | | atexit(malloc_mutex_cleanup); |
35 | | ONCE_ROUTINE_EPILOG; |
36 | | } |
37 | | |
38 | | DEFINE_ONCE(g_malloc_once); |
39 | | |
40 | | static EbHandle get_malloc_mutex() { |
41 | | svt_run_once(&g_malloc_once, create_malloc_mutex); |
42 | | return g_malloc_mutex; |
43 | | } |
44 | | |
45 | | // Simple hash function to speed up entry search |
46 | | // Takes the top half and bottom half of the pointer and adds them together. |
47 | | static inline uint32_t hash(const void* const p) { |
48 | | const uintptr_t bit_mask = ((uintptr_t)1 << sizeof(bit_mask) / 2 * CHAR_BIT) - 1, v = (uintptr_t)p; |
49 | | return (uint32_t)((v >> (sizeof(v) / 2 * CHAR_BIT)) + (v & bit_mask)); |
50 | | } |
51 | | |
52 | | typedef struct MemoryEntry { |
53 | | void* ptr; |
54 | | size_t count; |
55 | | const char* file; |
56 | | EbPtrType type; |
57 | | uint32_t line; |
58 | | } MemoryEntry; |
59 | | |
60 | | //+1 to get a better hash result |
61 | | #define MEM_ENTRY_SIZE (4 * 1024 * 1024 + 1) |
62 | | |
63 | | static MemoryEntry g_mem_entry[MEM_ENTRY_SIZE]; |
64 | | |
65 | | #define TO_INDEX(v) ((v) % MEM_ENTRY_SIZE) |
66 | | static bool g_add_mem_entry_warning = true; |
67 | | static bool g_remove_mem_entry_warning = true; |
68 | | |
69 | | /********************************************************************************* |
70 | | * |
71 | | * @brief |
72 | | * compare and update current memory entry. |
73 | | * |
74 | | * @param[in] e |
75 | | * current memory entry. |
76 | | * |
77 | | * @param[in] param |
78 | | * param you set to for_each_mem_entry |
79 | | * |
80 | | * |
81 | | * @returns return true if you want get early exit in for_each_mem_entry |
82 | | * |
83 | | s* |
84 | | ********************************************************************************/ |
85 | | |
86 | | typedef bool (*Predicate)(MemoryEntry* e, void* param); |
87 | | |
88 | | /********************************************************************************* |
89 | | * |
90 | | * @brief |
91 | | * Loop through mem entries. |
92 | | * |
93 | | * @param[in] bucket |
94 | | * the hash bucket |
95 | | * |
96 | | * @param[in] start |
97 | | * loop start position |
98 | | * |
99 | | * @param[in] pred |
100 | | * return true if you want early exit |
101 | | * |
102 | | * @param[out] param |
103 | | * param send to pred. |
104 | | * |
105 | | * @returns return true if we got early exit. |
106 | | * |
107 | | * |
108 | | ********************************************************************************/ |
109 | | static bool for_each_hash_entry(MemoryEntry* bucket, uint32_t start, Predicate pred, void* param) { |
110 | | const uint32_t s = TO_INDEX(start); |
111 | | uint32_t i = s; |
112 | | |
113 | | do { |
114 | | MemoryEntry* e = bucket + i; |
115 | | if (pred(e, param)) { |
116 | | return true; |
117 | | } |
118 | | i++; |
119 | | i = TO_INDEX(i); |
120 | | } while (i != s); |
121 | | return false; |
122 | | } |
123 | | |
124 | | static bool for_each_mem_entry(uint32_t start, Predicate pred, void* param) { |
125 | | bool ret; |
126 | | EbHandle m = get_malloc_mutex(); |
127 | | svt_block_on_mutex(m); |
128 | | ret = for_each_hash_entry(g_mem_entry, start, pred, param); |
129 | | svt_release_mutex(m); |
130 | | return ret; |
131 | | } |
132 | | |
133 | | static const char* mem_type_name(EbPtrType type) { |
134 | | static const char* name[EB_PTR_TYPE_TOTAL] = { |
135 | | "malloced memory", "calloced memory", "aligned memory", "mutex", "semaphore", "thread"}; |
136 | | return name[type]; |
137 | | } |
138 | | |
139 | | static bool add_mem_entry(MemoryEntry* e, void* param) { |
140 | | if (!e->ptr) { |
141 | | *e = *(MemoryEntry*)param; |
142 | | return true; |
143 | | } |
144 | | return false; |
145 | | } |
146 | | |
147 | | static bool remove_mem_entry(MemoryEntry* e, void* param) { |
148 | | MemoryEntry* item = param; |
149 | | if (e->ptr == item->ptr) { |
150 | | // The second case is a special case, we use EB_FREE to free calloced memory |
151 | | if (e->type == item->type || (e->type == EB_C_PTR && item->type == EB_N_PTR)) { |
152 | | e->ptr = NULL; |
153 | | return true; |
154 | | } |
155 | | } |
156 | | return false; |
157 | | } |
158 | | |
159 | | typedef struct MemSummary { |
160 | | size_t amount[EB_PTR_TYPE_TOTAL]; |
161 | | uint32_t occupied; |
162 | | } MemSummary; |
163 | | |
164 | | static bool count_mem_entry(MemoryEntry* e, void* param) { |
165 | | if (e->ptr) { |
166 | | MemSummary* sum = param; |
167 | | sum->amount[e->type] += e->count; |
168 | | sum->occupied++; |
169 | | } |
170 | | return false; |
171 | | } |
172 | | |
173 | | static inline void get_memory_usage_and_scale(size_t amount, double* const usage, char* const scale) { |
174 | | static const char scales[] = {' ', 'K', 'M', 'G', 'T'}; |
175 | | size_t i = 1; |
176 | | for (; i < sizeof(scales) && amount >= (size_t)1 << (++i * 10);) |
177 | | ; |
178 | | *scale = scales[--i]; |
179 | | *usage = (double)amount / (double)((size_t)1 << (i * 10)); |
180 | | } |
181 | | |
182 | | //this need more memory and cpu |
183 | | #define PROFILE_MEMORY_USAGE |
184 | | #ifdef PROFILE_MEMORY_USAGE |
185 | | |
186 | | //if we use a static array here, this size + sizeof(g_mem_entry) will exceed max size allowed on windows. |
187 | | static MemoryEntry* g_profile_entry; |
188 | | |
189 | | static bool add_location(MemoryEntry* e, void* param) { |
190 | | MemoryEntry* new_item = param; |
191 | | if (!e->ptr) { |
192 | | *e = *new_item; |
193 | | return true; |
194 | | } |
195 | | if (e->file == new_item->file && e->line == new_item->line) { |
196 | | e->count += new_item->count; |
197 | | return true; |
198 | | } |
199 | | // to next position. |
200 | | return false; |
201 | | } |
202 | | |
203 | | static bool collect_mem(MemoryEntry* e, void* param) { |
204 | | EbPtrType* type = param; |
205 | | if (e->ptr && e->type == *type) { |
206 | | for_each_hash_entry(g_profile_entry, 0, add_location, e); |
207 | | } |
208 | | //Loop entire bucket. |
209 | | return false; |
210 | | } |
211 | | |
212 | | static int compare_count(const void* a, const void* b) { |
213 | | const MemoryEntry* pa = a; |
214 | | const MemoryEntry* pb = b; |
215 | | return pb->count < pa->count ? -1 : pb->count != pa->count; |
216 | | } |
217 | | |
218 | | static void print_top_10_locations() { |
219 | | EbHandle m = get_malloc_mutex(); |
220 | | EbPtrType type = EB_N_PTR; |
221 | | svt_block_on_mutex(m); |
222 | | g_profile_entry = calloc(MEM_ENTRY_SIZE, sizeof(*g_profile_entry)); |
223 | | if (!g_profile_entry) { |
224 | | SVT_ERROR("not enough memory for memory profile"); |
225 | | svt_release_mutex(m); |
226 | | return; |
227 | | } |
228 | | |
229 | | for_each_hash_entry(g_mem_entry, 0, collect_mem, &type); |
230 | | qsort(g_profile_entry, MEM_ENTRY_SIZE, sizeof(*g_profile_entry), compare_count); |
231 | | |
232 | | SVT_INFO("top 10 %s locations:\n", mem_type_name(type)); |
233 | | for (int i = 0; i < 10; i++) { |
234 | | double usage; |
235 | | char scale; |
236 | | MemoryEntry* e = g_profile_entry + i; |
237 | | get_memory_usage_and_scale(e->count, &usage, &scale); |
238 | | SVT_INFO("(%.2lf %cB): %s:%d\n", usage, scale, e->file, e->line); |
239 | | } |
240 | | free(g_profile_entry); |
241 | | svt_release_mutex(m); |
242 | | } |
243 | | #endif //PROFILE_MEMORY_USAGE |
244 | | |
245 | | static int g_component_count; |
246 | | |
247 | | static bool print_leak(MemoryEntry* e, void* param) { |
248 | | if (e->ptr) { |
249 | | bool* leaked = param; |
250 | | *leaked = true; |
251 | | SVT_ERROR("%s leaked at %s:%d\n", mem_type_name(e->type), e->file, e->line); |
252 | | } |
253 | | //loop through all items |
254 | | return false; |
255 | | } |
256 | | |
257 | | void svt_print_memory_usage() { |
258 | | MemSummary sum; |
259 | | double usage; |
260 | | char scale; |
261 | | memset(&sum, 0, sizeof(sum)); |
262 | | |
263 | | for_each_mem_entry(0, count_mem_entry, &sum); |
264 | | SVT_INFO("SVT Memory Usage:\n"); |
265 | | get_memory_usage_and_scale(sum.amount[EB_N_PTR] + sum.amount[EB_C_PTR] + sum.amount[EB_A_PTR], &usage, &scale); |
266 | | SVT_INFO(" total allocated memory: %.2lf %cB\n", usage, scale); |
267 | | get_memory_usage_and_scale(sum.amount[EB_N_PTR], &usage, &scale); |
268 | | SVT_INFO(" malloced memory: %.2lf %cB\n", usage, scale); |
269 | | get_memory_usage_and_scale(sum.amount[EB_C_PTR], &usage, &scale); |
270 | | SVT_INFO(" callocated memory: %.2lf %cB\n", usage, scale); |
271 | | get_memory_usage_and_scale(sum.amount[EB_A_PTR], &usage, &scale); |
272 | | SVT_INFO(" allocated aligned memory: %.2lf %cB\n", usage, scale); |
273 | | |
274 | | SVT_INFO(" mutex count: %zu\n", sum.amount[EB_MUTEX]); |
275 | | SVT_INFO(" semaphore count: %zu\n", sum.amount[EB_SEMAPHORE]); |
276 | | SVT_INFO(" thread count: %zu\n", sum.amount[EB_THREAD]); |
277 | | SVT_INFO(" hash table fulless: %f, hash bucket is %s\n", |
278 | | (double)sum.occupied / MEM_ENTRY_SIZE, |
279 | | (double)sum.occupied / MEM_ENTRY_SIZE < .3 ? "healthy" : "too full"); |
280 | | #ifdef PROFILE_MEMORY_USAGE |
281 | | print_top_10_locations(); |
282 | | #endif |
283 | | } |
284 | | |
285 | | void svt_increase_component_count() { |
286 | | EbHandle m = get_malloc_mutex(); |
287 | | svt_block_on_mutex(m); |
288 | | g_component_count++; |
289 | | svt_release_mutex(m); |
290 | | } |
291 | | |
292 | | void svt_decrease_component_count() { |
293 | | EbHandle m = get_malloc_mutex(); |
294 | | svt_block_on_mutex(m); |
295 | | g_component_count--; |
296 | | if (!g_component_count) { |
297 | | bool leaked = false; |
298 | | for_each_hash_entry(g_mem_entry, 0, print_leak, &leaked); |
299 | | if (!leaked) { |
300 | | SVT_INFO("you have no memory leak\n"); |
301 | | } |
302 | | } |
303 | | svt_release_mutex(m); |
304 | | } |
305 | | |
306 | | void svt_add_mem_entry_impl(void* ptr, EbPtrType type, size_t count, const char* file, uint32_t line) { |
307 | | if (for_each_mem_entry(hash(ptr), |
308 | | add_mem_entry, |
309 | | &(MemoryEntry){.ptr = ptr, .type = type, .count = count, .file = file, .line = line})) { |
310 | | return; |
311 | | } |
312 | | if (g_add_mem_entry_warning) { |
313 | | SVT_ERROR( |
314 | | "can't add memory entry.\n" |
315 | | "You have memory leak or you need increase MEM_ENTRY_SIZE\n"); |
316 | | g_add_mem_entry_warning = false; |
317 | | } |
318 | | } |
319 | | |
320 | | void svt_remove_mem_entry(void* ptr, EbPtrType type) { |
321 | | if (!ptr) { |
322 | | return; |
323 | | } |
324 | | if (for_each_mem_entry(hash(ptr), remove_mem_entry, &(MemoryEntry){.ptr = ptr, .type = type})) { |
325 | | return; |
326 | | } |
327 | | if (g_remove_mem_entry_warning) { |
328 | | SVT_ERROR("something wrong. you freed a unallocated memory %p, type = %s\n", ptr, mem_type_name(type)); |
329 | | g_remove_mem_entry_warning = false; |
330 | | } |
331 | | } |
332 | | #endif |