-
-
Notifications
You must be signed in to change notification settings - Fork 3
/
MassBlockAlloc.pas
3258 lines (2781 loc) · 111 KB
/
MassBlockAlloc.pas
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
{-------------------------------------------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-------------------------------------------------------------------------------}
{===============================================================================
MassBlockAlloc
This library was designed for situation, where a large number of relatively
small blocks of equal size is rapidly allocated and deallocated in multi
thread environment and where performance is important.
As rapid (de)allocation of small data can put default memory manager under
a significant load, this library was created to offload this operation from
it and (potentially) improve performance.
The blocks are not trully allocated and deallocated. The allocator (an
instance of class TMassBlockAlloc) allocates memory in large chunks called
segments, where each segment can hold many blocks. When a new block is
required, it is only selected from a list of unused blocks in a segment.
When block is freed, it is only marked as unused in its parent segment.
Since the memory is managed completely within this library, it is also
possible to force the allocator to allocate all the blocks with specific
address alignment (eg. for use in vector instructions such as SSE or AVX).
Version 1.0 (2024-04-14)
Last change 2024-10-04
©2024 František Milt
Contacts:
František Milt: [email protected]
Support:
If you find this code useful, please consider supporting its author(s) by
making a small donation using the following link(s):
https://www.paypal.me/FMilt
Changelog:
For detailed changelog and history please refer to this git repository:
github.com/TheLazyTomcat/Lib.MassBlockAlloc
Dependencies:
AuxClasses - github.com/TheLazyTomcat/Lib.AuxClasses
* AuxExceptions - github.com/TheLazyTomcat/Lib.AuxExceptions
AuxMath - github.com/TheLazyTomcat/Lib.AuxMath
AuxTypes - github.com/TheLazyTomcat/Lib.AuxTypes
BitOps - github.com/TheLazyTomcat/Lib.BitOps
BitVector - github.com/TheLazyTomcat/Lib.BitVector
Library AuxExceptions is required only when rebasing local exception classes
(see symbol MassBlockAlloc_UseAuxExceptions for details).
Library AuxExceptions might also be required as an indirect dependency.
Indirect dependencies:
BasicUIM - github.com/TheLazyTomcat/Lib.BasicUIM
BinaryStreamingLite - github.com/TheLazyTomcat/Lib.BinaryStreamingLite
SimpleCPUID - github.com/TheLazyTomcat/Lib.SimpleCPUID
StrRect - github.com/TheLazyTomcat/Lib.StrRect
UInt64Utils - github.com/TheLazyTomcat/Lib.UInt64Utils
WinFileInfo - github.com/TheLazyTomcat/Lib.WinFileInfo
===============================================================================}
unit MassBlockAlloc;
{
MassBlockAlloc_UseAuxExceptions
If you want library-specific exceptions to be based on more advanced classes
provided by AuxExceptions library instead of basic Exception class, and don't
want to or cannot change code in this unit, you can define global symbol
MassBlockAlloc_UseAuxExceptions to achieve this.
}
{$IF Defined(MassBlockAlloc_UseAuxExceptions)}
{$DEFINE UseAuxExceptions}
{$IFEND}
//------------------------------------------------------------------------------
{$IF defined(CPU64) or defined(CPU64BITS)}
{$DEFINE CPU64bit}
{$ELSEIF defined(CPU16)}
{$MESSAGE FATAL '16bit CPU not supported'}
{$ELSE}
{$DEFINE CPU32bit}
{$IFEND}
{$IF Defined(WINDOWS) or Defined(MSWINDOWS)}
{$DEFINE Windows}
{$ELSEIF Defined(LINUX) and Defined(FPC)}
{$DEFINE Linux}
{$ELSE}
{$MESSAGE FATAL 'Unsupported operating system.'}
{$IFEND}
{$IFDEF FPC}
{$MODE ObjFPC}
{$MODESWITCH DuplicateLocals+}
{$DEFINE FPC_DisableWarns}
{$MACRO ON}
{$ENDIF}
{$H+}
//------------------------------------------------------------------------------
{
AllowLargeSegments
Quadruples maximum allowed size of segment.
Segment size is normally limited to 256MiB (1GiB in 64bit builds), so by
defining this symbol this limit is increased to 1GiB (4GiB in 64bit builds).
Not defined by default.
To enable/define this symbol in a project without changing this library,
define project-wide symbol MassBlockAlloc_AllowLargeSegments_On.
}
{$UNDEF AllowLargeSegments}
{$IFDEF MassBlockAlloc_AllowLargeSegments_On}
{$DEFINE AllowLargeSegments}
{$ENDIF}
interface
uses
SysUtils, Classes, SyncObjs,
AuxTypes, AuxClasses, BitVector, BitOps
{$IFDEF UseAuxExceptions}, AuxExceptions{$ENDIF};
{===============================================================================
Library-specific exception
===============================================================================}
type
EMBAException = class({$IFDEF UseAuxExceptions}EAEGeneralException{$ELSE}Exception{$ENDIF});
EMBASystemError = class(EMBAException);
EMBAInvalidValue = class(EMBAException);
EMBAInvalidState = class(EMBAException);
EMBAInvalidAddress = class(EMBAException);
EMBAInvalidAction = class(EMBAException);
EMBAIndexOutOfBounds = class(EMBAException);
EMBAOutOfResources = class(EMBAException);
{===============================================================================
Public constants
===============================================================================}
const
OneKiB = TMemSize(1024); // one kibibyte (2^10, kilobyte for you oldschools :P)
OneMiB = TMemSize(1024 * OneKiB); // one mebibyte (2^20, megabyte)
OneGiB = TMemSize(1024 * OneMiB); // one gibibyte (2^30, gigabyte)
const
// maximum size of segment (differs according to system and AllowLargeSegments symbol)
MBA_MAX_SEG_SZ = TMemSize({$IFDEF AllowLargeSegments}4 * {$ENDIF}{$IFDEF CPU64bit}OneGiB{$ELSE}256 * OneMiB{$ENDIF});
// maximum size of one block
MBA_MAX_BLK_SZ = TMemSize(128 * OneMiB);
{===============================================================================
--------------------------------------------------------------------------------
TMBASegment
--------------------------------------------------------------------------------
===============================================================================}
type
// used to return burst allocation
TMBAPointerArray = array of Pointer;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
type
{
TMBASegmentSizingStyle
Selects method calculating size of segment when one is being created.
szMinCount - Size is selected so that the segment will be large enough to
fit at least the prescribed number of blocks. Note that the
segment size is calculated with page size granularity.
BlockCount must be larger than zero and resulting size must
not exceed MBA_MAX_SEG_SZ.
szMinSize - Segment will be at least as large as prescribed (ie. never
smaller). Calculated with page size granularity.
MemorySize must be larger than zero and smaller or equal to
MBA_MAX_SEG_SZ.
szExactSize - Segment will have exactly the given size, irrespective of its
value or page size.
The prescribed size must be large enough to accomodate at
least one block (note that, if MapInSegment is true, this
also includes the allocation map - which is one byte per 8
blocks) and smaller or equal to MBA_MAX_SEG_SZ.
}
TMBASegmentSizingStyle = (szMinCount,szMinSize,szExactSize);
{
TMBASegmentSettings
Used to store and pass settings of a segment (instance of TMBASegment).
FailOnUnfreed - When the segment is being destroyed, there are still some
allocated/unfreed blocks and this option is true, then an
exception of class EMBAInvalidState is raised in destructor.
When false, then the unfreed blocks are ignored and
destructor proceeds normally.
default value - True
MapInSegment - When true, then the internal map of allocated blocks is
stored in the segment memory (that is, in the same memory
space as blocks).
Otherwise memory for the map is allocated separately.
default value - False
BlockSize - Requested size of the block, in bytes. Note that size
reserved in the segment for one block might be (much)
larger, depending on selected memory alignment (padding).
Must be larger than zero and smaller than MBA_MAX_BLK_SZ.
default value - 0 - Must be set by the user!
Alignment - Each block within the segment is guaranteed to have this
memory alignment.
If block size is not an integral multiple of alignment
bytes, then a padding is created between the blocks to
ensure proper alignment of consecutive blocks - this
creates a wasted space, so be aware of it!
You can use this eg. when allocating vectors used in calls
to SSE/AVX.
default value - maNone (no alignment)
SizingStyle - See description of type TMBASegmentSizingStyle.
default value - szMinCount
BlockCount - Requested number of blocks. Observed only for SizingStyle
of szMinCount (see description of TMBASegmentSizingStyle
for details).
default value - 0 - Must be set by the user!
MemorySize - Requested size of segment memory. Observed only for
SizingStyle of szMinSize and szExactSize (see description
of TMBASegmentSizingStyle for details).
default value - <none> (not observed for szMinCount)
}
TMBASegmentSettings = record
FailOnUnfreed: Boolean;
MapInSegment: Boolean;
BlockSize: TMemSize;
Alignment: TMemoryAlignment;
case SizingStyle: TMBASegmentSizingStyle of
szMinCount: (BlockCount: Integer);
szMinSize,
szExactSize: (MemorySize: TMemSize);
end;
{===============================================================================
TMBASegment - class declaration
===============================================================================}
type
TMBASegment = class(TCustomListObject)
protected
fSettings: TMBASegmentSettings;
fReservedBlockSize: TMemSize;
fBlockCount: Integer;
fMemorySize: TMemSize;
fMemory: Pointer;
fAllocationMap: TBitVectorStatic;
fLowAddress: Pointer; // address of first block
fHighAddress: Pointer; // address BEHIND the last block (reserved size)
// getters, setters
Function GetIsAllocated(Index: Integer): Boolean; virtual;
Function GetBlockAddress(Index: Integer): Pointer; virtual;
// list methods
Function GetCapacity: Integer; override;
procedure SetCapacity(Value: Integer); override;
Function GetCount: Integer; override;
procedure SetCount(Value: Integer); override;
// init/final
Function CalculateMemorySize: TMemSize; virtual;
Function CalculateBlockCount: Integer; virtual;
procedure Initialize(const Settings: TMBASegmentSettings); virtual;
procedure Finalize; virtual;
// auxiliary methods and utilities
Function FindSpaceForBuffer(BufferSize: TMemSize; out RequiredBlockCount: Integer): Integer; virtual;
public
{
MemoryPageSize
Returns size of memory page (in bytes) as indicated by operating system for
the calling process.
}
class Function MemoryPageSize: TMemSize; virtual;
constructor Create(const Settings: TMBASegmentSettings);
destructor Destroy; override;
{
LowIndex
Index of first block in Blocks array property.
}
Function LowIndex: Integer; override;
{
HighIndex
Index of last block in Blocks array property.
}
Function HighIndex: Integer; override;
{
BufferBlockCount
Returns number of blocks withing this segment needed to allocate a buffer
of given size (note that the full RESERVED block size is taken into account
in this calculation).
NOTE - it might return a number larger than BlockCount, this is not
checked as this function is considered to be only informative.
}
Function BufferBlockCount(BufferSize: TMemSize): Integer; overload; virtual;
//- address checking -------------------------------------------------------
{
AddressOwned
Returns true when the given address is within any present block (points to
a byte that is part of the block), false otherwise.
When strict is true, then the address must be within its indicated size
(Settings.BlockSize), when strict is false then it can lay anywhere within
reserved block size (ie. it can point into block padding).
}
Function AddressOwned(Address: Pointer; Strict: Boolean = False): Boolean; virtual;
{
AddressIndexOf
Returns index of block to which the given address points.
If strict is true, then only indicated block size is observed, otherwise
the address can be anywhere within reserved memory of the block.
When the address does not point to any present block (or points to padding
when Strict is true), then a negative value is returned.
}
Function AddressIndexOf(Address: Pointer; Strict: Boolean = False): Integer; virtual;
{
Finds block to which given address points, stores its index in Index output
parameter and returns true. If no block overlapping with the given address
can be found, then the function returns false and value of Index is
undefined.
Refer to method AddressIndexOf for explanation of parameter Strict.
}
Function AddressFind(Address: Pointer; Strict: Boolean; out Index: Integer): Boolean; virtual;
{
BlockOwned
Returns true when the given address points to any present block (its
starting address), false otherwise.
}
Function BlockOwned(Block: Pointer): Boolean; virtual;
{
BlockIndexOf
Returns index of block with the given starting address. If the address does
not point to any block, then a negative value is returned.
}
Function BlockIndexOf(Block: Pointer): Integer; virtual;
{
BlockFind
Finds block with the given starting address, stores its index in Index
output parameter and returns true. If no block with given address can be
found, then the function returns false and value of Index is undefined.
}
Function BlockFind(Block: Pointer; out Index: Integer): Boolean; virtual;
//- basic (de)allocation ---------------------------------------------------
{
AllocateBlock
Selects first unallocated block, marks it as allocated and sets Block param
to its address. If no block can be allocated, then an exception of class
EMBAOutOfResources is raised.
Can also raise an EMBAInvalidState exception if internal data are somehow
damaged.
When init memory is set to true, then the block memory is cleared (filled
with zeroes), otherwise its content is completely undefined and might
contain content from when it was previously allocated.
}
procedure AllocateBlock(out Block: Pointer; InitMemory: Boolean); virtual;
{
FreeBlock
Marks given block as not allocated and sets Block parameter to nil.
If given pointer does not point to a block withing this segment, then an
exception of class EMBAInvalidAddress is raised.
If freeing buffer that is not allocated, then an EMBAInvalidState exception
is raised.
}
procedure FreeBlock(var Block: Pointer); virtual;
{
CanAllocateBuffer
Returns true if this segment can allocate buffer of given size, false
otherwise.
}
Function CanAllocateBuffer(BufferSize: TMemSize): Boolean; virtual;
{
TryAllocateBuffer
Tries to allocate buffer of given size. True is returned when it succeeds,
false otherwise - in which case nothing is allocated and value of Buffer is
undefined.
}
Function TryAllocateBuffer(out Buffer: Pointer; BufferSize: TMemSize; InitMemory: Boolean): Boolean; virtual;
{
AllocateBuffer
Allocates buffer of given size.
Buffer size must be larger than zero, otherwise an EMBAInvalidValue
exception is raised.
If it cannot be allocated (eg. because there is not enough contiguous free
blocks), then EMBAOutOfResources exception is raised.
}
procedure AllocateBuffer(out Buffer: Pointer; BufferSize: TMemSize; InitMemory: Boolean); virtual;
{
FreeBuffer
Deallocates all constituent blocks of given buffer and sets parameter Buffer
to nil.
If the Buffer does not point to any block within this segment, then an
EMBAInvalidAddress exception is raised.
BufferSize must be larger than zero, otherwise an EMBAInvalidValue exception
is raised.
If the buffer size is not a valid number (eg. it is not the same number as
was used during allocation), then an EMBAInvalidValue or EMBAInvalidState
exception can be raised.
}
procedure FreeBuffer(var Buffer: Pointer; BufferSize: TMemSize); virtual;
{
AllocateAll
If the segment is empty, then it marks all blocks as allocated and returns
their addresses in Blocks output parameter (in the order they appear in the
segment) - practically allocating all blocks in one go.
If this segment is not empty, then an EMBAInvalidState exception is raised.
InitMemory set to true ensures that all blcck will be zeroed, otherwise
their memory can contain bogus data.
}
procedure AllocateAll(out Blocks: TMBAPointerArray; InitMemory: Boolean); virtual;
//- informative methods (names should be self-explanatory) -----------------
Function IsFull: Boolean; virtual;
Function IsEmpty: Boolean; virtual;
Function AllocatedBlockCount: Integer; virtual;
Function FreeBlockCount: Integer; virtual;
//- memory statistics ------------------------------------------------------
Function ReservedMemory: TMemSize; virtual; // memory reserved for all blocks (including potential block padding)
Function BlocksMemory: TMemSize; virtual; // memory of all blocks (excluding padding, so only BlockCount * BlockSize)
Function AllocatedMemory: TMemSize; virtual; // memory of allocated blocks (excluding padding)
Function WastedMemory: TMemSize; virtual; // all padding (including padding of individual blocks)
Function MemoryEfficiency: Double; virtual; // (MemorySize - WastedMemory) / MemorySize (ignores unused space of buffers/vectors)
Function MemoryUtilization: Double; virtual; // AllocatedMemory / BlocksMemory (ignores unused space of buffers/vectors)
//- properties -------------------------------------------------------------
property Capacity: Integer read GetCapacity;
property Count: Integer read GetCount;
property Settings: TMBASegmentSettings read fSettings;
property ReservedBlockSize: TMemSize read fReservedBlockSize;
property BlockCount: Integer read fBlockCount;
property MemorySize: TMemSize read fMemorySize;
property Memory: Pointer read fMemory;
property AllocationMap[Index: Integer]: Boolean read GetIsAllocated;
property Blocks[Index: Integer]: Pointer read GetBlockAddress; default;
end;
{===============================================================================
--------------------------------------------------------------------------------
TMassBlockAlloc
--------------------------------------------------------------------------------
===============================================================================}
type
{
TMBAAllocatorSettings
Used to store and pass settings of an allocator (TMassBlockAlloc instance).
FreeEmptySegments - When a block is freed and a segment in which it was
freed becomes empty (ie. has no more allocated
blocks), then this segment is immediately freed and
removed when this option is set to true.
When it is set to false, then the empty segment is
kept for further use.
You should carefully consider whether to enable or
disable this option. It can significantly increase
performance, but at the cost of memory space - decide
what is more important to your particular use case.
default value - False
ThreadProtection - Enables thread protection of the allocator state. You
should always leave this option enabled (true). Only
if you are 100% sure the allocator will be used
within a signle thread, then you might disable it.
Must be set to true if asynchronous cache filling is
to be enabled.
default value - True
BlockCacheSettings - Settings for block caching.
This mechanism pre-allocates number of blocks (count
depends on cache length) and stores them in the cache.
Later, when allocating a block using cached methods,
it is only taken from the cache and returned - this
is usually much faster than normally allocating it.
This also applies to block freeing - instead of
returning it to the segment, is is only placed into
the cache for later re-use.
But note that the cache is not automatically refilled
or emptied (when overfilled) - this must be done
manually by calling CacheFill method, but you can
select a convenient time when to do this.
.Enable - Enables (true) or disables (false) block caching.
default value - True
.Length - Length of the block cache (number of blocks it can
store).
Note that the actual size of the cache is double of
this number, but only this number of blocks can ever
be pre-allocated. This is to leave a space for
blocks returned by cache-freeing them.
Must be bigger than zero (when caching is enabled).
default value - 128
.TrustedReturns - When true, the blocks returned to cache-enabled
freeing are just put into the cache without checking
them for validity. When set to false, all blocks,
before being put to the cache, are checked.
Make sure you enable this option if pointers
returned are not 100% guaranteed to be valid block
pointers.
default value - True
.AsynchronousFill - Settings for asynchronous cache filling.
A background thread is spawned and this thread will,
either on-demand (a call to CacheFill) or after a
timeout (CycleLength), automatically fill the cache.
For this to work, the cache must be enabled and also
the thread protection must be enabled.
..Enable - Enables asynchronous filling.
default value - False
..Interruptable - When true, then the async. cache filling (blocks
allocation and deallocation) can be interrupted,
otherwise it will run until completion.
The async. filling runs in a thread and is
protected by thread lock, the same lock that
de/allocating functions are also acquiring. This
means that, while this is running (which might be
relatively long time), no allocating or
deallocating function can enter the section and
so they will block the call. This might be
undesirable, and for this an iterrupts are
implemented.
When enabled - if an async. filling is in process
and a de/allocating function is called, then this
call will set a flag that is constantly checked by
the filling.
When the filling evaluates the flag as being set
(meaning de/alloc. function is waiting to enter),
it will immediatelly stop operation and exit,
allowing the d/a function to continue and do its
work.
The async. filling is then completed in next
iteration of its cycle (after a timeout or when
demanded).
default value - True
..CycleLength - Length (in milliseconds) or timeout of one async.
cache filling cycle.
The filling thread waits until a manual demand to
do the filling is made (by calling CacheFill), but
this waiting is not infinite. It will timeout
after the CycleLength milliseconds and perform the
filling at that point automatically (if needed).
default value - 1000 (ms, one second)
..PassExceptions - Enables passing of cache filling exceptions from
asynchronous filling thread to the allocator. See
cache exceptions for more details.
default value - False
}
TMBAAllocatorSettings = record
FreeEmptySegments: Boolean;
ThreadProtection: Boolean;
BlockCacheSettings: record
Enable: Boolean;
Length: Integer;
TrustedReturns: Boolean;
AsynchronousFill: record
Enable: Boolean;
Interruptable: Boolean;
CycleLength: UInt32;
PassExceptions: Boolean;
end;
end;
SegmentSettings: TMBASegmentSettings;
end;
const
DefaultAllocatorSettings: TMBAAllocatorSettings = (
FreeEmptySegments: False;
ThreadProtection: True;
BlockCacheSettings: (
Enable: True;
Length: 128;
TrustedReturns: True;
AsynchronousFill: (
Enable: False;
Interruptable: True;
CycleLength: 1000;
PassExceptions: False));
SegmentSettings: (
FailOnUnfreed: True;
MapInSegment: False;
BlockSize: 0;
Alignment: maNone;
SizingStyle: szMinCount;
BlockCount: 0));
//------------------------------------------------------------------------------
type
// used to return information when validating a block
TMBAValidationInfo = record
Address: Pointer;
Owned: Boolean;
Allocated: Boolean;
SegmentIndex: Integer; // negative for not-owned blocks
SegmentObject: TMBASegment; // nil for not-owned blocks
BlockIndex: Integer; // negative for not-owned blocks
end;
TMBAValidationInfoArray = array of TMBAValidationInfo;
{===============================================================================
TMassBlockAlloc - class declaration
===============================================================================}
type
TMassBlockAlloc = class(TCustomListObject)
protected
fSettings: TMBAAllocatorSettings;
fSegments: array of TMBASegment;
fSegmentCount: Integer;
fThreadLock: TCriticalSection;
fCache: record
Enabled: Boolean;
Data: array of Pointer; // must be thread protected
Count: Integer; // -||-
AsyncFill: record
Enabled: Boolean;
Interrupts: Boolean;
InterruptFlag: Integer; // interlocked access only
CycleEvent: TEvent;
FillerThread: TThread;
Exceptions: record // thread protect entire subrecord
Objects: array[0..255] of TObject;
Count: Integer;
Start: Integer;
end;
end;
end;
// getters, setters
Function GetSegment(Index: Integer): TMBASegment; virtual;
// inherited list methods
Function GetCapacity: Integer; override;
procedure SetCapacity(Value: Integer); override;
Function GetCount: Integer; override;
procedure SetCount(Value: Integer); override;
// internal list management
Function AddSegment: Integer; virtual;
procedure DeleteSegment(Index: Integer); virtual;
procedure ClearSegments; virtual;
// init/final
procedure Initialize(Settings: TMBAAllocatorSettings); virtual;
procedure Finalize; virtual;
// other internals
Function InternalCheckBlocks(var Blocks: array of Pointer; out FaultIndex: Integer): Boolean; virtual;
procedure InternalAllocateBlock(out Block: Pointer; InitMemory: Boolean); virtual;
procedure InternalFreeBlock(var Block: Pointer); virtual;
procedure InternalAllocateBlocks(out Blocks: array of Pointer; InitMemory: Boolean); virtual;
procedure InternalFreeBlocks(var Blocks: array of Pointer); virtual;
Function InternalCacheFill(AsyncFill: Boolean): Integer; virtual;
// cache exceptions intarnals
procedure CacheExceptionsAdd(ExceptObject: TObject); virtual;
public
constructor Create(Settings: TMBAAllocatorSettings); overload;
{
Create(BlockSize,MinBlocksPerSegment,MemoryAlingment)
Other settings (those not given by the function arguments) are set from
DefaultAllocatorSettings constant.
}
constructor Create(BlockSize: TMemSize; MinBlocksPerSegment: Integer; MemoryAlignment: TMemoryAlignment = maNone); overload;
destructor Destroy; override;
{
LowIndex
Lowest valid index for Segments array property.
}
Function LowIndex: Integer; override;
{
HighIndex
Highest valid index for Segments array property.
}
Function HighIndex: Integer; override;
{
ThreadLockAcquire
Locks the thread protection - a critical section protecting data integrity
of the allocator in multi-threaded environment. While the lock is in effect,
no other thread can lock it - a try to do so will block until the lock is
realeased by a thread that holds the lock.
Can be called recursively in single thread, but each call to
ThreadLockAcquire must be paired by a call to ThreadLockRelease to
successfully unlock the section.
Use this mechanism in multi-threaded environment when you are about to do
number of calls and don't want the sequence to be interrupted by requests
from other threads.
}
procedure ThreadLockAcquire; virtual;
{
ThreadLockRelease
Unlocks the thread protection.
See ThreadLockAcquire for more details.
}
procedure ThreadLockRelease; virtual;
{
AllocationAcquire
Causes interrupt to asynchronous cache filling (if enabled)] and locks the
thread protection (see ThreadLockAcquire for more details).
Can be called recursively, but each call must be paired by a call to
AllocationRelease to release the lock and disable interrupt.
}
procedure AllocationAcquire; virtual;
{
AllocationRelease
Releases thread lock and decrements async. fill interrupt counter (in that
order).
}
procedure AllocationRelease; virtual;
//- block validation (checking) --------------------------------------------
{
ValidateBlock
Returns validation information of the given block in ValidationInfo output
argument.
WARNING - the returned data might not be valid by the time the function
returns if the allocator is used by multiple threads. If you
want to work with the data (and especially with the segment
object) make sure to do the call and data processing inside
a thread lock (ThreadLockAcquire, ThreadLockRelease).
}
procedure ValidateBlock(Block: Pointer; out ValidationInfo: TMBAValidationInfo); overload; virtual;
{
ValidateBlock
Returns true when the block is deemed valid, which means it is both owned
by this allocator (ie. the address actually points to start of a block
within existing segment owned by this object) and allocated (was previously
allocated by this allocator), false otherwise.
}
Function ValidateBlock(Block: Pointer): Boolean; overload; virtual;
{
ValidateBlocks
Returns validation information for all given blocks in ValidationInfo
output array argument.
The ValidationInfo array will have the same length as Blocks and
information for each block will be placed at corresponding index in the
output (Block[n] -> ValidationInfo[n]).
See ValidateBlock for information about multi-threading issues.
}
procedure ValidateBlocks(const Blocks: array of Pointer; out ValidationInfo: TMBAValidationInfoArray); overload; virtual;
{
ValidateBlocks
Returns true when all given blocks are owned by this allocator and are also
all allocated, false otherwise.
If no block is passed in the Block array, then false is returned.
}
Function ValidateBlocks(const Blocks: array of Pointer): Boolean; overload; virtual;
//- single block (de)allocation --------------------------------------------
{
AllocateBlock
Finds not allocated (free) block in existing segments and returns its
address. If no free block is found, then new segment is added and the block
is allocated in this segment.
If InitMemory is set to true, then the returned block is zeroed, otherwise
its content is completely undefined and may contain any data.
}
procedure AllocateBlock(out Block: Pointer; InitMemory: Boolean = False); overload; virtual;
Function AllocateBlock(InitMemory: Boolean = False): Pointer; overload; virtual;
{
FreeBlock
Finds segment that owns the passed block and frees it using this segment.
The variable pointed to by Block argument is set to nil.
If the given address does not point to any block within existing segments,
then an EMBAInvalidAddress exception is raised.
}
procedure FreeBlock(var Block: Pointer); virtual;
//- multiple blocks (de)allocation -----------------------------------------
{
AllocateBlocks
Allocates multiple blocks to fill the given array.
Init memory set to true ensures memory of all allocated blocks is zeroed.
Note that it is more efficient (faster) to use this function instead of
multiple calls to AllocateBlock.
}
procedure AllocateBlocks(out Blocks: array of Pointer; InitMemory: Boolean = False); virtual;
{
FreeBlocks
Frees given blocks and sets them to nil.
All given blocks are first checked for validity. If any is deemed invalid
(eg. does not belong to any existing segment), then an EMBAInvalidAddress
exception is raised and NONE of the blocks is freed.
Note that there is a slight possibility that an exception will be raised
even is some of the blocks are freed. You can easily discern that this
happened because the exception will be of class EMBAInvalidValue. Also,
you can probe which blocks were freed and which were not - the freed ones
will be already set to nil, the unfreed will still have their addresses.
}
procedure FreeBlocks(var Blocks: array of Pointer); virtual;
//- buffer (de)allocation --------------------------------------------------
{
AllocateBuffer
Allocates general memory buffer using the segments.
The allocation is done by finding contiguous sequence of blocks that
together give the required size - they are then all marked as allocated and
the buffer is simply overlayed on those blocks.
The buffer size must be larger than zero, otherwise an EMBAInvalidValue
exception is raised. It must also fit into the segments (its size must be
smaller or equal to reserved memory of a single segment), otherwise an
exception of class EMBAOutOfResources is raised.
If InitMemory is set to true, then the buffer memory is cleared (set to all
zero), otherwise it can contain bogus data.
Note that the buffer (the address) will be aligned according to memory
alignment given in settings.
WARNING - to free the buffer, do NOT use standard memory management
functions or methods for blocks freeing, always use FreeBuffer
method and make sure you pass the same size there as is passed
here.
}
procedure AllocateBuffer(out Buffer: Pointer; BufferSize: TMemSize; InitMemory: Boolean = False); virtual;
{
FreeBuffer
Frees the general memory buffer that was previously allocated using
AllocateBuffer.
Make sure you pass the same size as was passed when allocating the buffer.
}
procedure FreeBuffer(var Buffer: Pointer; BufferSize: TMemSize); virtual;
//- block vector (de)allocation --------------------------------------------
{
AllocateBlockVector
Allocates a contigous array (vector) of blocks, each of the size given in
settings (SegmentSettings.BlockSize) - this is equivalent to allocating
a buffer of size VectorLength * BlockSize.
The blocks are byte-packed, there is no padding between them. Therefore,
only the first block (the address of vector) is guaranteed to have proper
alignment, consecutive blocks start just where the previous ones ended.
The VectorLength must be larger than zero, otherwise an EMBAInvalidValue
exception is raised. Also, all limits in effect for AllocateBuffer apply
to this call.
WARNING - always use FreeBlockVector to free the returned address and
make sure to pass the same length as was used in allocation.
}
procedure AllocateBlockVector(out Vector: Pointer; VectorLength: Integer; InitMemory: Boolean = False); virtual;
{
FreeBlockVector
Frees vector of blocks allocated by AllocateBlockVector - equivalent to
calling FreeBuffer with BufferSize set to VectorLength * BlockSize.
}
procedure FreeBlockVector(var Vector: Pointer; VectorLength: Integer); virtual;
//- cache management -------------------------------------------------------
{
--- Block cache ---
This mechanism is here to speed-up short allocation and deallocation bursts
(also works for random scattered de/allocations).
It works by pre-allocating given number of blocks and storing them in a
simple array (the cache). Then, when cached allocation is called, it merely
takes this pre-allocated block from the array and returns it, which is
usually much faster than full allocation (search for free block, marking
it allocated, ...).
Deallocation works the same way - the returned block is only put into the
cache and left there for future use, it is not classically freed.
But note that the cache must be also re-filled/cleaned from time to time.
You can either do it manually (by calling CacheFill), or, by enabling
asynchronous cache filling, leave it on a background thread.
}
{
CacheCount
Returns number of pre-allocated blocks available in block cache.
Note that, if the allocator is used by multiple threads, the number might
not reflect reality by the time the function returns, simply because other
threads can use the cache immediately as it is unlocked.
To ensure the number stays correct, you have to thread-lock the allocator
(ThreadLockAcquire) prior to calling CacheCount (remember to unlock it
when you are done by calling ThreadLockRelease, otherwise the allocator
could not serve other threads).
}
Function CacheCount: Integer; virtual;
{
CacheFill
Checks number of pre-allocated blocks present in the block cache. If it is
lower than cache length given in settings, then new blocks are allocated
and placed in the cache. If it is higher, then blocks over the given number
are freed.
To put it simpy, this function ensures that the cache contains exactly the
number of pre-allocated blocks as indicated in settings.
If ForceSynchronous is true or when asynchronous filling is disabled, then
the action is done directly in the calling context (ie. "here and now").
If ForceSynchronous is false and asynchronous filling is enabled, then no
action is performed, the call only signals filler thread to do the filling.
Note that if the cache is disabled, this function does nothing and returns
immediately.
}
procedure CacheFill(ForceSynchronous: Boolean = False); virtual;
//- cache eceptions management ---------------------------------------------
{