-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathmemory.cpp
More file actions
284 lines (230 loc) · 7.23 KB
/
memory.cpp
File metadata and controls
284 lines (230 loc) · 7.23 KB
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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
/*
This is memory.cpp
Coxeter version 3.0 Copyright (C) 2002 Fokko du Cloux
See file main.cpp for full copyright notice
*/
#include "memory.h"
#include <limits.h>
#include "error.h"
namespace memory {
using namespace error;
};
namespace {
using namespace memory;
const Ulong MEMORY_MAX = ULONG_MAX;
const Ulong ABYTES = sizeof(Align);
const Ulong ARENA_BITS = 16;
};
/****************************************************************************
This module contains the memory allocator for this program. Since we are
doing a lot of dynamic memory allocation, and use many variable-sized
structures, both small and large, efficient memory allocation seems to
be a crucial performance issue.
We try to take an approach which is as simple as possible, for the sake
of efficiency, while trying to be not too wasteful. First of all, memory
is gotten from the system, via calloc, in chunks of 2^N.a bytes, where
a = ABYTES will ensure proper alignment for all the types used
in this program (here we make the crucial assumption that all builtin
types have a size which is a power of two.) The only exception is when
a memory request does not fit in such a chunk; then we allocate a block
of size 2^m.a for large enough m. These chunks, once gotten,
are never given back; however, we try to reuse them insofar as possible.
Only three basic operations are provided : alloc, which returns a pointer
to a new memory block; grow, which resizes a block to a larger block;
and free, which frees a block. All blocks handed out in this way have
sizes of the form 2^m.a, 0 <= m < BITS(Ulong). On average this should
lead to a memory efficiency of 75%, which is not worse than what I was
doing earlier, anyway.
****************************************************************************/
namespace memory {
/**
Return the memory arena.
*/
Arena& arena() {
static Arena a(ARENA_BITS);
return a;
}
/****************************************************************************
Chapter I -- The Arena class.
****************************************************************************/
Arena::Arena(Ulong bsBits) {
memset(d_list,0,BITS(Ulong)*sizeof(void *));
memset(d_used,0,BITS(Ulong)*sizeof(Ulong));
memset(d_allocated,0,BITS(Ulong)*sizeof(Ulong));
d_bsBits = bsBits;
d_count = 0;
}
/**
Nothing to do here! All memory is allocated in fixed-size arrays.
*/
Arena::~Arena() {}
/**
Provides a new block of size 2^{b}. It looks first of a block of
size > 2^b is available; if yes, it splits the required block up
from that one. If not, it requests from the system a block of size
2^d_bsBits, if b <= d_bsBits, and of size 2^b otherwise (the
unit here is always ABYTES.)
Note that this is the only place where we can run out of memory.
In that case, the error OUT_OF_MEMORY is set, and the corresponding
error function run. In most cases, this will terminate the program.
In case of failure, returns a null pointer.
NOTE : as this function will be heavily used, it should be rewritten
using a bitmap of available blocks.
*/
void Arena::newBlock(unsigned b) {
for (unsigned j = b+1; j < BITS(Ulong); ++j) {
if (d_list[j]) /* split this block up */
{
Align *ptr = reinterpret_cast<Align *> (d_list[j]);
d_list[j] = d_list[j]->next;
d_allocated[j]--;
for (unsigned i = b; i < j; ++i) {
d_list[i] = reinterpret_cast<MemBlock *> (ptr + (1L<<i));
d_allocated[i]++;
}
d_list[b]->next = reinterpret_cast<MemBlock *> (ptr);
d_list[b]->next->next = 0;
d_allocated[b]++;
return;
}
}
/* if we get here we need more memory from the system */
if (b >= d_bsBits) { /* get block directly */
if (d_count > MEMORY_MAX-(1L<<b)) {
Error(OUT_OF_MEMORY);
return;
}
d_list[b] = static_cast<MemBlock *> (calloc(1L<<b,ABYTES));
if (d_list[b] == 0) {
Error(OUT_OF_MEMORY);
return;
}
d_count += 1L<<b;
d_allocated[b]++;
return;
}
if (d_count > MEMORY_MAX-(1L<<d_bsBits)) {
Error(OUT_OF_MEMORY);
return;
}
Align *ptr = static_cast<Align *> (calloc(1L<<d_bsBits,ABYTES));
if (ptr == 0) {
Error(OUT_OF_MEMORY);
return;
}
d_count += 1L<<d_bsBits;
for (unsigned j = b; j < d_bsBits; ++j) {
d_list[j] = reinterpret_cast<MemBlock *> (ptr + (1L<<j));
d_allocated[j]++;
}
d_list[b]->next = reinterpret_cast<MemBlock *> (ptr);
d_allocated[b]++;
return;
}
/**
Returns a pointer to a block of 2^m.ABYTES bytes, where m is the
smallest integer such that 2^m.ABYTES >= n.
It is assumed that ABYTES is a power of 2.
The memory is zero-initialized.
*/
void* Arena::alloc(size_t n)
{
if (n == 0)
return 0;
/* compute size of block */
unsigned b = 0;
if (n > ABYTES)
b = lastBit(n-1)-lastbit[ABYTES]+1;
if (d_list[b] == 0) { /* need to make a new block */
newBlock(b);
if (ERRNO)
return 0;
}
/* take block off from list */
MemBlock *block = d_list[b];
d_list[b] = d_list[b]->next;
block->next = 0;
d_used[b]++;
return static_cast<void *> (block);
}
/**
Returns the size of the actual memory allocation provided on a request
of n nodes of size m, in units of m
*/
Ulong Arena::allocSize(Ulong n, Ulong m) const
{
if (n == 0)
return 0;
if (n*m <= ABYTES)
return ABYTES/m;
return ((1 << lastBit(n*m-1)-lastbit[ABYTES]+1)*ABYTES)/m;
}
/**
Returns the actual number of bytes of the memory allocation (as opposed
to allocSize, which rounds the allocation to the largest multiple of m.)
*/
Ulong Arena::byteSize(Ulong n, Ulong m) const
{
if (n == 0)
return 0;
if (n*m <= ABYTES)
return ABYTES;
return (1 << lastBit(n*m-1)-lastbit[ABYTES]+1)*ABYTES;
}
/**
Resizes ptr to size new_size. This involves getting the larger block,
copying the contents of ptr to it, and freeing ptr; we never try to
fuse smaller adjacent blocks together.
Returns 0 and sets the error MEMORY_WARNING in case of overflow, if
CATCH_MEMORY_OVERFLOW is set.
NOTE : equivalent to alloc if old_size = 0.
*/
void *memory::Arena::realloc(void *ptr, size_t old_size, size_t new_size)
{
void *new_ptr = alloc(new_size);
if (ERRNO) /* overflow */
return 0;
if (old_size) {
memcpy(new_ptr,ptr,old_size);
free(ptr,old_size);
}
return new_ptr;
}
/**
Returns the memory block allocated to ptr to the free list. In order to
know to which list the pointer should be appended (it will in fact be
prepended), we need to pass the size to which ptr was allocated.
*/
void Arena::free(void *ptr, size_t n)
{
if (ptr == 0)
return;
if (n == 0)
return;
unsigned b = 0;
if (n > ABYTES)
b = lastBit(n-1)-lastbit[ABYTES]+1;
memset(ptr,0,(1L<<b)*ABYTES);
MemBlock *block = (MemBlock *)ptr;
block->next = d_list[b];
d_list[b] = block;
d_used[b]--;
return;
}
/**
Prints information about the memory arena.
*/
void Arena::print(FILE *file) const
{
fprintf(file,"%-10s%10s/%-10s\n","size : 2^","used","allocated");
Ulong used_count = 0;
for (unsigned j = 0; j < BITS(Ulong); ++j) {
fprintf(file,"%3u%7s%10lu/%-10lu\n",j,"",d_used[j],d_allocated[j]);
used_count += (1L<<j)*d_used[j];
}
fprintf(file,"\n");
fprintf(file,"total : %10lu/%-10lu %lu-byte units used/allocated\n",
used_count,static_cast<Ulong>(d_count),ABYTES);
}
void pause() { ; }
}