• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright © 2020, VideoLAN and dav1d authors
3  * Copyright © 2020, Two Orioles, LLC
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright notice, this
10  *    list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright notice,
13  *    this list of conditions and the following disclaimer in the documentation
14  *    and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include "config.h"
29 
30 #include <stdint.h>
31 
32 #include "src/internal.h"
33 
34 #if TRACK_HEAP_ALLOCATIONS
35 #include <stdio.h>
36 
37 #include "src/log.h"
38 
39 #define DEFAULT_ALIGN 16
40 
41 typedef struct {
42     size_t sz;
43     unsigned align;
44     enum AllocationType type;
45 } Dav1dAllocationData;
46 
47 typedef struct {
48     size_t curr_sz;
49     size_t peak_sz;
50     unsigned num_allocs;
51     unsigned num_reuses;
52 } AllocStats;
53 
54 static AllocStats tracked_allocs[N_ALLOC_TYPES];
55 static size_t curr_total_sz;
56 static size_t peak_total_sz;
57 static pthread_mutex_t track_alloc_mutex = PTHREAD_MUTEX_INITIALIZER;
58 
track_alloc(const enum AllocationType type,char * ptr,const size_t sz,const size_t align)59 static void *track_alloc(const enum AllocationType type, char *ptr,
60                          const size_t sz, const size_t align)
61 {
62     assert(align >= sizeof(Dav1dAllocationData));
63     if (ptr) {
64         ptr += align;
65         Dav1dAllocationData *const d = &((Dav1dAllocationData*)ptr)[-1];
66         AllocStats *const s = &tracked_allocs[type];
67 
68         d->sz = sz;
69         d->align = (unsigned)align;
70         d->type = type;
71 
72         pthread_mutex_lock(&track_alloc_mutex);
73         s->num_allocs++;
74         s->curr_sz += sz;
75         if (s->curr_sz > s->peak_sz)
76             s->peak_sz = s->curr_sz;
77 
78         curr_total_sz += sz;
79         if (curr_total_sz > peak_total_sz)
80             peak_total_sz = curr_total_sz;
81         pthread_mutex_unlock(&track_alloc_mutex);
82     }
83     return ptr;
84 }
85 
track_free(char * const ptr)86 static void *track_free(char *const ptr) {
87     const Dav1dAllocationData *const d = &((Dav1dAllocationData*)ptr)[-1];
88     const size_t sz = d->sz;
89 
90     pthread_mutex_lock(&track_alloc_mutex);
91     tracked_allocs[d->type].curr_sz -= sz;
92     curr_total_sz -= sz;
93     pthread_mutex_unlock(&track_alloc_mutex);
94 
95     return ptr - d->align;
96 }
97 
dav1d_track_reuse(const enum AllocationType type)98 static void dav1d_track_reuse(const enum AllocationType type) {
99     pthread_mutex_lock(&track_alloc_mutex);
100     tracked_allocs[type].num_reuses++;
101     pthread_mutex_unlock(&track_alloc_mutex);
102 }
103 
dav1d_malloc(const enum AllocationType type,const size_t sz)104 void *dav1d_malloc(const enum AllocationType type, const size_t sz) {
105     void *const ptr = malloc(sz + DEFAULT_ALIGN);
106     return track_alloc(type, ptr, sz, DEFAULT_ALIGN);
107 }
108 
dav1d_alloc_aligned(const enum AllocationType type,const size_t sz,const size_t align)109 void *dav1d_alloc_aligned(const enum AllocationType type,
110                           const size_t sz, const size_t align)
111 {
112     assert(!(align & (align - 1)));
113     void *ptr;
114 #ifdef _WIN32
115     ptr = _aligned_malloc(sz + align, align);
116 #elif defined(HAVE_POSIX_MEMALIGN)
117     if (posix_memalign(&ptr, align, sz + align)) return NULL;
118 #else
119     ptr = memalign(align, sz + align);
120 #endif
121 
122     return track_alloc(type, ptr, sz, align);
123 }
124 
dav1d_realloc(const enum AllocationType type,void * ptr,const size_t sz)125 void *dav1d_realloc(const enum AllocationType type,
126                     void *ptr, const size_t sz)
127 {
128     if (!ptr)
129         return dav1d_malloc(type, sz);
130     ptr = realloc((char*)ptr - DEFAULT_ALIGN, sz + DEFAULT_ALIGN);
131     if (ptr)
132         ptr = track_free((char*)ptr + DEFAULT_ALIGN);
133     return track_alloc(type, ptr, sz, DEFAULT_ALIGN);
134 }
135 
dav1d_free(void * ptr)136 void dav1d_free(void *ptr) {
137     if (ptr)
138         free(track_free(ptr));
139 }
140 
dav1d_free_aligned(void * ptr)141 void dav1d_free_aligned(void *ptr) {
142     if (ptr) {
143         ptr = track_free(ptr);
144 #ifdef _WIN32
145         _aligned_free(ptr);
146 #else
147         free(ptr);
148 #endif
149     }
150 }
151 
cmp_stats(const void * const a,const void * const b)152 static COLD int cmp_stats(const void *const a, const void *const b) {
153     const size_t a_sz = ((const AllocStats*)a)->peak_sz;
154     const size_t b_sz = ((const AllocStats*)b)->peak_sz;
155     return a_sz < b_sz ? -1 : a_sz > b_sz;
156 }
157 
158 /* Insert spaces as thousands separators for better readability */
format_tsep(char * const s,const size_t n,const size_t value)159 static COLD int format_tsep(char *const s, const size_t n, const size_t value) {
160     if (value < 1000)
161         return snprintf(s, n, "%u", (unsigned)value);
162 
163     const int len = format_tsep(s, n, value / 1000);
164     assert((size_t)len < n);
165     return len + snprintf(s + len, n - len, " %03u", (unsigned)(value % 1000));
166 }
167 
dav1d_log_alloc_stats(Dav1dContext * const c)168 COLD void dav1d_log_alloc_stats(Dav1dContext *const c) {
169     static const char *const type_names[N_ALLOC_TYPES] = {
170         [ALLOC_BLOCK     ] = "Block data",
171         [ALLOC_CDEF      ] = "CDEF line buffers",
172         [ALLOC_CDF       ] = "CDF contexts",
173         [ALLOC_COEF      ] = "Coefficient data",
174         [ALLOC_COMMON_CTX] = "Common context data",
175         [ALLOC_DAV1DDATA ] = "Dav1dData",
176         [ALLOC_IPRED     ] = "Intra pred edges",
177         [ALLOC_LF        ] = "Loopfilter data",
178         [ALLOC_LR        ] = "Looprestoration data",
179         [ALLOC_OBU_HDR   ] = "OBU headers",
180         [ALLOC_OBU_META  ] = "OBU metadata",
181         [ALLOC_PAL       ] = "Palette data",
182         [ALLOC_PIC       ] = "Picture buffers",
183         [ALLOC_PIC_CTX   ] = "Picture context data",
184         [ALLOC_REFMVS    ] = "Reference mv data",
185         [ALLOC_SEGMAP    ] = "Segmentation maps",
186         [ALLOC_THREAD_CTX] = "Thread context data",
187         [ALLOC_TILE      ] = "Tile data",
188     };
189 
190     struct {
191         AllocStats stats;
192         enum AllocationType type;
193     } data[N_ALLOC_TYPES];
194     unsigned total_allocs = 0;
195     unsigned total_reuses = 0;
196 
197     pthread_mutex_lock(&track_alloc_mutex);
198     for (int i = 0; i < N_ALLOC_TYPES; i++) {
199         AllocStats *const s = &data[i].stats;
200         *s = tracked_allocs[i];
201         data[i].type = i;
202         total_allocs += s->num_allocs;
203         total_reuses += s->num_reuses;
204     }
205     size_t total_sz = peak_total_sz;
206     pthread_mutex_unlock(&track_alloc_mutex);
207 
208     /* Sort types by memory usage */
209     qsort(&data, N_ALLOC_TYPES, sizeof(*data), cmp_stats);
210 
211     const double inv_total_share = 100.0 / total_sz;
212     char total_sz_buf[32];
213     const int sz_len = 4 + format_tsep(total_sz_buf, sizeof(total_sz_buf), total_sz);
214 
215     dav1d_log(c, "\n Type                    Allocs    Reuses    Share    Peak size\n"
216                  "---------------------------------------------------------------------\n");
217     for (int i = N_ALLOC_TYPES - 1; i >= 0; i--) {
218         const AllocStats *const s = &data[i].stats;
219         if (s->num_allocs) {
220             const double share = s->peak_sz * inv_total_share;
221             char sz_buf[32];
222             format_tsep(sz_buf, sizeof(sz_buf), s->peak_sz);
223             dav1d_log(c, " %-20s%10u%10u%8.1f%%%*s\n", type_names[data[i].type],
224                       s->num_allocs, s->num_reuses, share, sz_len, sz_buf);
225         }
226     }
227     dav1d_log(c, "---------------------------------------------------------------------\n"
228                  "%31u%10u             %s\n",
229                  total_allocs, total_reuses, total_sz_buf);
230 }
231 #endif /* TRACK_HEAP_ALLOCATIONS */
232 
mem_pool_destroy(Dav1dMemPool * const pool)233 static COLD void mem_pool_destroy(Dav1dMemPool *const pool) {
234     pthread_mutex_destroy(&pool->lock);
235     dav1d_free(pool);
236 }
237 
dav1d_mem_pool_push(Dav1dMemPool * const pool,Dav1dMemPoolBuffer * const buf)238 void dav1d_mem_pool_push(Dav1dMemPool *const pool, Dav1dMemPoolBuffer *const buf) {
239     pthread_mutex_lock(&pool->lock);
240     const int ref_cnt = --pool->ref_cnt;
241     if (!pool->end) {
242         buf->next = pool->buf;
243         pool->buf = buf;
244         pthread_mutex_unlock(&pool->lock);
245         assert(ref_cnt > 0);
246     } else {
247         pthread_mutex_unlock(&pool->lock);
248         dav1d_free_aligned(buf->data);
249         if (!ref_cnt) mem_pool_destroy(pool);
250     }
251 }
252 
dav1d_mem_pool_pop(Dav1dMemPool * const pool,const size_t size)253 Dav1dMemPoolBuffer *dav1d_mem_pool_pop(Dav1dMemPool *const pool, const size_t size) {
254     assert(!(size & (sizeof(void*) - 1)));
255     pthread_mutex_lock(&pool->lock);
256     Dav1dMemPoolBuffer *buf = pool->buf;
257     pool->ref_cnt++;
258     uint8_t *data;
259     if (buf) {
260         pool->buf = buf->next;
261         pthread_mutex_unlock(&pool->lock);
262         data = buf->data;
263         if ((uintptr_t)buf - (uintptr_t)data != size) {
264             /* Reallocate if the size has changed */
265             dav1d_free_aligned(data);
266             goto alloc;
267         }
268 #if TRACK_HEAP_ALLOCATIONS
269         dav1d_track_reuse(pool->type);
270 #endif
271     } else {
272         pthread_mutex_unlock(&pool->lock);
273 alloc:
274         data = dav1d_alloc_aligned(pool->type,
275                                    size + sizeof(Dav1dMemPoolBuffer), 64);
276         if (!data) {
277             pthread_mutex_lock(&pool->lock);
278             const int ref_cnt = --pool->ref_cnt;
279             pthread_mutex_unlock(&pool->lock);
280             if (!ref_cnt) mem_pool_destroy(pool);
281             return NULL;
282         }
283         buf = (Dav1dMemPoolBuffer*)(data + size);
284         buf->data = data;
285     }
286 
287     return buf;
288 }
289 
dav1d_mem_pool_init(const enum AllocationType type,Dav1dMemPool ** const ppool)290 COLD int dav1d_mem_pool_init(const enum AllocationType type,
291                              Dav1dMemPool **const ppool)
292 {
293     Dav1dMemPool *const pool = dav1d_malloc(ALLOC_COMMON_CTX,
294                                             sizeof(Dav1dMemPool));
295     if (pool) {
296         if (!pthread_mutex_init(&pool->lock, NULL)) {
297             pool->buf = NULL;
298             pool->ref_cnt = 1;
299             pool->end = 0;
300 #if TRACK_HEAP_ALLOCATIONS
301             pool->type = type;
302 #endif
303             *ppool = pool;
304             return 0;
305         }
306         dav1d_free(pool);
307     }
308     *ppool = NULL;
309     return DAV1D_ERR(ENOMEM);
310 }
311 
dav1d_mem_pool_end(Dav1dMemPool * const pool)312 COLD void dav1d_mem_pool_end(Dav1dMemPool *const pool) {
313     if (pool) {
314         pthread_mutex_lock(&pool->lock);
315         Dav1dMemPoolBuffer *buf = pool->buf;
316         const int ref_cnt = --pool->ref_cnt;
317         pool->buf = NULL;
318         pool->end = 1;
319         pthread_mutex_unlock(&pool->lock);
320 
321         while (buf) {
322             void *const data = buf->data;
323             buf = buf->next;
324             dav1d_free_aligned(data);
325         }
326         if (!ref_cnt) mem_pool_destroy(pool);
327     }
328 }
329