summaryrefslogtreecommitdiffstats
path: root/libnetdata/onewayalloc/onewayalloc.c
blob: 2f007b1898077520456beab252d15619ebcde174 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
#include "onewayalloc.h"

// https://www.gnu.org/software/libc/manual/html_node/Aligned-Memory-Blocks.html
#define OWA_NATURAL_ALIGNMENT  (sizeof(uintptr_t) * 2)

typedef struct owa_page {
    size_t stats_pages;
    size_t stats_pages_size;
    size_t stats_mallocs_made;
    size_t stats_mallocs_size;
    size_t size;        // the total size of the page
    size_t offset;      // the first free byte of the page
    struct owa_page *next;     // the next page on the list
    struct owa_page *last;     // the last page on the list - we currently allocate on this
} OWA_PAGE;

static size_t onewayalloc_total_memory = 0;

size_t onewayalloc_allocated_memory(void) {
    return __atomic_load_n(&onewayalloc_total_memory, __ATOMIC_RELAXED);
}

// allocations need to be aligned to CPU register width
// https://en.wikipedia.org/wiki/Data_structure_alignment
static inline size_t natural_alignment(size_t size) {
    if(unlikely(size % OWA_NATURAL_ALIGNMENT))
        size = size + OWA_NATURAL_ALIGNMENT - (size % OWA_NATURAL_ALIGNMENT);

    return size;
}

// Create an OWA
// Once it is created, the called may call the onewayalloc_mallocz()
// any number of times, for any amount of memory.

static OWA_PAGE *onewayalloc_create_internal(OWA_PAGE *head, size_t size_hint) {
    static size_t OWA_NATURAL_PAGE_SIZE = 0;

    if(unlikely(!OWA_NATURAL_PAGE_SIZE)) {
        long int page_size = sysconf(_SC_PAGE_SIZE);
        if (unlikely(page_size == -1))
            OWA_NATURAL_PAGE_SIZE = 4096;
        else
            OWA_NATURAL_PAGE_SIZE = page_size;
    }

    // our default page size
    size_t size = OWA_NATURAL_PAGE_SIZE;

    // make sure the new page will fit both the requested size
    // and the OWA_PAGE structure at its beginning
    size_hint += natural_alignment(sizeof(OWA_PAGE));

    // prefer the user size if it is bigger than our size
    if(size_hint > size) size = size_hint;

    // try to allocate half of the total we have allocated already
    if(likely(head)) {
        size_t optimal_size = head->stats_pages_size / 2;
        if(optimal_size > size) size = optimal_size;
    }

    // Make sure our allocations are always a multiple of the hardware page size
    if(size % OWA_NATURAL_PAGE_SIZE) size = size + OWA_NATURAL_PAGE_SIZE - (size % OWA_NATURAL_PAGE_SIZE);

    // OWA_PAGE *page = (OWA_PAGE *)netdata_mmap(NULL, size, MAP_ANONYMOUS|MAP_PRIVATE, 0);
    // if(unlikely(!page)) fatal("Cannot allocate onewayalloc buffer of size %zu", size);
    OWA_PAGE *page = (OWA_PAGE *)mallocz(size);
    __atomic_add_fetch(&onewayalloc_total_memory, size, __ATOMIC_RELAXED);

    page->size = size;
    page->offset = natural_alignment(sizeof(OWA_PAGE));
    page->next = page->last = NULL;

    if(unlikely(!head)) {
        // this is the first time we are called
        head = page;
        head->stats_pages = 0;
        head->stats_pages_size = 0;
        head->stats_mallocs_made = 0;
        head->stats_mallocs_size = 0;
    }
    else {
        // link this page into our existing linked list
        head->last->next = page;
    }

    head->last = page;
    head->stats_pages++;
    head->stats_pages_size += size;

    return page;
}

ONEWAYALLOC *onewayalloc_create(size_t size_hint) {
    return (ONEWAYALLOC *)onewayalloc_create_internal(NULL, size_hint);
}

void *onewayalloc_mallocz(ONEWAYALLOC *owa, size_t size) {
    OWA_PAGE *head = (OWA_PAGE *)owa;
    OWA_PAGE *page = head->last;

    // update stats
    head->stats_mallocs_made++;
    head->stats_mallocs_size += size;

    // make sure the size is aligned
    size = natural_alignment(size);

    if(unlikely(page->size - page->offset < size)) {
        // we don't have enough space to fit the data
        // let's get another page
        page = onewayalloc_create_internal(head, (size > page->size)?size:page->size);
    }

    char *mem = (char *)page;
    mem = &mem[page->offset];
    page->offset += size;

    return (void *)mem;
}

void *onewayalloc_callocz(ONEWAYALLOC *owa, size_t nmemb, size_t size) {
    size_t total = nmemb * size;
    void *mem = onewayalloc_mallocz(owa, total);
    memset(mem, 0, total);
    return mem;
}

char *onewayalloc_strdupz(ONEWAYALLOC *owa, const char *s) {
    size_t size = strlen(s) + 1;
    char *d = onewayalloc_mallocz((OWA_PAGE *)owa, size);
    memcpy(d, s, size);
    return d;
}

void *onewayalloc_memdupz(ONEWAYALLOC *owa, const void *src, size_t size) {
    void *mem = onewayalloc_mallocz((OWA_PAGE *)owa, size);
    // memcpy() is way faster than strcpy() since it does not check for '\0'
    memcpy(mem, src, size);
    return mem;
}

void onewayalloc_freez(ONEWAYALLOC *owa __maybe_unused, const void *ptr __maybe_unused) {
#ifdef NETDATA_INTERNAL_CHECKS
    // allow the caller to call us for a mallocz() allocation
    // so try to find it in our memory and if it is not there
    // log an error

    if (unlikely(!ptr))
        return;

    OWA_PAGE *head = (OWA_PAGE *)owa;
    OWA_PAGE *page;
    uintptr_t seeking = (uintptr_t)ptr;

    for(page = head; page ;page = page->next) {
        uintptr_t start = (uintptr_t)page;
        uintptr_t end = start + page->size;

        if(seeking >= start && seeking <= end) {
            // found it - it is ours
            // just return to let the caller think we actually did something
            return;
        }
    }

    // not found - it is not ours
    // let's free it with the system allocator
    error("ONEWAYALLOC: request to free address 0x%p that is not allocated by this OWA", ptr);
#endif

    return;
}

void *onewayalloc_doublesize(ONEWAYALLOC *owa, const void *src, size_t oldsize) {
    size_t newsize = oldsize * 2;
    void *dst = onewayalloc_mallocz(owa, newsize);
    memcpy(dst, src, oldsize);
    onewayalloc_freez(owa, src);
    return dst;
}

void onewayalloc_destroy(ONEWAYALLOC *owa) {
    if(!owa) return;

    OWA_PAGE *head = (OWA_PAGE *)owa;

    //info("OWA: %zu allocations of %zu total bytes, in %zu pages of %zu total bytes",
    //     head->stats_mallocs_made, head->stats_mallocs_size,
    //     head->stats_pages, head->stats_pages_size);

    size_t total_size = 0;
    OWA_PAGE *page = head;
    while(page) {
        total_size += page->size;

        OWA_PAGE *p = page;
        page = page->next;

        // munmap(p, p->size);
        freez(p);
    }

    __atomic_sub_fetch(&onewayalloc_total_memory, total_size, __ATOMIC_RELAXED);
}