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
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
|
<section xmlns="http://docbook.org/ns/docbook" version="5.0"
xml:id="manual.ext.allocator.mt" xreflabel="mt allocator">
<?dbhtml filename="mt_allocator.html"?>
<info><title>mt_allocator</title>
<keywordset>
<keyword>
ISO C++
</keyword>
<keyword>
allocator
</keyword>
</keywordset>
</info>
<para>
</para>
<section xml:id="allocator.mt.intro"><info><title>Intro</title></info>
<para>
The mt allocator [hereinafter referred to simply as "the allocator"]
is a fixed size (power of two) allocator that was initially
developed specifically to suit the needs of multi threaded
applications [hereinafter referred to as an MT application]. Over
time the allocator has evolved and been improved in many ways, in
particular it now also does a good job in single threaded
applications [hereinafter referred to as a ST application]. (Note:
In this document, when referring to single threaded applications
this also includes applications that are compiled with gcc without
thread support enabled. This is accomplished using ifdef's on
__GTHREADS). This allocator is tunable, very flexible, and capable
of high-performance.
</para>
<para>
The aim of this document is to describe - from an application point of
view - the "inner workings" of the allocator.
</para>
</section>
<section xml:id="allocator.mt.design_issues"><info><title>Design Issues</title></info>
<section xml:id="allocator.mt.overview"><info><title>Overview</title></info>
<para> There are three general components to the allocator: a datum
describing the characteristics of the memory pool, a policy class
containing this pool that links instantiation types to common or
individual pools, and a class inheriting from the policy class that is
the actual allocator.
</para>
<para>The datum describing pools characteristics is
</para>
<programlisting>
template<bool _Thread>
class __pool
</programlisting>
<para> This class is parametrized on thread support, and is explicitly
specialized for both multiple threads (with <code>bool==true</code>)
and single threads (via <code>bool==false</code>.) It is possible to
use a custom pool datum instead of the default class that is provided.
</para>
<para> There are two distinct policy classes, each of which can be used
with either type of underlying pool datum.
</para>
<programlisting>
template<bool _Thread>
struct __common_pool_policy
template<typename _Tp, bool _Thread>
struct __per_type_pool_policy
</programlisting>
<para> The first policy, <code>__common_pool_policy</code>, implements a
common pool. This means that allocators that are instantiated with
different types, say <code>char</code> and <code>long</code> will both
use the same pool. This is the default policy.
</para>
<para> The second policy, <code>__per_type_pool_policy</code>, implements
a separate pool for each instantiating type. Thus, <code>char</code>
and <code>long</code> will use separate pools. This allows per-type
tuning, for instance.
</para>
<para> Putting this all together, the actual allocator class is
</para>
<programlisting>
template<typename _Tp, typename _Poolp = __default_policy>
class __mt_alloc : public __mt_alloc_base<_Tp>, _Poolp
</programlisting>
<para> This class has the interface required for standard library allocator
classes, namely member functions <code>allocate</code> and
<code>deallocate</code>, plus others.
</para>
</section>
</section>
<section xml:id="allocator.mt.impl"><info><title>Implementation</title></info>
<section xml:id="allocator.mt.tune"><info><title>Tunable Parameters</title></info>
<para>Certain allocation parameters can be modified, or tuned. There
exists a nested <code>struct __pool_base::_Tune</code> that contains all
these parameters, which include settings for
</para>
<itemizedlist>
<listitem><para>Alignment</para></listitem>
<listitem><para>Maximum bytes before calling <code>::operator new</code> directly</para></listitem>
<listitem><para>Minimum bytes</para></listitem>
<listitem><para>Size of underlying global allocations</para></listitem>
<listitem><para>Maximum number of supported threads</para></listitem>
<listitem><para>Migration of deallocations to the global free list</para></listitem>
<listitem><para>Shunt for global <code>new</code> and <code>delete</code></para></listitem>
</itemizedlist>
<para>Adjusting parameters for a given instance of an allocator can only
happen before any allocations take place, when the allocator itself is
initialized. For instance:
</para>
<programlisting>
#include <ext/mt_allocator.h>
struct pod
{
int i;
int j;
};
int main()
{
typedef pod value_type;
typedef __gnu_cxx::__mt_alloc<value_type> allocator_type;
typedef __gnu_cxx::__pool_base::_Tune tune_type;
tune_type t_default;
tune_type t_opt(16, 5120, 32, 5120, 20, 10, false);
tune_type t_single(16, 5120, 32, 5120, 1, 10, false);
tune_type t;
t = allocator_type::_M_get_options();
allocator_type::_M_set_options(t_opt);
t = allocator_type::_M_get_options();
allocator_type a;
allocator_type::pointer p1 = a.allocate(128);
allocator_type::pointer p2 = a.allocate(5128);
a.deallocate(p1, 128);
a.deallocate(p2, 5128);
return 0;
}
</programlisting>
</section>
<section xml:id="allocator.mt.init"><info><title>Initialization</title></info>
<para>
The static variables (pointers to freelists, tuning parameters etc)
are initialized as above, or are set to the global defaults.
</para>
<para>
The very first allocate() call will always call the
_S_initialize_once() function. In order to make sure that this
function is called exactly once we make use of a __gthread_once call
in MT applications and check a static bool (_S_init) in ST
applications.
</para>
<para>
The _S_initialize() function:
- If the GLIBCXX_FORCE_NEW environment variable is set, it sets the bool
_S_force_new to true and then returns. This will cause subsequent calls to
allocate() to return memory directly from a new() call, and deallocate will
only do a delete() call.
</para>
<para>
- If the GLIBCXX_FORCE_NEW environment variable is not set, both ST and MT
applications will:
- Calculate the number of bins needed. A bin is a specific power of two size
of bytes. I.e., by default the allocator will deal with requests of up to
128 bytes (or whatever the value of _S_max_bytes is when _S_init() is
called). This means that there will be bins of the following sizes
(in bytes): 1, 2, 4, 8, 16, 32, 64, 128.
- Create the _S_binmap array. All requests are rounded up to the next
"large enough" bin. I.e., a request for 29 bytes will cause a block from
the "32 byte bin" to be returned to the application. The purpose of
_S_binmap is to speed up the process of finding out which bin to use.
I.e., the value of _S_binmap[ 29 ] is initialized to 5 (bin 5 = 32 bytes).
</para>
<para>
- Create the _S_bin array. This array consists of bin_records. There will be
as many bin_records in this array as the number of bins that we calculated
earlier. I.e., if _S_max_bytes = 128 there will be 8 entries.
Each bin_record is then initialized:
- bin_record->first = An array of pointers to block_records. There will be
as many block_records pointers as there are maximum number of threads
(in a ST application there is only 1 thread, in a MT application there
are _S_max_threads).
This holds the pointer to the first free block for each thread in this
bin. I.e., if we would like to know where the first free block of size 32
for thread number 3 is we would look this up by: _S_bin[ 5 ].first[ 3 ]
The above created block_record pointers members are now initialized to
their initial values. I.e. _S_bin[ n ].first[ n ] = NULL;
</para>
<para>
- Additionally a MT application will:
- Create a list of free thread id's. The pointer to the first entry
is stored in _S_thread_freelist_first. The reason for this approach is
that the __gthread_self() call will not return a value that corresponds to
the maximum number of threads allowed but rather a process id number or
something else. So what we do is that we create a list of thread_records.
This list is _S_max_threads long and each entry holds a size_t thread_id
which is initialized to 1, 2, 3, 4, 5 and so on up to _S_max_threads.
Each time a thread calls allocate() or deallocate() we call
_S_get_thread_id() which looks at the value of _S_thread_key which is a
thread local storage pointer. If this is NULL we know that this is a newly
created thread and we pop the first entry from this list and saves the
pointer to this record in the _S_thread_key variable. The next time
we will get the pointer to the thread_record back and we use the
thread_record->thread_id as identification. I.e., the first thread that
calls allocate will get the first record in this list and thus be thread
number 1 and will then find the pointer to its first free 32 byte block
in _S_bin[ 5 ].first[ 1 ]
When we create the _S_thread_key we also define a destructor
(_S_thread_key_destr) which means that when the thread dies, this
thread_record is returned to the front of this list and the thread id
can then be reused if a new thread is created.
This list is protected by a mutex (_S_thread_freelist_mutex) which is only
locked when records are removed or added to the list.
</para>
<para>
- Initialize the free and used counters of each bin_record:
- bin_record->free = An array of size_t. This keeps track of the number
of blocks on a specific thread's freelist in each bin. I.e., if a thread
has 12 32-byte blocks on it's freelists and allocates one of these, this
counter would be decreased to 11.
- bin_record->used = An array of size_t. This keeps track of the number
of blocks currently in use of this size by this thread. I.e., if a thread
has made 678 requests (and no deallocations...) of 32-byte blocks this
counter will read 678.
The above created arrays are now initialized with their initial values.
I.e. _S_bin[ n ].free[ n ] = 0;
</para>
<para>
- Initialize the mutex of each bin_record: The bin_record->mutex
is used to protect the global freelist. This concept of a global
freelist is explained in more detail in the section "A multi
threaded example", but basically this mutex is locked whenever a
block of memory is retrieved or returned to the global freelist
for this specific bin. This only occurs when a number of blocks
are grabbed from the global list to a thread specific list or when
a thread decides to return some blocks to the global freelist.
</para>
</section>
<section xml:id="allocator.mt.deallocation"><info><title>Deallocation Notes</title></info>
<para> Notes about deallocation. This allocator does not explicitly
release memory. Because of this, memory debugging programs like
valgrind or purify may notice leaks: sorry about this
inconvenience. Operating systems will reclaim allocated memory at
program termination anyway. If sidestepping this kind of noise is
desired, there are three options: use an allocator, like
<code>new_allocator</code> that releases memory while debugging, use
GLIBCXX_FORCE_NEW to bypass the allocator's internal pools, or use a
custom pool datum that releases resources on destruction.
</para>
<para>
On systems with the function <code>__cxa_atexit</code>, the
allocator can be forced to free all memory allocated before program
termination with the member function
<code>__pool_type::_M_destroy</code>. However, because this member
function relies on the precise and exactly-conforming ordering of
static destructors, including those of a static local
<code>__pool</code> object, it should not be used, ever, on systems
that don't have the necessary underlying support. In addition, in
practice, forcing deallocation can be tricky, as it requires the
<code>__pool</code> object to be fully-constructed before the object
that uses it is fully constructed. For most (but not all) STL
containers, this works, as an instance of the allocator is constructed
as part of a container's constructor. However, this assumption is
implementation-specific, and subject to change. For an example of a
pool that frees memory, see the following
<link xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://gcc.gnu.org/viewcvs/trunk/libstdc++-v3/testsuite/ext/mt_allocator/deallocate_local-6.cc?view=markup">
example.</link>
</para>
</section>
</section>
<section xml:id="allocator.mt.example_single"><info><title>Single Thread Example</title></info>
<para>
Let's start by describing how the data on a freelist is laid out in memory.
This is the first two blocks in freelist for thread id 3 in bin 3 (8 bytes):
</para>
<programlisting>
+----------------+
| next* ---------|--+ (_S_bin[ 3 ].first[ 3 ] points here)
| | |
| | |
| | |
+----------------+ |
| thread_id = 3 | |
| | |
| | |
| | |
+----------------+ |
| DATA | | (A pointer to here is what is returned to the
| | | the application when needed)
| | |
| | |
| | |
| | |
| | |
| | |
+----------------+ |
+----------------+ |
| next* |<-+ (If next == NULL it's the last one on the list)
| |
| |
| |
+----------------+
| thread_id = 3 |
| |
| |
| |
+----------------+
| DATA |
| |
| |
| |
| |
| |
| |
| |
+----------------+
</programlisting>
<para>
With this in mind we simplify things a bit for a while and say that there is
only one thread (a ST application). In this case all operations are made to
what is referred to as the global pool - thread id 0 (No thread may be
assigned this id since they span from 1 to _S_max_threads in a MT application).
</para>
<para>
When the application requests memory (calling allocate()) we first look at the
requested size and if this is > _S_max_bytes we call new() directly and return.
</para>
<para>
If the requested size is within limits we start by finding out from which
bin we should serve this request by looking in _S_binmap.
</para>
<para>
A quick look at _S_bin[ bin ].first[ 0 ] tells us if there are any blocks of
this size on the freelist (0). If this is not NULL - fine, just remove the
block that _S_bin[ bin ].first[ 0 ] points to from the list,
update _S_bin[ bin ].first[ 0 ] and return a pointer to that blocks data.
</para>
<para>
If the freelist is empty (the pointer is NULL) we must get memory from the
system and build us a freelist within this memory. All requests for new memory
is made in chunks of _S_chunk_size. Knowing the size of a block_record and
the bytes that this bin stores we then calculate how many blocks we can create
within this chunk, build the list, remove the first block, update the pointer
(_S_bin[ bin ].first[ 0 ]) and return a pointer to that blocks data.
</para>
<para>
Deallocation is equally simple; the pointer is casted back to a block_record
pointer, lookup which bin to use based on the size, add the block to the front
of the global freelist and update the pointer as needed
(_S_bin[ bin ].first[ 0 ]).
</para>
<para>
The decision to add deallocated blocks to the front of the freelist was made
after a set of performance measurements that showed that this is roughly 10%
faster than maintaining a set of "last pointers" as well.
</para>
</section>
<section xml:id="allocator.mt.example_multi"><info><title>Multiple Thread Example</title></info>
<para>
In the ST example we never used the thread_id variable present in each block.
Let's start by explaining the purpose of this in a MT application.
</para>
<para>
The concept of "ownership" was introduced since many MT applications
allocate and deallocate memory to shared containers from different
threads (such as a cache shared amongst all threads). This introduces
a problem if the allocator only returns memory to the current threads
freelist (I.e., there might be one thread doing all the allocation and
thus obtaining ever more memory from the system and another thread
that is getting a longer and longer freelist - this will in the end
consume all available memory).
</para>
<para>
Each time a block is moved from the global list (where ownership is
irrelevant), to a threads freelist (or when a new freelist is built
from a chunk directly onto a threads freelist or when a deallocation
occurs on a block which was not allocated by the same thread id as the
one doing the deallocation) the thread id is set to the current one.
</para>
<para>
What's the use? Well, when a deallocation occurs we can now look at
the thread id and find out if it was allocated by another thread id
and decrease the used counter of that thread instead, thus keeping the
free and used counters correct. And keeping the free and used counters
corrects is very important since the relationship between these two
variables decides if memory should be returned to the global pool or
not when a deallocation occurs.
</para>
<para>
When the application requests memory (calling allocate()) we first
look at the requested size and if this is >_S_max_bytes we call new()
directly and return.
</para>
<para>
If the requested size is within limits we start by finding out from which
bin we should serve this request by looking in _S_binmap.
</para>
<para>
A call to _S_get_thread_id() returns the thread id for the calling thread
(and if no value has been set in _S_thread_key, a new id is assigned and
returned).
</para>
<para>
A quick look at _S_bin[ bin ].first[ thread_id ] tells us if there are
any blocks of this size on the current threads freelist. If this is
not NULL - fine, just remove the block that _S_bin[ bin ].first[
thread_id ] points to from the list, update _S_bin[ bin ].first[
thread_id ], update the free and used counters and return a pointer to
that blocks data.
</para>
<para>
If the freelist is empty (the pointer is NULL) we start by looking at
the global freelist (0). If there are blocks available on the global
freelist we lock this bins mutex and move up to block_count (the
number of blocks of this bins size that will fit into a _S_chunk_size)
or until end of list - whatever comes first - to the current threads
freelist and at the same time change the thread_id ownership and
update the counters and pointers. When the bins mutex has been
unlocked, we remove the block that _S_bin[ bin ].first[ thread_id ]
points to from the list, update _S_bin[ bin ].first[ thread_id ],
update the free and used counters, and return a pointer to that blocks
data.
</para>
<para>
The reason that the number of blocks moved to the current threads
freelist is limited to block_count is to minimize the chance that a
subsequent deallocate() call will return the excess blocks to the
global freelist (based on the _S_freelist_headroom calculation, see
below).
</para>
<para>
However if there isn't any memory on the global pool we need to get
memory from the system - this is done in exactly the same way as in a
single threaded application with one major difference; the list built
in the newly allocated memory (of _S_chunk_size size) is added to the
current threads freelist instead of to the global.
</para>
<para>
The basic process of a deallocation call is simple: always add the
block to the front of the current threads freelist and update the
counters and pointers (as described earlier with the specific check of
ownership that causes the used counter of the thread that originally
allocated the block to be decreased instead of the current threads
counter).
</para>
<para>
And here comes the free and used counters to service. Each time a
deallocation() call is made, the length of the current threads
freelist is compared to the amount memory in use by this thread.
</para>
<para>
Let's go back to the example of an application that has one thread
that does all the allocations and one that deallocates. Both these
threads use say 516 32-byte blocks that was allocated during thread
creation for example. Their used counters will both say 516 at this
point. The allocation thread now grabs 1000 32-byte blocks and puts
them in a shared container. The used counter for this thread is now
1516.
</para>
<para>
The deallocation thread now deallocates 500 of these blocks. For each
deallocation made the used counter of the allocating thread is
decreased and the freelist of the deallocation thread gets longer and
longer. But the calculation made in deallocate() will limit the length
of the freelist in the deallocation thread to _S_freelist_headroom %
of it's used counter. In this case, when the freelist (given that the
_S_freelist_headroom is at it's default value of 10%) exceeds 52
(516/10) blocks will be returned to the global pool where the
allocating thread may pick them up and reuse them.
</para>
<para>
In order to reduce lock contention (since this requires this bins
mutex to be locked) this operation is also made in chunks of blocks
(just like when chunks of blocks are moved from the global freelist to
a threads freelist mentioned above). The "formula" used can probably
be improved to further reduce the risk of blocks being "bounced back
and forth" between freelists.
</para>
</section>
</section>
|