boost_container 0.1.0

Boost C++ library boost_container packaged using Zanbil
Documentation
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
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>

<style>
p {text-align:justify}
li {text-align:justify}
blockquote.note
{
    background-color:#E0E0E0;
    padding-left: 15px;
    padding-right: 15px;
    padding-top: 1px;
    padding-bottom: 1px;
}
ins {background-color:#FFFFA0}
del {background-color:#FFFFA0}

cpp_comment {color:#007F33}

pre {color:#000066}
code{color:#000066}

</style>

<head>
<title>Applying classic memory allocation strategies to C++ containers (v0.1)</title>
</head>

<h1>Applying classic memory allocation strategies to C++ containers (v0.1)</h1>

<body>
<p>
Ion Gazta&ntilde;aga &lt;<a href="mailto:igaztanaga@gmail.com">igaztanaga@gmail.com</a>&gt;<br>
</p>

<h1>Table of Contents</h1>

<ul>
<li><a href="#Introduction">Introduction</a></li>
<li><a href="#Chapter1">Chapter 1: In-place expansion for <code>vector</code></a></li>
   <ul>
   <li><a href="#WhatWeTried">What we tried: Existing proposals for in-place expansion</a></li>
   <li><a href="#NewFunctionsForExpansion">New functions for expansion capabilities</a></li>
   <li><a href="#AConfigurableAllocator">A configurable allocator for testing</a></li>
   <li><a href="#TestingForward">Testing forward expansion in <code>vector</code></a></li>
   <li><a href="#TestingBackwards">Testing backwards expansion in <code>vector</code></a></li>
   <li><a href="#TestingShrinkToFit">Testing shrink to fit in <code>vector</code></a></li>
   </ul>
<li><a href="#Chapter2">Chapter 2: Burst allocation for node containers</a></li>
   <ul>
   <li><a href="#BurstAllocationIntro">Burst allocation introduction</a></li>
   <li><a href="#BurstAllocationInfrastructure">Burst allocation infrastructure</a></li>
   <li><a href="#BurstInterfaceForAllocators">Burst allocation interface for allocators</a></li>
   <li><a href="#TestingBurstAllocation">Testing burst allocation</a></li>
   </ul>
<li><a href="#Chapter3">Chapter 3: Adaptive pools for node containers</a></li>
   <ul>
   <li><a href="#SimpleSegregatedStorage">Simple segregated storage: advantages and problems</a></li>
   <li><a href="#AdaptivePoolsExplained">Adaptive pools explained</a></li>
   <li><a href="#PresentingAdaptivePool">Presenting <code>adaptive_pool</code></a></li>
   <li><a href="#TestingAdaptivePool">Testing <code>adaptive_pool</code></a></li>
   </ul>
</ul>

<h1><a name="Introduction"></a>Introduction</h1>
<p>
I'm sure that many C++ programmers have ever wondered where does good old realloc 
fit in C++. And that's a good question. Could we improve <code>std::vector</code> performance 
using memory expansion mechanisms to avoid too many copies? But <code>std::vector</code> 
is not the only container that could benefit from some classic strategies memory allocation: What if we 
could take advantage of the insertion of multiple elements in <code>std::list</code> using 
a burst allocation mechanism that could amortize costs (mutex locks, searches...) that 
can't be amortized when using single node allocation strategies? Some space-saving 
strategies also come to mind with node containers: Can we find a mechanism to build a 
memory allocator that offers the size-saving benefits of a simple segregated storage 
strategy (see <a href="http://www.boost.org/libs/pool">Boost.Pool</a>) without suffering
too big node pools when most of nodes have been deallocated?
</p>
<p>
This article will explain how some classic improvement strategies have been applied to 
C++ allocators. Many of these mechanism are being used for shared-memory allocators and 
containers (see <a href="http://www.boost.org/libs/interprocess">Boost.Interprocess</a>) 
where size restrictions are tighter than with heap allocators and other strategies (e.g. 
per-thread-pthread pools) are not applicable. So we'll be using <a href="http://www.boost.org/libs/interprocess">
Boost.Interprocess</a> containers to measure the speed up that we can obtain when using 
those classic strategies in C++.
</p>
<p>
Obviously, these C++ allocators need some underlying general purpose allocator, since 
new and delete don't offer expansion and burst capabilities. In this article we'll 
use a modified <a href="http://g.oswego.edu/dl/html/malloc.html">Doug Lea Malloc, aka 
DLmalloc</a> memory allocator with new basic functions that will offer the necessary 
underlying machinery to implement all these classic strategies. <b>DLmalloc</b> is known to be 
very size and speed efficient, and this allocator is used as the basis of many malloc 
implementations, including multithreaded allocators built above <b>DLmalloc</b>
(See <a href="http://directory.fsf.org/project/ptmalloc2/">ptmalloc2</a>, 
<a href="http://www.malloc.de/en/">ptmalloc3</a> or
<a href="http://www.nedprod.com/programs/portable/nedmalloc/index.html">nedmalloc</a>
). So 
improvements obtained by the classic strategies applied to C++ will measure real-world 
speedups, even when the allocator is very efficient.
</p>
<p>
I've developed some additional classes, like STL allocators, pools and tests, I've put 
all this code under Boost Software license, and I've respected Boost guidelines so that 
the developed code can be considered integrated in the Boost Container.</a>.
</p>

<h1><a name="Chapter1"></a>Chapter 1: In-place expansion for <code>vector</code></h1>

<h2><a name="WhatWeTried"></a>What we tried: Existing proposals for in-place expansion</h2>

<p>
There've been a couple of C++ proposal to add in-place expansion mechanisms to standard 
allocators (<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n1953.html">N1953: 
Upgrading the Interface of Allocators using API Versioning</a> written by Howard Hinnant 
and <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2045.html">N2045: 
Improving STL allocators</a> written by me based on Hinnant's proposal). Unfortunately 
they didn't receive much interest in the committee but there is hope that we could get 
some of this functionality for C++1x if the C++ community shows some interest. Let's 
review a bit what these proposals said:
</p>
<p>Hinnant's proposal adds some new functionalities to C++ allocators:
<ol>
<li>
The ability to know the size of a memory buffer allocated with the allocator.
<li>
The ability to expand a previously obtained memory buffer (similar to realloc but with no 
memory copy)
<li>
The ability to shrink a previously obtained memory buffer (similar to realloc but with no 
memory copy)
<li>
Three size concepts when allocating memory: <b>preferred size</b>, <b>limit size</b> and <b>received size</b>.</li>
</ol>
<P></P>
<p>
The first functionality is interesting to avoid wasting some precious bytes the memory 
allocator adds in the end of the buffer due to alignment reasons or because its own 
memory allocation algorithm. As an example, <b>DLmalloc</b> on 32 bit systems will need a 
minimum of 16 bytes for every allocation (4 bytes are payload and 12 bytes are available 
for the user): If we try to initialize <code>std::string</code> with a 4 character 
narrow string (<code>"abcd"</code>) we will waste 8 bytes, because we have no way to know
that there is room in our newly allocated buffer that could be used for longer strings
without going again through the allocator. Other allocators waste even 
more memory. In 64 bit systems the minimum size (and thus, the waste) is bigger:
32 bytes (24 bytes available for the user) for <b>DLmalloc</b>.
</p>
<p>
The second functionality is the classical use case of realloc in C: instead of 
allocating a new buffer, the programmer tries to expand the current one. If the 
expansion succeeds, there is no need to copy data to the new buffer (in C++, that means 
avoiding potentially costly copy-constructors). The third one is exactly the inverse: a 
vector that has grown considerably has now a few elements, <code>vector.capacity()</code> is
much bigger than <code>vector.size()</code> and we'd like to return memory 
to the system. Many programmers use the <a href="http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Shrink-to-fit">
shrink-to-fit trick</a> to return memory to the system, but this trick needs
a possibly throwing copy, whereas a shrink to fit operation implemented
by the allocator would not need any copying at all.
</p>
<p>
The last functionality is really interesting because a vector increases its capacity 
usually using a growth factor (usually 1.5f or 2.0f) to avoid too many reallocations. That is, when we insert 
new objects in a vector and <code>vector.capacity() == vector.size()</code>, then the 
vector allocates much more memory than the size needed to insert a new object. When 
allocating new memory to insert a new object, <b>limit size</b> is <code>size() + 1</code>,
<b>preferred size</b> is <code>size()*growth_factor</code> and <b>received size</b> is the real
size of the buffer. When inserting a new element in a vector, is usually better to 
expand the memory to hold the new element than to allocate a new bigger buffer.
</p>
<p>
<a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2045.html">N2045</a>
makes some additions:
<ol>
<li>
The ability to expand the current buffer not only forward, but also backwards (forward 
expansion, backwards expansion or both).
<li>
Offers the possibility to allocate or expand the buffer in a single call to avoid excessive 
locking.
<li>
Separates array allocation and node allocation, similarly to <code>operator new</code> and <code>operator 
new[]</code>.
<li>
Centralizes all the expansion, shrinking and allocation functionality in a single function.
<li>
Pushes <b>limit size</b> and <b>preferred size</b> concepts also to new allocations and shrink to 
fit operations.</li>
</ol>
<P></P>
<p>
The first addition is useful for embedded systems and other size-limited memories, like 
fixed-size shared memory. If a vector can only be expanded forward, then it can't reuse 
preceding free memory if that preceding memory is not big enough (that is, 1.5 or 2.0 times bigger than
the current buffer). With backwards expansion, this limitation disappears and
backwards expansion can be combined with forward expansion to optimize memory usage. 
</p>
<p>
The vector has to copy elements backwards so backwards expansion needs exactly the
same number of copies as a new allocation. Many times backwards expansion can be implemented
very efficiently in some memory algorithms that have fast access to previous and next memory buffers.
Backwards expansion also has some downsides:
<ol>
<li>
The code handling data copy for a backwards expanded buffer is not trivial (the new buffer 
has both already constructed objects and raw memory)
<li>
In order to maintain old objects aligned (and thus reusable for being move assigned) the 
underlying C function implementing backwards expansion must guarantee 
that the distance between the new beginning of the backwards expanded buffer and the old 
beginning must be multiple of the size of the objects already constructed in the buffer (and of course, 
multiple of the alignment required by the memory allocation algorithm). A buffer 
holding a type with <code>sizeof(Type)</code> == 12 can't be expanded backwards 16 bytes, because the 
distance between the old beginning and the new beginning is not multiple of 12, and thus, the 
old object will be partially overwritten and it can't be safely move-assigned.</li>
</ol>
<P></P>
<p>
The second addition tries to compact in a single call several tries to expand or 
allocate memory because usually a vector will first try to expand memory and if this fails,
it will try to allocate a new buffer. Since checking for expansion 
capabilities is quite fast comparing to mutex locking and other checks performed by the 
allocator, both functions can be combined in a single call.
</p>
<p>
The third addition (separating node and array allocation) is based on the fact that with 
the addition of expansion functions, node allocators based classic segregated storage 
pools won't be able to be allocators with expansion capabilities. This might not sound 
like a big problem for pure-node containers like <code>std::list</code> but some hybrid 
containers like <code>std::unordered_map</code> wouldn't be able to use both segregated 
storage (for nodes) and expansion features (for the bucket array).
</p>
<p>
Furthermore, some allocators (see <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2554.pdf">
N2554 The Scoped Allocator Model (Rev 2)</a>) can be propagated through containers, so 
having an allocator that can use in-place expansion capabilities and node-pooling 
capabilities is very interesting. And this can only be achieved if the allocator uses
different functions for single element and array operations.
</p>
<p>
The last two 
additions are just optimizations to reduce locking and to minimize the number of allocation 
calls.
</p>
<p>
Hinnant's proposal was implemented in the Freescale (then Metrowerks) standard library 
(MSL) with great success. My approach was implemented in <a href="http://www.boost.org/libs/interprocess">
Boost.Interprocess</a>. This article is an evolution of my last proposal but applying 
these techniques in a widely used heap allocator.
</p>

<h2><a name="NewFunctionsForExpansion">New functions for expansion capabilities</h2>

As mentioned, <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2045.html">
N2045</a> proposed an advanced new function for allocators that offers the 
possibility to request buffer expansion (combined or not with allocation if the 
expansion fails), and shrinking called <code>allocation_command</code>. This is the 
explanation of the function:
<pre>
   enum allocation_type
   {
      <cpp_comment>//Bitwise OR (|) combinable values</cpp_comment>
      allocate_new        = ...,
      expand_fwd          = ...,
      expand_bwd          = ...,
      shrink_in_place     = ...,
      try_shrink_in_place = ...,
      nothrow_allocation  = ...
   };


   template&lt;class T&gt;
   std::pair&lt;T *, bool&gt; allocation_command
      ( allocation_type command,    std::size_t limit_size
      , std::size_t preferred_size, std::size_t &amp;received_size, T *reuse_ptr = 0);

</pre>
<p>
<b>Preconditions for this function</b>:
</p>
<p>
<ul>
<li>
The parameter <code>command</code> must contain at least of these values: <code>shrink_in_place</code>,
<code>try_shrink_in_place</code>, <code>try_shrink_in_place</code>, <code>expand_fwd</code>, <code>expand_bwd</code> or <code>allocate_new</code>.
</li>
<li>
If the parameter <code>command</code> contains the value <code>shrink_in_place</code> it can't 
contain any of these values: <code>expand_fwd</code>, <code>expand_bwd</code>,
<code>allocate_new</code>, <code>try_shrink_in_place</code>.
</li>
<li>
If the parameter <code>command</code> contains the value <code>try_shrink_in_place</code> it can't 
contain any of these values: <code>expand_fwd</code>, <code>expand_bwd</code>,
<code>allocate_new</code>, <code>shrink_in_place</code>.
</li>
<li>
If the parameter <code>command</code> contains <code>expand_fwd</code> or <code>expand_bwd</code>, the 
parameter <code>reuse_ptr</code>
must be non-null and returned by a previous allocation function.
<li>
If the parameter <code>command</code> contains the value <code>shrink_in_place</code>,
the parameter <code>limit_size</code>
must be equal or greater than the parameter <code>preferred_size</code>.
<li>
If the parameter <code>command</code> contains any of these values: <code>expand_fwd</code> or <code>
expand_bwd</code>, the parameter <code>limit_size</code> must be equal or less than the 
parameter <code>preferred_size</code>.</li>
</ul>
<P></P>
<p>
<b>Which are the effects of this function</b>:
</p>
<p>
<ul>
<li>
If the parameter <code>command</code> contains the value <code>shrink_in_place</code>,
(with optional additional <code>nothrow_allocation</code>) the function will 
try to reduce the size of the memory block referenced by pointer <code>reuse_ptr</code> to the 
value <code>preferred_size</code> moving only the end of the block. If it's not possible, it 
will try to reduce the size of the memory block as much as possible as long as this results in <code>
size(p) &lt;= limit_size</code>. Success is reported only if this results in <code>preferred_size 
&lt;= size(p)</code> and <code>size(p) &lt;= limit_size</code>.
</li>
<li>
If the parameter <code>command</code> contains the value <code>try_shrink_in_place</code>,
(with optional additional <code>nothrow_allocation</code>) the function will act as if
a <code>shrink_in_place</code> was demaned but it won't shrink the buffer. This function
is useful to know if a shrink operation will have success with the given parameters and
obtain in the parameter <code>received_size</code> a value that is guaranteed to succeed
as <code>limit_size</code> on a subsequent shrink in place operation. This function is 
useful to know exactly how many objects a caller should destroy with the given
<code>limit_size</code> and <code>preferred_size</code>.
</li>
<li>
If the parameter <code>command</code> only contains the value <code>expand_fwd</code> (with 
optional additional <code>nothrow_allocation</code>), the allocator will try to increase the 
size of the memory block referenced by pointer reuse moving only the end of the block to the 
value <code>preferred_size</code>. If it's not possible, it will try to increase the size of 
the memory block as much as possible as long as this results in <code>size(p) &gt;= limit_size</code>. 
Success is reported only if this results in <code>limit_size &lt;= size(p)</code>.
<li>
If the parameter <code>command</code> only contains the value <code>expand_bwd</code> (with 
optional additional <code>nothrow_allocation</code>), the allocator will try to increase the 
size of the memory block referenced by pointer <code>reuse_ptr</code> only moving the start of 
the block to a returned new position <code>new_ptr</code>. If it's not possible, it will try 
to move the start of the block as much as possible as long as this results in <code>size(new_ptr) 
&gt;= limit_size</code>. Success is reported only if this results in <code>limit_size &lt;= 
size(new_ptr)</code>.
<li>
If the parameter <code>command</code> only contains the value <code>allocate_new</code> (with 
optional additional <code>nothrow_allocation</code>), the allocator will try to allocate 
memory for <code>preferred_size</code> objects. If it's not possible it will try to allocate 
memory for at least <code>limit_size</code>
objects.
<li>
If the parameter <code>command</code> only contains a combination of <code>expand_fwd</code> and
<code>allocate_new</code>, (with optional additional <code>nothrow_allocation</code>) the 
allocator will try first the forward expansion. If this fails, it would try a new 
allocation.
<li>
If the parameter <code>command</code> only contains a combination of <code>expand_bwd</code> and
<code>allocate_new</code> (with optional additional <code>nothrow_allocation</code>), the 
allocator will try first to obtain <code>preferred_size</code> objects using both methods if 
necessary. If this fails, it will try to obtain <code>limit_size</code>
objects using both methods if necessary.
<li>
If the parameter <code>command</code> only contains a combination of <code>expand_fwd</code> and
<code>expand_bwd</code> (with optional additional <code>nothrow_allocation</code>), the 
allocator will try first forward expansion. If this fails it will try to obtain preferred_size 
objects using backwards expansion or a combination of forward and backwards expansion. If this 
fails, it will try to obtain <code>limit_size</code>
objects using both methods if necessary.
<li>
If the parameter <code>command</code> only contains a combination of allocation_new, <code>expand_fwd</code>
and <code>expand_bwd</code>, (with optional additional <code>nothrow_allocation</code>) the 
allocator will try first forward expansion. If this fails it will try to obtain preferred_size 
objects using new allocation, backwards expansion or a combination of forward and backwards 
expansion. If this fails, it will try to obtain <code>limit_size</code>
objects using the same methods.
<li>
The allocator always writes the size or the expanded/allocated/shrunk memory block in <code>received_size</code>. 
On failure the allocator writes in <code>received_size</code> a possibly successful <code>limit_size</code>
parameter for a new call.</li>
</ul>
<P></P>
<p>
<b>Throws an exception if two conditions are met</b>:
<ul>
<li>
The allocator is unable to allocate/expand/shrink the memory or there is an error in 
preconditions
<li>
The parameter command does not contain <code>nothrow_allocation</code>.</li>
</ul>
<P></P>
<p>
<b>This function returns</b>:
<ul>
<li>
The address of the allocated memory or the new address of the expanded memory as the first 
member of the pair. If the parameter command contains <code>nothrow_allocation</code>
the first member will be 0 if the allocation/expansion fails or there is an error in 
preconditions.
<li>
The second member of the pair will be false if the memory has been allocated, true if the 
memory has been expanded. If the first member is 0, the second member has an undefined value.</li>
</ul>
<P></P>
<p>
<b>Notes</b>:
<ul>
<li>
If the user chooses <code>char</code>
as template argument the returned buffer will be suitably aligned to hold any type.
<li>
If the user chooses <code>char</code> as template argument and a backwards expansion is 
performed, although properly aligned, the returned buffer might not be suitable because the 
distance between the new beginning and the old beginning might not multiple of the type the 
user wants to construct, since due to internal restrictions the expansion can be slightly 
bigger than the requested bytes. <b>When performing backwards expansion, if you have already 
constructed objects in the old buffer, it's important to correctly specify the type</b>.</li>
</ul>
</p>

<p>
Obviously, we need an underlying C function that offers these possibilities.
I've added the following function to the modified dlmalloc library:

<pre>

typedef struct boost_cont_command_ret_impl
{
   void *first;
   int   second;
}boost_cont_command_ret_t;

boost_cont_command_ret_t boost_cont_allocation_command
   ( allocation_type command   , size_t sizeof_object
   , size_t limit_objects      , size_t preferred_objects
   , size_t *received_objects  , void *reuse_ptr );
</pre>

<p>
This function does the same job as <code>allocation_command</code>, so it takes the
same options and an additional parameter indicating the size of the object (specially
important for backwards expansion). This function does not throw exceptions because C
has no exceptions but other than these two issues, its use is exactly the same
as <code>allocation_command</code>.

</p>
</p>

<h2><a name="AConfigurableAllocator"></a>A configurable allocator for testing</h2>

<p>
To compare apples to apples, we just can't compare the standard allocator versus an 
allocator based on <b>DLmalloc</b> with expansion capabilities. For this purpose we develop an 
allocator (called, surprisingly, <code>ba::allocator</code>). Here's the declaration:
</p>
<pre>

namespace boost {
namespace container {

template&lt;class T, unsigned Version = 2, unsigned int AllocationDisableMask = 0&gt;
class allocator;

} <cpp_comment>//namespace container {</cpp_comment>
} <cpp_comment>//namespace boost {</cpp_comment>

</pre>
<p>
The first template parameter is the type of the objects to be allocated. The second is 
the version of the allocator (see <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n1953.html">
N1953</a>) for details. If Version is 1, then the allocator is a standard conforming 
classic allocator around <code>dlmalloc</code> and <code>dlfree </code>with no 
additional features. If Version is 2, then the allocator has expansion capabilities and 
other features like specifying limit and preferred sizes and obtaining the real size of 
an allocated memory, separated node and array allocations, etc...
</p>
<p>
The third parameter is a mask that can be used to disable some capabilities. For 
example, specifying <code>expand_bwd | expand_fwd</code> will disable forward 
and backwards expansion and the allocator will only provide new buffer allocations.
</p>

<h2><a name="TestingForward"></a>Testing forward expansion in <code>vector</code></h2>

<p>
Let's first present the class that will be used in our performance tests. It's a simple 
wrapper over an <code>int</code>, with a copy constructor that realizes a job that will 
be equivalent to most move-constructors in C++0x. Of course performance gains will be 
bigger in copyable but non-moveable heavy objects, but I think this class is fair 
enough.
</p>
<pre>

class MyInt
{
   int int_;

   public:
   MyInt(int i = 0) : int_(i){}

   MyInt(const MyInt &amp;other)
      :  int_(other.int_)
   {}

   MyInt &amp; operator=(const MyInt &amp;other)
   {
      int_ = other.int_;
      return *this;
   }
};

</pre>
<p>
Now we'll push back an element one by one in a vector, and measure the time using 
<a href="http://www.boost.org/libs/date_time">Boost.DateTime</a>:
</p>
<pre>
   unsigned int numalloc = 0, numexpand = 0, capacity = 0;

   ptime tini = microsec_clock::universal_time();

   for(unsigned int r = 0; r != num_iterations; ++r){
      bi::vector&lt;MyInt, IntAllocator&gt; v;
      v.reset_alloc_stats();   <cpp_comment>//Reset allocation statistics</cpp_comment>
      for(unsigned int e = 0; e != num_elements; ++e)
         v.push_back(e);
      numalloc  += v.num_alloc;   numexpand += v.num_expand_fwd;
      capacity = static_cast&lt;unsigned int&gt;(v.capacity());
   }

   ptime tend = microsec_clock::universal_time();
   <cpp_comment>//...</cpp_comment>
</pre>
<p>
We'll test 4 different allocators:
<ol>
   <li><b>StdAllocator</b>: equivalent to <code>std::allocator&lt;int&gt;</code>, the standard 
      allocator that comes with the C++ compiler</li>
   <li><b>AllocatorPlusV1</b>: equivalent to <code>ba::allocator&lt;int, 1&gt;</code>, a STL 
      conformant allocator around <code>dlmalloc</code> and <code>dlfree</code>.</li>
   <li><b>AllocatorPlusV2Mask</b>: equivalent to <code>ba::allocator&lt;int, 2, expand_bwd | 
      expand_fwd&gt;</code>, an allocator with capabilities to specify a limit size the 
      preferred size and real size of a buffer, but with expansion capabilities disabled.</li>
   <li><b>AllocatorPlusV2</b>: equivalent to <code>ba::allocator&lt;int, 2&gt;</code>, a fully 
      equipped improved allocator. (Note: There is no need to disable backwards expansions because
      forward expansion has always preference over new allocations and backwards expansion).</li>
</ol>
</p>
<p>
and we'll use several <code>[num_iterations, num_elements]</code> combinations: <code>[10000, 
10000], [100000, 1000], [1000000, 100] and [10000000, 10]</code>
</p>
<p>
For each allocator we'll show the following information:
<ul>
   <li><code>push_back</code> speed (normalized to AllocatorPlusV1)</li>
   <li>The number of calls to the allocator per iteration</li>
   <li>The number of new allocations per iteration</li>
   <li>The number of expansion per iteration</li>
   <li>The final capacity of the vector</li>
</ul>
Now let's see the results of the test in a Windows XP machine (AMD 1.47 
Ghz) using Visual C++ 2003:
</p>
<img src="fwd_expansion_msvc-7.1_1.jpg"></img>
<img src="fwd_expansion_msvc-7.1_2.jpg"></img>
<img src="fwd_expansion_msvc-7.1_3.jpg"></img>
<p>
As we see in the picture, <b>DLmalloc</b> is faster than the standard allocator 
provided by MSVC 7.1 (StdAllocator vs. AllocatorPlus). We also see that
V2 allocators call less times to allocator functions (for size=10, 4 times vs. 6 times),
because using the <i>received_size</i> parameter of <code>allocation_command</code> the
container can take advantage of the real size of the newly allocated small buffer.
</p>

<p>
For big sizes (size=10000), due to the expansion capabilities of AllocatorPlusV2,
this is twice as fast as AllocatorPlusV2Mask and 2.5 times faster than the standard
conforming, <b>DLmalloc</b> based AllocatorPlusV1. As we can see in the third graph, only
AllocatorPlusV2 performs expand in place, whereas the rest only allocate new buffers.
</p>

<p>
Now we'll execute the same test in a Linux (Suse 10.1) virtual machine (VMWare 6) using gcc-4.1 compiler
and will check <code>push_back</code> times:
</p>

<img src="fwd_expansion_linux-gcc-4.1.jpg"></img>

<p>
We see that the standard allocator is faster than our V1 (a standard conforming <b>DLmalloc</b> wrapper)
by a small margin for some sizes and slightly slower for bigger sizes.
That's because gblic malloc is based on ptmalloc2, which is actually
based on <b>DLmalloc</b>, but uses per-thread-pools, so minimizes mutex locks.
</p>

<p>
Like in Windows, expand-in-place capable allocator is twice as fast as the V1 allocator,
confirming that forward expansion offers a considerable speed up. This speed up will be
noticeable in initialization phases when vectors are filled with many back insertions. The
speed-up will be more noticeable if vector elements are elements with no move constructors
(e.g. third-party classes than can't be updated with move semantics).
</p>

<h2><a name="TestingBackwards"></a>Testing backwards expansion in <code>vector</code></h2>

<p>
Historically backwards expansion has not been very used for allocation strategies, basically
because it does not offer the speed improvements offered by forward expansion for
usual vector implementations. Similarly to new allocation, we need to move old objects
(an implementation might avoid data-moving if it manages uninitialized data in the beginning of
the memory buffer, but that's not usual). Backwards expansion has the ability to reduce the peak
memory used by the vector (while allocating new memory, the vector has to maintain the old 
buffer until copies data to the new one) effectively reusing free memory placed just before the
buffer to be expanded.
</p>

<p>
As we've said before, the backwards expansion algorithm has to calculate the LCM (least common multiple)
of the memory allocation alignment (8 bytes in <b>DLmalloc</b>) and the size of the object. 
For very efficient algorithms like <b>DLmalloc</b>, I've found that calculating the LCM using the
traditional <a href="http://en.wikipedia.org/wiki/Euclidean_algorithm">Euclidean</a> algorithm was a big
overhead that made backwards expansion considerably slower than new
allocation. It's necessary to take advantage of the power of 2 memory alignment and implement
optimizations for object sizes also power of 2 or multiple of 2, 4, or 8 bytes, and use
optimized versions to calculate the LCM. Fortunately,
these optimizations made backwards expansion slightly faster than a new allocation, making
backwards expansion better than new allocation in every aspect.
</p>

<p>
To test backwards expansion we'll execute the same <code>push_back</code> test
using the following 4 allocators:

<ol>
<li>
<b>StdAllocator</b>: equivalent to <code>std::allocator&lt;int&gt;</code>, the standard 
allocator that comes with the C++ compiler
<li>
<b>AllocatorPlusV1</b>: equivalent to <code>ba::allocator&lt;int, 1&gt;</code>, a STL 
conformant allocator around <code>dlmalloc</code> and <code>dlfree</code>.
<li>
<b>AllocatorPlusV2Mask</b>: equivalent to <code>ba::allocator&lt;int, 2, expand_bwd | 
expand_fwd&gt;</code>, an allocator with capabilities to specify a limit size the 
preferred size and real size of a buffer, but with expansion capabilities disabled.
<li>
<b>AllocatorPlusV2</b>: equivalent to <code>ba::allocator&lt;int, 2, expand_fwd&gt;</code>,
a fully equipped improved allocator, that avoids forward expansion (forward expansion
has priority over backwards expansion, so we need to disable it to only measure backwards
expansion).</li>
</ol>

Now let's see the results of the test in a Windows XP machine (AMD 1.47 
Ghz) using Visual C++ 2003:
</p>

<img src="bwd_expansion_msvc-7.1.jpg"></img>

<p>
And now in a Linux (Suse 10.1) virtual machine (VMWare 6) using gcc-4.1 compiler:
</p>

<img src="bwd_expansion_linux-gcc-4.1.jpg"></img>

<p>
We see that for most cases backwards expansion (AllocatorPlusV2) is slightly faster than new
allocation (AllocatorPlusV1) and new allocation with <code>received_size</code> capabilities
(AllocatorPlusV2Mask).
</p>

<h2><a name="TestingShrinkToFit"></a>Testing shrink to fit in <code>vector</code></h2>

<p>
The <a href="http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Shrink-to-fit">
Shrink-to-fit</a> is a widely used idiom that can be improved with the <code>shrink_in_place"</code>
option provided by <code>allocation_command</code> method. To test this function we'll use the following test:
</p>

<pre>
   const std::size_t Step = 5;

   ptime tini = microsec_clock::universal_time();

   typedef boost::interprocess::detail::integral_constant
      &lt;unsigned, boost::interprocess::detail::version&lt;Allocator&gt;::value&gt; alloc_version;

   for(unsigned int r = 0; r != num_iterations; ++r){
      <cpp_comment>//Create a vector with num_elements size</cpp_comment>
      bi::vector&lt;MyInt, IntAllocator&gt; v(num_elements);
      v.reset_alloc_stats();
      <cpp_comment>//Empty the vector erasing the last Step elements and calling shrink_to_fit()</cpp_comment>
      for(unsigned int e = num_elements; e != 0; e -= Step){
         v.erase(v.end() - Step, v.end());
         v.shrink_to_fit();
      }
   }

   ptime tend = microsec_clock::universal_time();
</pre>

<p>
That is, we'll initialize the vector with an initial size and will erase the last Step elements (5 in this case),
and shrink to fit the capacity of the vector. We'll repeat this operation for different
<code>[num_iterations, num_elements]</code> combinations: <code>[100, 
10000], [1000, 200] and [10000, 500]</code>
</p>

<p>
To test shrink to fit we'll execute the test using the following 3 allocators:

<ol>
<li>
<b>StdAllocator</b>: equivalent to <code>std::allocator&lt;int&gt;</code>, the standard 
allocator that comes with the C++ compiler
<li>
<b>AllocatorPlusV1</b>: equivalent to <code>ba::allocator&lt;int, 1&gt;</code>, a STL 
conformant allocator around <code>dlmalloc</code> and <code>dlfree</code>.
<li>
<b>AllocatorPlusV2</b>: equivalent to <code>ba::allocator&lt;int, 2, expand_fwd&gt;</code>,
a fully equipped improved allocator with shrink to fit capacity.</li>
</ol>

Now let's see the results of the test in a Windows XP machine (AMD 1.47 
Ghz) using Visual C++ 2003:
</p>


<img src="shrink_to_fit_msvc-7.1_1.jpg"></img>
<img src="shrink_to_fit_msvc-7.1_2.jpg"></img>

<p>
As the initial vector size grows, <code>AllocatorPlusV2</code> shines (up to 90 times faster!)
because a shrink to fit operation is a <b>no-throw</b>
operation that can have <b>amortized constant-time</b> complexity due to the fact that an allocator usually implements
shrink to fit with a deallocation: the allocator divides the old buffer in two independent buffers and
deallocates the second buffer. On the other hand, vectors with allocators with no shrink to fit functionality
need to allocate a new buffer, move elements to the new one (moving elements is a linear operation
with the size of the vector) and deallocate the old buffer.
</p>
<p>
Now we'll execute the same test in a Linux (Suse 10.1) virtual machine (VMWare 6) using gcc-4.1 compiler
and will check <code>push_back</code> times:
</p>

<img src="shrink_to_fit_linux-gcc-4.1_1.jpg"></img>

<p>
Result are similar to Windows results, with <code>AllocatorPlusV2</code> shinning more and more when initial vector
size grows.
</p>

<h1><a name="Chapter2"></a>Chapter 2: Burst allocation for node containers</h1>

<h2><a name="BurstAllocationIntro"></a>Burst allocation and deallocation introduction</h2>

<p>
Many times, operations can be performed faster if they are packed in groups, because some
costs are not related to the number of operations, but to the number of groups. An example
of this operations is the <code>template<class Iterator> iterator insert(Iterator begin, Iterator end)</code>
operation in vectors. If <code>Iterator</code> is a forward iterator, most implementations
calculate the distance between <code>begin</code> and <code>end</code> so that they know
the size of the buffer needed to hold all those elements. With this information, they only
perform one allocation to insert all the range.
</p>

<p>
Other containers can't take advantage of this possibility because they are <b>node</b>
containers, meaning that each element is obtained with a call to the allocator that
allocates a single element. Since each element has its own independent memory, these
containers offer stronger iterator validity guarantees and pointers and references
to inserted objects are not easily invalidated. On the other hand, inserting N elements in
those containers, require N calls to the allocator.
</p>

<p>
To solve this, the idea is to have a function that could allocate a lot of objects in a single
call, guaranteeing that <b>each object can be individually deallocatable</b>. Some memory management
algorithms offer this possibility, even with the possibility to specify a different buffer size
for each operation in a group. For example, <b>DLmalloc</b> offers the following two functions:

<pre>
void** independent_calloc(size_t n_elements, size_t element_size, void*chunks[]);
</pre>

<p>
<b>Explanation</b>: <code>independent_calloc</code> is similar to calloc, but instead of returning a
single cleared space, it returns an array of pointers to n_elements
independent elements that can hold contents of size elem_size, each
of which starts out cleared, and can be independently freed,
realloc'ed etc. The elements are guaranteed to be adjacently
allocated (this is not guaranteed to occur with multiple callocs or
mallocs), which may also improve cache locality in some
applications. [...]
</p>

<pre>
void** independent_comalloc(size_t n_elements, size_t sizes[], void* chunks[]);
</pre>

<p>
<b>Explanation</b>: <code>independent_comalloc</code> allocates, all at once, a set of n_elements
chunks with sizes indicated in the "sizes" array. It returns an array of pointers to these elements,
each of which can be independently freed, realloc'ed etc. The elements are guaranteed to be
adjacently allocated (this is not guaranteed to occur with multiple callocs or mallocs),
which may also improve cache locality in some applications. [...]
</p>

<p>
For these two functions each element must be individually freed when it is no longer
needed and objects are guaranteed to be contiguously allocated (to help cache locality).
Also, the "chunks" argument is optional (i.e., may be null, which is
probably the most typical usage). If it is null, the returned array
is itself dynamically allocated and should also be freed when it is
no longer needed. Otherwise, the chunks array must be of at least
n_elements in length. It is filled in with the pointers to the
chunks.
</p>

<p>
According to <b>DLmalloc</b> documentation <code>independent_calloc</code>
simplifies and speeds up implementations of many
kinds of pools.  It may also be useful when constructing large data
structures that initially have a fixed number of fixed-sized nodes,
but the number is not known at compile time, and some of the nodes
may later need to be freed:
</p>

<pre>
  struct Node { int item; struct Node* next; };

  struct Node* build_list() {
    struct Node** pool;
    int n = read_number_of_nodes_needed();
    if (n &lt;= 0) return 0;
    pool = (struct Node**)(independent_calloc(n, sizeof(struct Node), 0);
    if (pool == 0) die();
    <cpp_comment>// organize into a linked list...</cpp_comment>
    struct Node* first = pool[0];
    for (i = 0; i &lt; n-1; ++i)
      pool[i]-&gt;next = pool[i+1];
    free(pool);     <cpp_comment>// Can now free the array (or not, if it is needed later)</cpp_comment>
    return first;
  }
</pre>

</p>

<p>
According to <b>DLmalloc</b> documentation <code>independent_comalloc</code>
can be used to speed up allocation in cases where several structs or
objects must always be allocated at the same time:

<pre>
  struct Head { ... }
  struct Foot { ... }

  void send_message(char* msg) {
    int msglen = strlen(msg);
    size_t sizes[3] = { sizeof(struct Head), msglen, sizeof(struct Foot) };
    void* chunks[3];
    if (independent_comalloc(3, sizes, chunks) == 0)
      die();
    struct Head* head = (struct Head*)(chunks[0]);
    char*        body = (char*)(chunks[1]);
    struct Foot* foot = (struct Foot*)(chunks[2]);
    <cpp_comment>// ...</cpp_comment>
  }
</pre>

</p>

<p>
Unfortunately, these two functions have some downsides when working with some containers:

<ul>
<li>Addresses of allocated nodes are passed in an array that is externally
provided or allocated by the function (so it must be deallocated after pointers to those
chunks have been used). This is quite rigid (a <code>list</code> is forced to allocate an
array to hold pointers to these nodes) and imposes an unnecessary overhead to node containers.</li>
<li>The guarantee that the memory will be contiguous is not necessary (although it might help)
for node containers and can require the allocation of quite big memory blocks. It's desirable to allocate
several contiguous chunks (say, a memory page) but not to force that all elements are
contiguous on memory.</li>
</ul>
</p>

<h2><a name="BurstAllocationInfrastructure"></a>Burst allocation infrastructure</h2>

<p>
The first issue (the required external or internally allocated array) can be avoided if every memory node is linked
with an intrusive singly linked list and offering an iterator-like interface. The intrusive list is built in the first bytes
reserved for the user data because the minimum node size guarantees 12 bytes for 32 bits systems and 24 bytes for 64 bit systems.
Obviously, once the user has overwritten that memory, there is no way to iterate to the next node so the programmer must be careful. Let's
present these new functions:

<pre>
<cpp_comment>/* Iterator functions */</cpp_comment>
typedef /**/ multialloc_it_t;                      <cpp_comment>/* Iterator type */</cpp_comment>
BOOST_CONTAINER_INIT_END_IT(IT)                    <cpp_comment>/* Postcondition: BOOST_CONTAINER_IS_END_IT(IT) != 0 */</cpp_comment>
BOOST_CONTAINER_IS_END_IT(IT)                      <cpp_comment>/* Is an end iterator? */</cpp_comment>
BOOST_CONTAINER_NEXT_IT(IT)                        <cpp_comment>/* operator ++ emulation */</cpp_comment>
BOOST_CONTAINER_ADDR_IT(IT)                        <cpp_comment>/* operator* emulation, returns the address of the memory */</cpp_comment>
</pre>

These functions offer an emulation of a C++ input iterator.

<pre>
<cpp_comment>/* Multiallocation chain functions */</cpp_comment>
typedef /**/ multialloc_it_chain_t;                <cpp_comment>/* Chain type */</cpp_comment>
BOOST_CONTAINER_IT_CHAIN_INIT(CHAIN)               <cpp_comment>/* Postcondition: BOOST_CONTAINER_IT_CHAIN_IT(IT_CHAIN) is end it */</cpp_comment>
BOOST_CONTAINER_IT_CHAIN_PUSH_BACK(CHAIN, MEM)     <cpp_comment>/* Push back the node MEM in the intrusive linked list chain */</cpp_comment>
BOOST_CONTAINER_IT_CHAIN_PUSH_FRONT(CHAIN, MEM)    <cpp_comment>/* Push front the node MEM in the intrusive linked list chain */</cpp_comment>
BOOST_CONTAINER_IT_CHAIN_SPLICE_BACK(CHAIN, CHAIN2)<cpp_comment>/* Splice chain2 in the end of the first chain */</cpp_comment>
BOOST_CONTAINER_IT_CHAIN_IT(IT_CHAIN)              <cpp_comment>/* Return an input iterator that traverse the chain */</cpp_comment>
BOOST_CONTAINER_IT_CHAIN_SIZE(IT_CHAIN)            <cpp_comment>/* Returns the size of the chain */</cpp_comment>
</pre>

These functions offer the possibility of building an intrusive chain (list) of nodes previously allocated by <b>DLmalloc</b>
and obtain an iterator that can traverse them. These functions are used to build a range that can be deleted
in a single call using with <code>boost_cont_multidealloc()</code> (See below).

<pre>
<cpp_comment>/* Some defines for "contiguous_elements" parameters */</cpp_comment>
DL_MULTIALLOC_ALL_CONTIGUOUS                       <cpp_comment>/* Allocate all elements contiguous on memory */</cpp_comment>
DL_MULTIALLOC_DEFAULT_CONTIGUOUS                   <cpp_comment>/* The implementation chooses the maximum contiguous elements</cpp_comment>
<cpp_comment>
/* Burst allocation: 
   Allocates "n_elements nodes" of size "elem_size" with a maximum (if possible) of "contiguous_elements".
   On success returns an input iterator. On failure returns an "end" iterator.*/</cpp_comment>
multialloc_it_t boost_cont_multialloc_nodes (size_t n_elements, size_t elem_size, size_t contiguous_elements);
<cpp_comment>
/* Burst allocation: 
   Allocates "n_arrays" of elements, whose size is "sizeof_element" and whose lengths are specified in the "sizes"
   parameter. Contiguous memory won't be (if possible) bigger than "contiguous_elements"*sizeof_element.
   On success returns an input iterator. On failure returns an "end" iterator.*/</cpp_comment>
multialloc_it_t boost_cont_multialloc_arrays (size_t n_arrays, const size_t *lengths, size_t sizeof_element, size_t contiguous_elements);
<cpp_comment>
/* Burst deallocation: deallocates all the range specified by the iterator */</cpp_comment>
void boost_cont_multidealloc(multialloc_it_t it);
</pre>
</p>
<p>
<code>boost_cont_multialloc_nodes</code> is a function that allocates multiple nodes of the same size in the same call returning
an iterator to the range. </code> is a function that allocates multiple arrays in the same call returning
an iterator to the range. The user can specify the (suggested) contiguous number of elements in both functions.
<code>boost_cont_multidealloc</code> deallocates a range of arrays or nodes that have been allocated
by <code>boost_cont_multialloc_nodes</code>, <code>boost_cont_multialloc_arrays</code> or allocated by other <b>DLmalloc</b> functions and
chained using <code>BOOST_CONTAINER_IT_CHAIN_XXX</code> functions.
</p>

<h2><a name="BurstInterfaceForAllocators"></a>Burst interface for allocators</h2>

<p>
Now that we have a low-level swiss knife we can define a C++ interface for burst allocation:

<pre>
template&lt;class T&gt;
class allocator
{
   <cpp_comment>//...</cpp_comment>
   typedef /**/ multiallocation_iterator; <cpp_comment>//wrapper over multialloc_it_t</cpp_comment>
   typedef /**/ multiallocation_chain;    <cpp_comment>//wrapper over multialloc_chain_t</cpp_comment>
<cpp_comment>
   //------------------------------------------------------------------
   // Functions for array allocation (expandable memory)
   //------------------------------------------------------------------

   //Allocates many elements of size elem_size in a contiguous block
   //of memory. Elements must be individually deallocated with deallocate()</cpp_comment>
   multiallocation_iterator allocate_many(size_type elem_size, size_type n_elements);
<cpp_comment>
   //Allocates n_elements elements, each one of size elem_sizes[i] 
   //Elements must be individually deallocated with deallocate()</cpp_comment>
   multiallocation_iterator allocate_many(const size_type *elem_sizes, size_type n_elements);
<cpp_comment>
   //Deallocates the memory arrays pointed by the iterator range starting at it</cpp_comment>
   void deallocate_many(multiallocation_iterator it);
<cpp_comment>
   //------------------------------------------------------------------
   //Functions for node (size == 1, non-expandable memory) allocation 
   //------------------------------------------------------------------

   //Allocates many elements of size == 1.
   //Elements must be individually deallocated with deallocate_one()</cpp_comment>
   multiallocation_iterator allocate_individual(size_type num_elements);
<cpp_comment>
   //Deallocates the memory nodes pointed by the iterator range starting at it</cpp_comment>
   void deallocate_individual(multiallocation_iterator it);
};
</pre>

Burst allocation also follows the separation between node (size == 1) and array allocation functions
so that node allocation can use segregated storage mechanisms, whereas array allocation can use
another approach that can be used with expansion functions (<code>allocation_command</code>).
</p>

<p>
We have all that we need to implement burst allocation and deallocation for STL containers. Now, let's measure
if this leads to any speed improvement.
</p>

<h2><a name="TestingBurstAllocation"></a>Testing burst allocation</h2>

<p>
A good place to take advantage of burst allocation is range insertion. Other operations(range assignment,
copy construction) are also good places to implement burst allocation. So let's try this code with several
allocators to test insertion time:
</p>

<pre>
   tini = microsec_clock::universal_time();

   bi::list&lt;MyInt, IntAllocator&gt; l;
   for(unsigned int r = 0; r != num_iterations; ++r){
      l.insert(l.end(), num_elements, MyInt(r));
   }

   tend = microsec_clock::universal_time();
</pre>


<p>
That is, we'll try insertions of different range sizes (<code>num_elements</code>) and different
iterations (<code>num_iterations</code>). We'll end up with the same number of elements in the
list, so we can compare results easier. To measure burst deallocation we'll preprocess several
iterator ranges and measure the time to completely empty the list: 
</p>

<pre>
      <cpp_comment>//Now preprocess ranges to erase</cpp_comment>
      std::vector &lt;typename bi::list&lt;MyInt, IntAllocator&gt;::iterator&gt; ranges_to_erase;
      ranges_to_erase.push_back(l.begin());

      for(unsigned int r = 0; r != num_iterations; ++r){
         typename bi::list&lt;MyInt, IntAllocator&gt;::iterator next_pos(ranges_to_erase[r]);
         std::size_t n = num_elements;
         while(n--){ ++next_pos; }
         ranges_to_erase.push_back(next_pos);
      }

      <cpp_comment>//Measure range erasure function</cpp_comment>
      tini = microsec_clock::universal_time();

      for(unsigned int r = 0; r != num_iterations; ++r){
         l.erase(ranges_to_erase[r], ranges_to_erase[r+1]);
      }

      tend = microsec_clock::universal_time();
</pre>

<p>
As shown, a good place to take advantage of burst allocation is range erasure along with <code>clear()</code> and destructors.
</p>


<p>
We'll test 3 different allocators:
<ol>
   <li><b>StdAllocator</b>: equivalent to <code>std::allocator&lt;int&gt;</code>, the standard 
      allocator that comes with the C++ compiler</li>
   <li><b>AllocatorPlusV1</b>: equivalent to <code>ba::allocator&lt;int, 1&gt;</code>, a STL 
      conformant allocator around <code>dlmalloc</code> and <code>dlfree</code> (no burst allocation).</li>
   <li><b>AllocatorPlusV2</b>: equivalent to <code>ba::allocator&lt;int, 2&gt;</code>, burst-allocation capable
      allocator.</li>
</ol>
and we'll use these test values for <code>[num_iterations, num_elements]</code> combinations: <code>[200, 
10000], [2000, 1000], [20000, 100] and [200000, 10]</code>
</p>

<p>
For each allocator we'll show the following information:
<ul>
   <li>Insertion speed per element (normalized to AllocatorPlusV1 speed)</li>
   <li>Erasure speed per element (normalized to AllocatorPlusV1 speed)</li>
</ul>
Now let's see the results of the test in a Windows XP machine (AMD 1.47 
Ghz) using Visual C++ 2003:
</p>
<img src="burst_allocation_msvc-7.1_1.jpg"></img>

<p>
The graph shows that allocation speed per element is constant for AllocatorPlusV1 (surprisingly, not so constant for
Visual's standard allocator) and this is pretty logical since each element needs an allocation. AllocatorPlusV2 is
using <code>DL_MULTIALLOC_DEFAULT_CONTIGUOUS</code> which allocates contiguous nodes up to 4096 bytes. That's why
AllocatorPlusV2 performance stabilizes for big ranges reaching to a 3x speedup.
</p>

<img src="burst_allocation_msvc-7.1_2.jpg"></img>

<p>
Burst deallocation improvements are more modest, why?

<ul>
   <li>Each burst-allocated node is individually
   deallocatable and <code>list</code> must construct a node chain (iterating through all nodes) before calling
   <code>deallocate_individual</code>. That is, we must traverse nodes twice (once to construct the chain and
   another one internally in the internal C function)</li>
   <li>Burst allocation takes advantage of locality allocating big blocks of memory (in a single heap memory search)
   and creating chunks from this block. Burst deallocation must be able to deallocate nodes that can be far apart, so
   there is no chance to replace N allocations with a single, big deallocation.</li>
</ul>
</p>

<p>
This means that burst deallocation is just a loop of plain deallocations, amortizing costs like mutex locks, checks,
function calls and returns and other common operations. Nevertheless, the test shows a 30% speedup when comparing to
node to node deallocation with quite big ranges (>1000) and still noticeable speedups (20%) with small ranges (>=10).
</p>

<p>
Now we'll execute the same test in a Linux (Suse 10.1) virtual machine (VMWare 6) using gcc-4.1:
</p>

<img src="burst_allocation_linux-gcc-4.1_1.jpg"></img>

<img src="burst_allocation_linux-gcc-4.1_2.jpg"></img>

<p>
In Linux, the standard allocator (ptmalloc2, based on DLmalloc) performs very well, and the lock reduction
achieved by its per-thread-pool approach offers a similar performance than burst allocation (AllocatorPlusV2)
for small ranges. With bigger ranges (>=100) AllocatorPlusV2 maintains (more or less) its performance while
StdAllocator can't keep up. Like in WinXP Visual 7.1 tests, AllocatorPlusV2 is faster (up to 2x in some cases)
than AllocatorPlusV1 for big ranges.
</p>

<p>
Burst deallocation is not faster than the standard allocator in Linux, surely because ptmalloc2 reduces
mutex locks just like burst deallocation, but burst deallocation needs to traverse the range to be
erased twice as explained before. When comparing to AllocatorPlusV1, that is, a wrapper around
<code>dlmalloc</code> and <code>dlfree</code> burst deallocation (AllocatorPlusV2) is 30% faster for
big ranges.
</p>

<h1><a name="Chapter3"></a>Chapter 3: Adaptive pools for node containers</h1>

<h2><a name="SimpleSegregatedStorage"></a>Simple segregated storage: advantages and problems</h2>

<p>
When developing allocators to improve node containers performance, one of the widely used approaches
is using simple segregated storage (e.g. <a href="http://www.boost.org/libs/pool">Boost.Pool</a>).
The idea behind simple segregated storage is to divide a big memory portion ("block") allocated through a
general purpose memory manager into fixed-size "nodes".
A memory manager ("pool") holds a list of "blocks" and a singly linked list of free nodes.
Since all nodes are the same size, Simple Segregated Storage can't serve nodes of different sizes.
To solve this, an application creates a different pool for each size and usually different types with the same size
share the same pool.
</p>

<p>
The free node list is intrusive (since the node is free, the list pointer is built in the node),
so that <b>there is no size overhead</b> for each node. Simple Segregated Storage is very fast:
<ul>
   <li>Allocation: take the first free node of the free list.
      If there are no free nodes, a new block is allocated and partitioned. Complexity: amortized O(1)</li>
   <li>Deallocation: put the node in the front of the free list. Complexity: O(1)</li>
</ul>
</p>

<p>
Now the question is: when does the pool return fully free "blocks" (allocated each time the free node list was empty) to the
general purpose memory manager so that they can be reused by other pools or non-pool allocators? The common answer is: <b>usually never</b>.
Why? Simple Segregated Storage erases all bookeeping data from the node and this makes "block" deallocation very expensive: the pool
needs to traverse the free nodes list (nodes are <b>not</b> ordered by their original block) and check the address of each node
against the address of each "block" just to check if all the nodes of a "block" are free and the block can be safely deallocated. If
we have B blocks and N nodes, this is a O(N*B) operation. Usually N (and B) can be quite high and this operation can take <b>minutes</b>
if the application has allocated several tens of thousands of nodes. That's why the option to trim the memory is provided only as
an <b>optional</b> and <b>explicit</b> operation.
</p>

<p>
A manual operation makes allocator changes non-portable (How is that manual trimming activated? How long does it take?) so many
applications just ignore this operation. This also has a drawback: if an application allocates a big amount of nodes in a container
and then destroys the container, the pool will hold a big list of free nodes but that memory won't be reused by any other container
not using a pool with the same node size, leading to excessive memory waste.
</p>

<h2><a name="AdaptivePoolsExplained"></a>Adaptive pools explained</h2>

<h3>Adaptive pool and memory alignment</h3>

<p>
Can we obtain the small size overhead of simple segregated storage and a fast, continuous, memory trimming operation?
The idea behind adaptive pools is simple: when deallocating a node, an efficient self-trimming node pool needs
a fast access to the "block" containing the node. If so, the pool can check how many free nodes are in that "block"
(the "block" has a header storing the number of free blocks) and if all nodes are free, the pool can return
that block to the general purpose memory manager.
</p>

<p>
The problem is that storing bookeeping data (a pointer to the block containing that node) in a node has
a size overhead. But there is an extremely fast way to obtain a pointer to the block containing the node with zero
size overhead per-node: <b>memory alignment</b>.
</p>

<p>
Suppose a 4KB "block" also aligned to 4KB: a node just needs a mask operation to obtain the address of the block.
Once we have it, we can check the number of free nodes and deallocate the block if all the nodes are free. And once we
have fast access to the "block" we can develop a more complicated trimming scheme that favors the creation of free "blocks"
and improves trimming probabilities.
</p>

<h3>Alignment can lead to excessive memory waste</h3>

<p>
Allocators usually have a payload that store bookeeping information for deallocation. If an adaptive pool allocates 4KB
bytes aligned to 4KB, it will really allocate 4KB + payload bytes where the start of the non-payload section is aligned
to 4KB. This means that the memory adjacent to this aligned block won't be suitable for a new 4KB aligned allocation leading
to excessive memory waste (a 4KB allocation would waste next 4KB - payload bytes of memory).
</p>

</p>
There is a simple solution: allocate (4KB - payload bytes) bytes aligned to 4KB. This simple
solution make contiguous memory usable for a future 4KB aligned allocation leading to optimal memory usage. That's why
knowing the size of the payload is important for aligned memory allocations where the size of the allocation is not fixed.
</p>


<h3>Managing blocks</h3>

<p>
Once we have access from a node to its block, we can implement different policies for them.
Once a block is completely free we can just deallocate it but the allocation pattern
could lead to several half-used blocks, increasing in practice the memory usage
we'll trying to minimize.
</p>

<p>
Alternatively, we can implement a strategy in order to minimize the number of in-use
blocks. We can try to allocate nodes always from blocks with less free nodes,
leading, at least in theory, to more fully used blocks and more fully free blocks that
could be deallocated faster. Obviously, this block management must be very fast: searching for the
most used  block can ruin performance, specially when comparing with the efficient
simple segregated storage. The same happens when deallocating a node: if block
management needs too many operations deallocation can have very bad performance.
</p>

<h3>Minimizing alignment requirements</h3>

<p>
Adaptive pools can require excessive alignment requirements when many nodes are allocated in a chunk.
If a block is configured to hold at least 256 nodes and the size of a node is 24 bytes, the required size
for the block is (block payload + 20*256) > 4096 -> 8196 bytes, which also needs 8196 bytes alignment. This
means that increasing the number of nodes a block can hold also increases the required alignment.
</p>

<p>
We can improve the excessive alignment if we divide each block in sub-blocks. The first sub-block contains the
payload
of the block and the rest of sub-blocks just store a pointer to the first sub-block. This leads to an
additional degree of freedom: block overhead. The overhead per block (number of nodes * node size)/ (block size)
determines the size (and the alignment) of a sub-block, whereas the number of nodes per block determines the
number of sub-blocks per block. This trick maintains the alignment requirement <b>lower</b>
than the approach that does not use sub-blocks and <b>constant</b> with the number of nodes per block.
On the other hand, deallocation requires an additional indirection (from node to subblock and from subblock
to the block header) and a bit more complicated block initialization.
</p>

<h2><a name="PresentingAdaptivePool"></a>Presenting <code>adaptive_pool</code></h2>

<p>
<code>adaptive_pool</code> is built above the aligned memory allocation function
from our modified <b>DLmalloc</b> library. Alignment must be power of two:

<pre>
void* boost_cont_memalign(size_t bytes, size_t alignment);
</pre>
</p>

<p>
<code>adaptive_pool</code> is implemented as follows:

<ul>
<li>Each block stores an intrusive free node singly linked list.</li>
<li>The pool stores an intrusive red-black tree of blocks with free nodes, ordered by free node count.</li>
<li>The pool deallocates all fully free blocks except a number of previously configured
number of blocks.</li>
<li>The user can configure the overhead per block so that alignment requirements can be minimized.</li>
</ul>
</p>

<p>This is the declaration of <code>adaptive_pool</code>:
<pre>
const std::size_t ADP_nodes_per_block    = unspecified;
const std::size_t ADP_max_free_blocks    = unspecified
const std::size_t ADP_overhead_percent   = unspecified;
const std::size_t ADP_only_alignment     = unspecified;

template &lt; class T
         , unsigned Version            = 2
         , std::size_t NodesPerBlock   = ADP_nodes_per_block
         , std::size_t MaxFreeBlocks   = ADP_max_free_blocks
         , std::size_t OverheadPercent = ADP_overhead_percent
         &gt;
class adaptive_pool;
</pre>
</p>

<p>
Description of the template parameters:
<ol>
<li>The type of the objects to be allocated</li>
<li>The version of the allocator. Similarly to other allocators presented in previous chapters,
   Version 2 allocator offers burst-allocation.</li>
<li><i>NodesPerBlock</i> is the minimum number of nodes per block we want
   to allocate.</li>
<li><i>MaxFreeBlocks</i> is the maximum number of fully free blocks that the pool will hold
   in the intrusive red-black tree (the rest of fully free blocks will be deallocated).</li>
<li><i>OverheadPercent</i> is a parameter that will configure the maximum memory overhead that each
   block will have (and thus, the number of sub-blocks).
   If this parameter is equal to <code>ADP_only_alignment</code>, then no sub-blocks will be used
   and <i>NodesPerBlock</i> will determine the alignment required by blocks.</li>
</ol>
</p>

<p>
To allocate a new node <code>adaptive_pool</code> needs to:
<ul>
<li>See if there are free blocks in the tree. If not, it should allocate a new one.</li>
<li>Take the first node of the first block</li>
<li>Decrease the count of fully free blocks if the block was fully free.</li>
<li>Erase the block from the tree if the block has no free nodes.</li>
</ul>
</p>

<p>
To deallocate a new node <code>adaptive_pool</code> needs to:
<ul>
<li>Obtain the block from the node.</li>
<li>Put the node in the free list of the block.</li>
<li>Reorder the block tree according to the free node count.</li>
<li>Increase the count of fully free blocks if the block is now fully free.</li>
<li>Insert the block in the tree if the free node count has passed from 0 to 1.</li>
<li>Deallocate remaining fully free nodes if the fully free block count is higher than the configured limit.</li>
</ul>
</p>

<h2><a name="TestingAdaptivePool"></a>Testing <code>adaptive_pool</code></h2>

<p>
In this test we'll measure 8 allocators:

<ol>
   <li><b>AllocatorPlusV1</b>: equivalent to <code>ba::allocator&lt;int, 1&gt;</code>, a STL 
      conformant allocator around <code>dlmalloc</code> and <code>dlfree</code> (no burst allocation).</li>
   <li><b>AllocatorPlusV2</b>: equivalent to <code>ba::allocator&lt;int, 2&gt;</code>, burst-allocation capable
      allocator.</li>
   <li><b>AdPoolAlignOnlyV1</b>: an adaptive pool with no sub-blocks and no burst allocation.</li>
   <li><b>AdPoolAlignOnlyV2</b>: an adaptive pool with no sub-blocks but with burst allocation.</li>
   <li><b>AdPool2PercentV1</b>: an adaptive pool with sub-blocks (configured with 2% overhead per block) and no burst allocation.</li>  
   <li><b>AdPool2PercentV2</b>: an adaptive pool with sub-blocks (configured with 2% overhead per block) but with burst allocation.</li>  
   <li><b>SimpleSegregatedStorageV1</b>: a classical simple segregated storage allocator with no burst allocation.</li>  
   <li><b>SimpleSegregatedStorageV2</b>: a classical simple segregated storage allocator with burst allocation.</li>  
</ol>
</p>

<p>
All pool allocators (allocators 3-8) are configured with a minimum of 256 nodes per block.
For a int list, in 32 bit systems, the required alignment
is 4096 bytes for 3 and 4 allocators (adaptive pools with no sub-blocks) and 2048 bytes for 5 and 6 allocators
(adaptive pools with sub-blocks).
</p>

<p>
For each allocator we'll show the following information:
<ul>
   <li><b>Insertion speed</b> per element (normalized to AllocatorPlusV1 speed)</li>
   <li><b>Erasure speed</b> per element (normalized to AllocatorPlusV1 speed)</li>
   <li><b>System bytes after insertion</b>: the amount of system bytes (memory cached by dlmalloc 
      from the operating system) reported by dlmalloc after inserting all elements</code></li>
   <li><b>Memory overhead</b>: The amount (in percentage) of payload memory per node</code></li>
   <li><b>In use bytes after erasure</b>: the amount of bytes privately hold by allocators
      after erasing all elements</code></li>
   <li><b>System bytes after erasure</b>: the amount of system bytes (memory cached by dlmalloc 
      from the operating system) reported by dlmalloc after erasing all elements</code></li>
   </li>
</ul>
</p>

<p>The test will be the same as the used in the section <a href="#TestingBurstAllocation">Testing burst allocation</a>.
Now let's see the results of the test in a Windows XP machine (AMD 1.47 Ghz) using Visual C++ 2003:
</p>

<img src="adaptive_pool_msvc-7.1_1.jpg"></img>

<p>
In general, insertion speeds are better for pool allocators than for <b>DLmalloc</b>. Analyzing only V1 allocators
(no burst allocation) we can see that <code>SimpleSegregatedStorageV1</code> performs best, followed by
<code>AdPoolAlignOnlyV1</code> and <code>AllocPool2PercentV1</code>. This is quite logical: simple
segregated storage minimizes all block operations because it does
not deallocate free blocks on the fly. <code>AdPoolAlignOnlyV1</code> and <code>AllocPool2PercentV1</code>
perform quite similarly.
</p>

<p>
When comparing V2 allocators (burst-allocation enabled) we see that performance of all pool allocators is very
similar. The reason for this is that the slightly higher costs of adaptive pools are practically minimized when the pool
allocates several nodes in a row: all needed blocks are preallocated in a single call, error handling in loops can be
simplified leading to tighter loops... Curiously, the performance of a burst-enabled <b>DLmalloc</b>
(<code>AllocatorPlusV2</code>) is on par with pools with burst allocation. This is not a surprise: <b>DLmalloc</b> burst
allocation uses exactly the same mechanism as pools: allocating big block and create nodes from them. The difference, though,
is in the overhead per node.
</p>

<img src="adaptive_pool_msvc-7.1_2.jpg"></img>

<p>
When analyzing deallocations, results for V1 allocators are pretty similar, with <code>SimpleSegregatedStorageV1</code>
leading the test. We must remember that <code>SimpleSegregatedStorageV1</code> does not perform any block managing
and deallocation, so this is the expected behavior. Adaptive pool deallocation times are exactly between <b>DLmalloc</b>
(<code>AllocatorPlusV1</code>) and segregated storage times. Adaptive pools perform a quite complicated block management
ordering free blocks by free node count and deallocating completely free blocks if the number of them is superior to 
a configure value (2 blocks in this test). As expected <code>AdPoolAlignOnlyV2</code> performs marginally better than
<code>AllocPool2PercentV2</code> because deallocating a node allocated with <code>AllocPool2PercentV2</code> (adaptive
pool with sub-blocks) needs an additional indirection to obtain the block header from the node (first we reach the
header of the sub-block and this header stores the address of the block header).
</p>

<p>
When analyzing burst-enabled allocators, however, the picture changes so that adaptive pools perform similarly to
than <code>SimpleSegregatedStorageV2</code>. Again, burst-allocation allows the elimination of some redundant checks
and management operations that yield to better performance and amortization of block managing costs. 
</p>

<p>
For big ranges, V2 (burst) adaptive pools are even better than simple segregated storage by a small margin.
</p>

<img src="adaptive_pool_msvc-7.1_3.jpg"></img>

<p>
Now let's see the amount of memory allocated from the operating system by <b>DLmalloc</b> with each allocator
(V1 and V2 practically allocate the same amount of memory so we only represent V2 allocators). <b>DLmalloc</b>
wrapper (<code>AllocatorPlusV2</code>) allocates more memory than the rest of allocators. Allocated nodes are
12 byte in 32 bit systems so <b>DLmalloc</b> needs 12 byte + overhead (4 bytes) = 16 bytes per node. With 2000000
nodes this yields to aproximately 32 MB. For pools, since overhead is practically inexistent the total memory 
is around 24MB (12 bytes per node).
</p>

<img src="adaptive_pool_msvc-7.1_4.jpg"></img>
<p>
In the previous graph we can see the overhead (in percentage) for each allocator. As expected, <b>DLmalloc</b>
offers a 33% overhead (4 bytes per each 12 byte node) whereas pools offer less than 2% overhead (remember that
we've configured <code>AllocPool2PercentVX</code> with a maximum of 2 percent overhead). As expected,
<code>SimpleSegregatedStorageVX</code> offers the minimum overhead but adaptive pools (with their additional
headers for block management) are also very size-efficient.
</p>

<img src="adaptive_pool_msvc-7.1_5.jpg"></img>
<p>
The previous picture shows how much in-use memory <b>DLmalloc</b> reports after deallocating all the nodes.
Note that this does not count the OS memory <b>DLmalloc</b> still caches, but memory that have been allocated
through <b>DLmalloc</b> and has not been deallocated yet through <b>DLmalloc</b>. In other words, memory still
hold by pools.
</p>

<p>
As expected <b>DLmalloc</b> wrappers (<code>AllocatorPlusVX</code>) have deallocated all their memory (no leaks!).
Also, simple segregated storage allocators have not deallocated a single block so they still hold all the memory.
Adaptive pools (<code>AdPoolAlignOnlyVX</code> and <code>AllocPool2PercentVX</code>) only hold the number of blocks
we've configured: 2 blocks x 4096 bytes per block = 8192 bytes. So we clearly show that adaptive pools
not only offer a low memory overhead and fast allocation times, but they also return memory to the underlying
memory allocator!
</p>

<p>
Once allocators return memory to <b>DLmalloc</b>, <b>DLmalloc</b> can trim its cached OS memory and return
that memory to the OS. The following graph shows the amount of memory <b>DLmalloc</b> caches after all
nodes have been deallocated:
</p>

<img src="adaptive_pool_msvc-7.1_6.jpg"></img>

<p>
Cached memory usually depends on the number of contiguous free pages that <b>DLmalloc</b> holds
internally so numbers are not important. The only thing that this graph shows is that adaptive pools,
after returning memory to <b>DLmalloc</b>, encourage also memory trimming so that <b>DLmalloc</b> can
return memory to the operating system.
</p>

<p>
Now we'll execute the same test in a Linux (Suse 10.1) virtual machine (VMWare 6) using gcc-4.1:
</p>

<img src="adaptive_pool_linux-gcc-4.1_1.jpg"></img>

<p>
Insertion speeds for V1 allocators are more or less the expected with segregated storage leading the group
and non-pool <b>DLmalloc</b> wrapper being the slowest one. V2 (burst) allocators are faster with similar
performance between adaptive pools and simple segregated storage as the number of the insertion range grows.
</p>

<img src="adaptive_pool_linux-gcc-4.1_2.jpg"></img>

<p>
Erasure speeds are similar to Windows, with <code>SimpleSegregatedStorageVX</code> leading the test, 
and adaptive pools come next.
</p>

</body>
</html>