Browse Source

* patch by Rika: avoid that capacity of TQueue grows to much for certain
usage patterns, resolves #39662

florian 3 years ago
parent
commit
4c5fc2f958
2 changed files with 70 additions and 8 deletions
  1. 46 8
      packages/rtl-generics/src/generics.collections.pas
  2. 24 0
      tests/webtbs/tw39662.pp

+ 46 - 8
packages/rtl-generics/src/generics.collections.pas

@@ -358,6 +358,7 @@ type
       constructor Create(AQueue: TQueue<T>);
       constructor Create(AQueue: TQueue<T>);
     end;
     end;
   protected
   protected
+    function PrepareAddingItem: SizeInt; override;
     function GetPtrEnumerator: TEnumerator<PT>; override;
     function GetPtrEnumerator: TEnumerator<PT>; override;
   protected
   protected
     // bug #24287 - workaround for generics type name conflict (Identifier not found)
     // bug #24287 - workaround for generics type name conflict (Identifier not found)
@@ -374,6 +375,7 @@ type
     function GetEnumerator: TEnumerator; reintroduce;
     function GetEnumerator: TEnumerator; reintroduce;
   private
   private
     FLow: SizeInt;
     FLow: SizeInt;
+    procedure MoveToFront;
   protected
   protected
     procedure SetCapacity(AValue: SizeInt); override;
     procedure SetCapacity(AValue: SizeInt); override;
     function DoRemove(AIndex: SizeInt; ACollectionNotification: TCollectionNotification): T; override;
     function DoRemove(AIndex: SizeInt; ACollectionNotification: TCollectionNotification): T; override;
@@ -2006,6 +2008,34 @@ end;
 
 
 { TQueue<T> }
 { TQueue<T> }
 
 
+function TQueue<T>.PrepareAddingItem: SizeInt;
+begin
+  repeat
+    result := FLength;
+    if result <= High(FItems) then
+    begin
+      FLength := result + 1;
+      exit;
+    end;
+
+    if SizeUint(FLow) >= 4 + SizeUint(result) div 4 then
+      // If the empty space at the beginning is comparable to queue size, convert
+      //
+      // .......QQQQQQQQQ
+      //        ↑FLow    ↑FLength=length(FItems)
+      //
+      // to
+      //
+      // QQQQQQQQQ.......
+      // ↑FLow=0
+      //
+      // and retry the shortcut above.
+      MoveToFront
+    else
+      exit(inherited);
+  until false;
+end;
+
 function TQueue<T>.GetPtrEnumerator: TEnumerator<PT>;
 function TQueue<T>.GetPtrEnumerator: TEnumerator<PT>;
 begin
 begin
   Result := TPointersenumerator.Create(Self);
   Result := TPointersenumerator.Create(Self);
@@ -2037,6 +2067,21 @@ begin
   Notify(Result, ACollectionNotification);
   Notify(Result, ACollectionNotification);
 end;
 end;
 
 
+procedure TQueue<T>.MoveToFront;
+var
+  i: SizeInt;
+begin
+  if FLength > FLow then
+    if IsManagedType(T) then
+      for i := 0 to FLength - FLow - 1 do
+        FItems[i] := FItems[FLow + i]
+    else
+      Move(FItems[FLow], FItems[0], (FLength - FLow) * SizeOf(T));
+
+  FLength := FLength - FLow;
+  FLow := 0;
+end;
+
 procedure TQueue<T>.SetCapacity(AValue: SizeInt);
 procedure TQueue<T>.SetCapacity(AValue: SizeInt);
 begin
 begin
   if AValue < Count then
   if AValue < Count then
@@ -2045,15 +2090,8 @@ begin
   if AValue = FLength then
   if AValue = FLength then
     Exit;
     Exit;
 
 
-  if (Count > 0) and (FLow > 0) then
-  begin
-    Move(FItems[FLow], FItems[0], Count * SizeOf(T));
-    FillChar(FItems[Count], (FLength - Count) * SizeOf(T), #0);
-  end;
-
+  MoveToFront;
   SetLength(FItems, AValue);
   SetLength(FItems, AValue);
-  FLength := Count;
-  FLow := 0;
 end;
 end;
 
 
 function TQueue<T>.GetCount: SizeInt;
 function TQueue<T>.GetCount: SizeInt;

+ 24 - 0
tests/webtbs/tw39662.pp

@@ -0,0 +1,24 @@
+uses Generics.Collections;
+  
+var
+  Queue: specialize TQueue<Integer>;
+  I, J: Integer;
+begin
+  Queue := specialize TQueue<Integer>.Create;
+ 
+  for I := 0 to 15 do
+    Queue.Enqueue(I);
+  for I := 1 to 10000 do
+    begin
+      for J := 1 to 15 do
+        Queue.Dequeue;
+      for J := 1 to 15 do
+        Queue.Enqueue(J);
+    end;
+ 
+  WriteLn(Queue.Capacity);
+  { avoid too large capacities }
+  if Queue.Capacity>64 then
+    halt(1);
+  Queue.Free;
+end.