405 lines
11 KiB
ObjectPascal
405 lines
11 KiB
ObjectPascal
unit FastMM4DataCollector;
|
|
|
|
{$I FastMM4Options.inc}
|
|
|
|
interface
|
|
|
|
type
|
|
TStaticCollector = record
|
|
strict private const
|
|
CDefaultPromoteGen1_sec = 1; // promote every second
|
|
CDefaultPromoteGen1Count = 1; // promote allocations with Count > 1
|
|
CGeneration1Size = 1024;
|
|
CGeneration2Size = 256;
|
|
CCollectedDataSize = CGeneration2Size;
|
|
CMaxPointers = 11; // same as in FastMM4
|
|
public type
|
|
TPointers = record
|
|
Pointers: array [1..CMaxPointers] of Pointer;
|
|
Count : integer;
|
|
class operator Equal(const a, b: TPointers): boolean;
|
|
end;
|
|
TDataInfo = record
|
|
Data : TPointers;
|
|
Count: integer;
|
|
end;
|
|
TCollectedData = array [1..CCollectedDataSize] of TDataInfo;
|
|
TGenerationOverflowCount = record
|
|
Generation1: integer;
|
|
Generation2: integer;
|
|
end;
|
|
strict private type
|
|
PDataInfo = ^TDataInfo;
|
|
TGenerationPlaceholder = array [1..1] of TDataInfo;
|
|
PGenerationPlaceholder = ^TGenerationPlaceholder;
|
|
TGenerationInfo = record
|
|
Data : PGenerationPlaceholder;
|
|
Size : integer;
|
|
Last : integer;
|
|
NextGeneration : integer;
|
|
PromoteEvery_sec: integer;
|
|
PromoteCountOver: integer;
|
|
OverflowCount : integer;
|
|
LastCheck_ms : int64;
|
|
end;
|
|
var
|
|
FGeneration1 : array [1..CGeneration1Size] of TDataInfo;
|
|
FGeneration2 : array [1..CGeneration2Size] of TDataInfo;
|
|
FGenerationInfo: array [0..2] of TGenerationInfo; //gen0 is used for merging
|
|
FLocked : ByteBool;
|
|
FPadding : array [1..3] of byte;
|
|
function GetGen1_PromoteCountOver: integer;
|
|
function GetGen1_PromoteEvery_sec: integer;
|
|
function GetOverflowCount: TGenerationOverflowCount;
|
|
procedure Lock;
|
|
function Now_ms: int64; inline;
|
|
procedure SetGen1_PromoteCountOver(const value: integer);
|
|
procedure SetGen1_PromoteEvery_sec(const value: integer);
|
|
private
|
|
procedure AddToGeneration(generation: integer; const aData: TPointers;
|
|
count: integer = 1);
|
|
procedure CheckPromoteGeneration(generation: integer); inline;
|
|
function FindInGeneration(generation: integer; const aData: TPointers): integer; inline;
|
|
function FindInsertionPoint(generation, count: integer): integer; inline;
|
|
procedure FlushAllGenerations;
|
|
function InsertIntoGeneration(generation: integer; const dataInfo: TDataInfo): boolean;
|
|
procedure PromoteGeneration(oldGen, newGen: integer);
|
|
procedure ResortGeneration(generation, idxData: integer);
|
|
public
|
|
procedure Initialize;
|
|
procedure Add(const pointers: pointer; count: integer);
|
|
procedure GetData(var data: TCollectedData; var count: integer);
|
|
procedure Merge(var mergedData: TCollectedData; var mergedCount: integer;
|
|
const newData: TCollectedData; newCount: integer);
|
|
property Gen1_PromoteCountOver: integer read GetGen1_PromoteCountOver
|
|
write SetGen1_PromoteCountOver;
|
|
property OverflowCount: TGenerationOverflowCount read GetOverflowCount;
|
|
property Gen1_PromoteEvery_sec: integer read GetGen1_PromoteEvery_sec write
|
|
SetGen1_PromoteEvery_sec;
|
|
end;
|
|
PStaticCollector = ^TStaticCollector;
|
|
|
|
implementation
|
|
|
|
uses
|
|
Winapi.Windows; //used in Now_ms
|
|
|
|
{$RANGECHECKS OFF}
|
|
|
|
type
|
|
PByteBool = ^ByteBool;
|
|
|
|
// Copied from FastMM4.pas
|
|
function LockCmpxchg8(CompareVal, NewVal: ByteBool; AAddress: PByteBool): ByteBool;
|
|
asm
|
|
{$if SizeOf(Pointer) = 4}
|
|
{On entry:
|
|
al = CompareVal,
|
|
dl = NewVal,
|
|
ecx = AAddress}
|
|
{$ifndef LINUX}
|
|
lock cmpxchg [ecx], dl
|
|
{$else}
|
|
{Workaround for Kylix compiler bug}
|
|
db $F0, $0F, $B0, $11
|
|
{$endif}
|
|
{$else}
|
|
{On entry:
|
|
cl = CompareVal
|
|
dl = NewVal
|
|
r8 = AAddress}
|
|
.noframe
|
|
mov rax, rcx
|
|
lock cmpxchg [r8], dl
|
|
{$ifend}
|
|
end;
|
|
|
|
{ TStaticCollector.TPointers }
|
|
|
|
class operator TStaticCollector.TPointers.Equal(const a, b: TPointers): boolean;
|
|
var
|
|
i: integer;
|
|
begin
|
|
Result := a.Count = b.Count;
|
|
if Result then
|
|
for i := 1 to a.Count do
|
|
if a.Pointers[i] <> b.Pointers[i] then
|
|
Exit(false);
|
|
end;
|
|
|
|
{ TStaticCollector }
|
|
|
|
procedure TStaticCollector.Add(const pointers: pointer; count: integer);
|
|
var
|
|
ptrData: TPointers;
|
|
begin
|
|
Lock;
|
|
ptrData.Count := CMaxPointers;
|
|
if count < CMaxPointers then
|
|
ptrData.Count := count;
|
|
Move(pointers^, ptrData.Pointers[1], ptrData.Count * SizeOf(pointer));
|
|
AddToGeneration(1, ptrData);
|
|
FLocked := false;
|
|
end;
|
|
|
|
procedure TStaticCollector.AddToGeneration(generation: integer; const aData: TPointers;
|
|
count: integer = 1);
|
|
var
|
|
dataInfo: TDataInfo;
|
|
idxData : integer;
|
|
begin
|
|
CheckPromoteGeneration(generation);
|
|
|
|
with FGenerationInfo[generation] do begin
|
|
idxData := FindInGeneration(generation, aData);
|
|
if idxData >= 1 then begin
|
|
Data^[idxData].Count := Data^[idxData].Count + count;
|
|
ResortGeneration(generation, idxData);
|
|
end
|
|
else begin
|
|
dataInfo.Data := aData;
|
|
dataInfo.Count := count;
|
|
InsertIntoGeneration(generation, dataInfo);
|
|
end;
|
|
end;
|
|
end; { TStaticCollector.AddToGeneration }
|
|
|
|
procedure TStaticCollector.CheckPromoteGeneration(generation: integer);
|
|
begin
|
|
with FGenerationInfo[generation] do begin
|
|
if NextGeneration > 0 then begin
|
|
if LastCheck_ms = 0 then
|
|
LastCheck_ms := Now_ms
|
|
else if ((Now_ms - LastCheck_ms) div 1000) >= PromoteEvery_sec then begin
|
|
PromoteGeneration(generation, NextGeneration);
|
|
LastCheck_ms := Now_ms;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TStaticCollector.FindInGeneration(generation: integer; const aData: TPointers):
|
|
integer;
|
|
begin
|
|
with FGenerationInfo[generation] do begin
|
|
for Result := 1 to Last do
|
|
if Data^[Result].Data = aData then
|
|
Exit;
|
|
end;
|
|
Result := 0;
|
|
end;
|
|
|
|
function TStaticCollector.FindInsertionPoint(generation, count: integer): integer;
|
|
var
|
|
insert: integer;
|
|
begin
|
|
with FGenerationInfo[generation] do begin
|
|
for insert := Last downto 1 do begin
|
|
if Data^[insert].Count > count then
|
|
Exit(insert+1);
|
|
end;
|
|
Result := 1;
|
|
end;
|
|
end;
|
|
|
|
procedure TStaticCollector.FlushAllGenerations;
|
|
var
|
|
generation: integer;
|
|
nextGen : integer;
|
|
begin
|
|
generation := 1;
|
|
while generation <> 0 do begin
|
|
nextGen := FGenerationInfo[generation].NextGeneration;
|
|
if nextGen > 0 then
|
|
PromoteGeneration(generation, nextGen);
|
|
generation := nextGen;
|
|
end;
|
|
end;
|
|
|
|
procedure TStaticCollector.GetData(var data: TCollectedData; var count: integer);
|
|
begin
|
|
Lock;
|
|
FlushAllGenerations;
|
|
Assert(Length(data) = Length(FGeneration2));
|
|
count := FGenerationInfo[2].Last;
|
|
Move(FGeneration2[1], data[1], count * SizeOf(data[1]));
|
|
FLocked := false;
|
|
end;
|
|
|
|
function TStaticCollector.GetGen1_PromoteCountOver: integer;
|
|
begin
|
|
Result := FGenerationInfo[1].PromoteCountOver;
|
|
end;
|
|
|
|
function TStaticCollector.GetGen1_PromoteEvery_sec: integer;
|
|
begin
|
|
Result := FGenerationInfo[1].PromoteEvery_sec;
|
|
end;
|
|
|
|
function TStaticCollector.GetOverflowCount: TGenerationOverflowCount;
|
|
begin
|
|
Result.Generation1 := FGenerationInfo[1].OverflowCount;
|
|
Result.Generation2 := FGenerationInfo[2].OverflowCount;
|
|
end;
|
|
|
|
procedure TStaticCollector.Initialize;
|
|
begin
|
|
Assert(SizeOf(TStaticCollector) mod SizeOf(pointer) = 0);
|
|
with FGenerationInfo[1] do begin
|
|
Data := PGenerationPlaceholder(@FGeneration1);
|
|
Size := CGeneration1Size;
|
|
Last := 0;
|
|
NextGeneration := 2;
|
|
PromoteEvery_sec := CDefaultPromoteGen1_sec;
|
|
PromoteCountOver := CDefaultPromoteGen1Count;
|
|
LastCheck_ms := 0;
|
|
end;
|
|
with FGenerationInfo[2] do begin
|
|
Data := PGenerationPlaceholder(@FGeneration2);
|
|
Size := CGeneration2Size;
|
|
NextGeneration := 0;
|
|
end;
|
|
end;
|
|
|
|
function TStaticCollector.InsertIntoGeneration(generation: integer; const dataInfo:
|
|
TDataInfo): boolean;
|
|
var
|
|
idx: integer;
|
|
begin
|
|
// We already know that this element does not exist in the generation.
|
|
|
|
Result := true;
|
|
with FGenerationInfo[generation] do begin
|
|
idx := FindInsertionPoint(generation, dataInfo.Count);
|
|
if idx > Last then begin
|
|
if Last = Size then begin
|
|
Inc(OverflowCount);
|
|
Result := false;
|
|
end
|
|
else begin
|
|
Inc(Last);
|
|
Data^[Last] := dataInfo;
|
|
end;
|
|
end
|
|
else begin
|
|
if Last < Size then begin
|
|
Move(Data^[idx], Data^[idx+1], (Last-idx+1) * SizeOf(Data^[idx]));
|
|
Inc(Last);
|
|
end
|
|
else begin
|
|
if Last > idx then
|
|
Move(Data^[idx], Data^[idx+1], (Last-idx) * SizeOf(Data^[idx]));
|
|
Inc(OverflowCount);
|
|
end;
|
|
Data^[idx] := dataInfo;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TStaticCollector.Lock;
|
|
begin
|
|
{$ifndef AssumeMultiThreaded}
|
|
if IsMultiThread then
|
|
{$endif}
|
|
begin
|
|
while LockCmpxchg8(False, True, @FLocked) <> False do
|
|
begin
|
|
{$ifdef NeverSleepOnThreadContention}
|
|
{$ifdef UseSwitchToThread}
|
|
SwitchToThread;
|
|
{$endif}
|
|
{$else}
|
|
Sleep(0);
|
|
if LockCmpxchg8(False, True, @FLocked) = False then
|
|
Break;
|
|
Sleep(1);
|
|
{$endif}
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TStaticCollector.Merge(var mergedData: TCollectedData;
|
|
var mergedCount: integer; const newData: TCollectedData; newCount: integer);
|
|
var
|
|
iNew: integer;
|
|
begin
|
|
// Merges two sorted arrays.
|
|
|
|
FGenerationInfo[0].Data := PGenerationPlaceholder(@mergedData);
|
|
FGenerationInfo[0].Last := mergedCount;
|
|
FGenerationInfo[0].Size := CCollectedDataSize;
|
|
FGenerationInfo[0].NextGeneration := 0;
|
|
|
|
for iNew := 1 to newCount do
|
|
AddToGeneration(0, newData[iNew].Data, newData[iNew].Count);
|
|
|
|
mergedCount := FGenerationInfo[0].Last;
|
|
end;
|
|
|
|
function TStaticCollector.Now_ms: int64;
|
|
var
|
|
st: TSystemTime;
|
|
begin
|
|
// We cannot use SysUtils as that gets memory allocator called before FastMM is initialized.
|
|
GetSystemTime(st);
|
|
SystemTimeToFileTime(st, TFileTime(Result));
|
|
Result := Result div 10000;
|
|
end;
|
|
|
|
procedure TStaticCollector.PromoteGeneration(oldGen, newGen: integer);
|
|
var
|
|
canInsert : boolean;
|
|
idxNew : integer;
|
|
idxOld : integer;
|
|
newGenData: PGenerationPlaceholder;
|
|
pOldData : PDataInfo;
|
|
begin
|
|
canInsert := true;
|
|
newGenData := FGenerationInfo[newGen].Data;
|
|
with FGenerationInfo[oldGen] do begin
|
|
for idxOld := 1 to Last do begin
|
|
pOldData := @Data^[idxOld];
|
|
if pOldData^.Count <= PromoteCountOver then
|
|
break; //for idxOld
|
|
idxNew := FindInGeneration(newGen, pOldData^.Data);
|
|
if idxNew > 0 then begin
|
|
newGenData^[idxNew].Count := newGenData^[idxNew].Count + pOldData^.Count;
|
|
ResortGeneration(newGen, idxNew);
|
|
end
|
|
else if canInsert then
|
|
canInsert := InsertIntoGeneration(newGen, pOldData^)
|
|
else with FGenerationInfo[newGen] do
|
|
Inc(OverflowCount);
|
|
end; //for idxOld
|
|
Last := 0;
|
|
end;
|
|
end;
|
|
|
|
procedure TStaticCollector.ResortGeneration(generation, idxData: integer);
|
|
var
|
|
dataInfo: TDataInfo;
|
|
idx : integer;
|
|
begin
|
|
// Data^[idxData].Count was just updated, resort the generation.
|
|
with FGenerationInfo[generation] do begin
|
|
idx := FindInsertionPoint(generation, Data^[idxData].Count);
|
|
if idx < idxData then begin
|
|
dataInfo := Data^[idxData];
|
|
Move(Data^[idx], Data^[idx+1], (idxData-idx) * SizeOf(Data^[idx]));
|
|
Data^[idx] := dataInfo;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TStaticCollector.SetGen1_PromoteCountOver(const value: integer);
|
|
begin
|
|
FGenerationInfo[1].PromoteCountOver := value;
|
|
end;
|
|
|
|
procedure TStaticCollector.SetGen1_PromoteEvery_sec(const value: integer);
|
|
begin
|
|
FGenerationInfo[1].PromoteEvery_sec := value;
|
|
end;
|
|
|
|
end.
|