Browse Source

RandomHash: final optimizations. Completed!

Herman Schoenfeld 6 years ago
parent
commit
99089aa741

+ 176 - 38
src/core/URandomHash.pas

@@ -139,6 +139,40 @@ type
     const
       N = 5;               // Number of hashing rounds required to compute a nonce (total rounds = 2^N - 1)
       M = (10 * 1024) * 5; // 10KB The memory expansion unit (in bytes)
+    public type
+
+      { TChecksummedByteCollection }
+
+      TChecksummedByteCollection = class
+      private
+        {$IFDEF FPC}
+        // NOTE: due to FPC bug, cannot declare this as 'class var' since crashes. Delphi does not support static fields
+        FInstances : UInt32; static;
+        {$ENDIF}
+        FBytes : TList<TBytes>;
+        FComputedIndex : Integer;
+        FChecksum : UInt32;
+        FMurMur3 : IHash;
+        function GetCount : Integer;
+        function CalculateChecksum : UInt32;
+      public
+        {$IFDEF FPC}
+        class property Instances : UInt32 read FInstances;
+        {$ENDIF}
+        constructor Create; overload;
+        constructor Create(const AManyBytes : TArray<TBytes>); overload;
+        destructor Destroy; override;
+        function Clone : TChecksummedByteCollection;
+        property Count : Integer read GetCount;
+        property Checksum : UInt32 read CalculateChecksum;
+        function Get(AIndex : Integer) : TBytes; inline;
+        procedure Add(const ABytes : TBytes); overload;
+        procedure AddRange(const AManyBytes : TArray<TBytes>); overload;
+        procedure AddRange(ACollection : TChecksummedByteCollection); overload;
+        procedure Clear;
+        function ToByteArray : TBytes;
+      end;
+
     private
       function GetCachedHeader : TBytes;
     {$IFNDEF UNITTESTS}private{$ELSE}public{$ENDIF}
@@ -146,7 +180,7 @@ type
       FHashAlg : array[0..17] of IHash;  // declared here to avoid race-condition during mining
       FCachedHeader : TBytes;
       FCachedNonce : UInt32;
-      FCachedOutput : TArray<TBytes>;
+      FCachedOutput : TChecksummedByteCollection;
 
       procedure MemTransform1(var ABuffer: TBytes; AReadStart, AWriteStart, ALength : Integer); inline;
       procedure MemTransform2(var ABuffer: TBytes; AReadStart, AWriteStart, ALength : Integer); inline;
@@ -157,13 +191,12 @@ type
       procedure MemTransform7(var ABuffer: TBytes; AReadStart, AWriteStart, ALength : Integer); inline;
       procedure MemTransform8(var ABuffer: TBytes; AReadStart, AWriteStart, ALength : Integer); inline;
       function Expand(const AInput: TBytes; AExpansionFactor: Int32) : TBytes;
-      function Compress(const AInputs: TArray<TBytes>): TBytes; inline;
+      function Compress(const AInputs: TChecksummedByteCollection): TBytes; inline;
       function GetNonce(const ABlockHeader: TBytes) : UInt32;
       function ChangeNonce(const ABlockHeader: TBytes; ANonce: UInt32): TBytes; inline;
       function Checksum(const AInput: TBytes; AOffset, ALength: Integer): UInt32; overload; inline;
       function Checksum(const AInput: TBytes): UInt32; overload; inline;
-      function Checksum(const AInput: TArray<TBytes>): UInt32; overload; inline;
-      function Hash(const ABlockHeader: TBytes; ARound: Int32) : TArray<TBytes>; overload;
+      function Hash(const ABlockHeader: TBytes; ARound: Int32) : TChecksummedByteCollection; overload;
     public
       property NextHeader : TBytes read GetCachedHeader;
       property NextNonce : UInt32 read FCachedNonce;
@@ -573,6 +606,8 @@ begin
  FMurmurHash3_x86_32 := nil;
  for i := Low(FHashAlg) to High(FHashAlg) do
    FHashAlg[i] := nil;
+ if Assigned(FCachedOutput) then
+   FreeAndNil(FCachedOutput);
  inherited Destroy;
 end;
 
@@ -587,20 +622,20 @@ end;
 
 function TRandomHashFast.Hash(const ABlockHeader: TBytes): TBytes;
 var
-  LAllOutputs: TArray<TBytes>;
-  LSeed: UInt32;
+  LAllOutputs: TChecksummedByteCollection;
+  LSeed, LTemp: UInt32;
+  LDisposables : TDisposables;
 begin
-  LAllOutputs := Hash(ABlockHeader, N);
+  LAllOutputs := LDisposables.AddObject( Hash(ABlockHeader, N) ) as TChecksummedByteCollection;
   Result := FHashAlg[0].ComputeBytes(Compress(LAllOutputs)).GetBytes;
 end;
 
-function TRandomHashFast.Hash(const ABlockHeader: TBytes; ARound: Int32) : TArray<TBytes>;
+function TRandomHashFast.Hash(const ABlockHeader: TBytes; ARound: Int32) : TChecksummedByteCollection;
 var
-  LRoundOutputs: TList<TBytes>;
+  LRoundOutputs, LParentOutputs, LNeighbourOutputs: TChecksummedByteCollection;
   LSeed: UInt32;
   LGen: TMersenne32;
   LRoundInput, LNeighbourNonceHeader, LOutput, LBytes: TBytes;
-  LParentOutputs, LNeighborOutputs, LToArray: TArray<TBytes>;
   LHashFunc: IHash;
   i, LNeighbourNonce, LSeedToCache: UInt32;
   LDisposables : TDisposables;
@@ -608,7 +643,8 @@ begin
   if (ARound < 1) or (ARound > N) then
     raise EArgumentOutOfRangeException.CreateRes(@SInvalidRound);
 
-  LRoundOutputs := LDisposables.AddObject( TList<TBytes>.Create() ) as TList<TBytes>;
+  LRoundOutputs := TChecksummedByteCollection.Create(); // NOTE: instance is destroyed by caller!
+
   LGen := LDisposables.AddObject( TMersenne32.Create(0) ) as TMersenne32;
   if ARound = 1 then begin
     LSeed := Checksum(ABlockHeader);
@@ -622,8 +658,9 @@ begin
     end else begin
       // Need to calculate parent output
       LParentOutputs := Hash(ABlockHeader, ARound - 1);
+      LDisposables.AddObject(LParentOutputs);
     end;
-    LSeed := Checksum(LParentOutputs);
+    LSeed := LParentOutputs.Checksum;
 
     LGen.Initialize(LSeed);
     LRoundOutputs.AddRange( LParentOutputs );
@@ -631,17 +668,20 @@ begin
     // Determine the neighbouring nonce
     LNeighbourNonce := LGen.NextUInt32;
     LNeighbourNonceHeader := ChangeNonce(ABlockHeader, LNeighbourNonce);
-    LNeighborOutputs := Hash(LNeighbourNonceHeader, ARound - 1);
+    LNeighbourOutputs := Hash(LNeighbourNonceHeader, ARound - 1);
+    LDisposables.AddObject(LNeighbourOutputs);
 
     // Cache neighbour nonce n-1 calculation if on final round (neighbour will be next nonce)
     if (ARound = N) then begin
       FCachedNonce := LNeighbourNonce;
       FCachedHeader := LNeighbourNonceHeader;
-      FCachedOutput := LNeighborOutputs;
+      if Assigned(FCachedOutput) then
+        FreeAndNil(FCachedOutput);
+      FCachedOutput := LNeighbourOutputs.Clone;
     end;
 
-    LRoundOutputs.AddRange(LNeighborOutputs);
-    LRoundInput := Compress( LRoundOutputs.ToArray );
+    LRoundOutputs.AddRange(LNeighbourOutputs);
+    LRoundInput := Compress( LRoundOutputs );
   end;
 
   LHashFunc := FHashAlg[LGen.NextUInt32 mod 18];
@@ -649,18 +689,18 @@ begin
   LOutput := Expand(LOutput, N - ARound);
   LRoundOutputs.Add(LOutput);
 
-  Result := LRoundOutputs.ToArray;
+  Result := LRoundOutputs; // caller must destroy instance
 end;
 
 function TRandomHashFast.GetNonce(const ABlockHeader: TBytes) : UInt32;
 var LLen : Integer;
 begin
- LLen := Length(ABlockHeader);
- if LLen < 4 then
+  LLen := Length(ABlockHeader);
+  if LLen < 4 then
    raise EArgumentOutOfRangeException.CreateRes(@SBlockHeaderTooSmallForNonce);
 
- // Last 4 bytes are nonce (LE)
- Result := ABlockHeader[LLen - 4] OR
+  // Last 4 bytes are nonce (LE)
+  Result := ABlockHeader[LLen - 4] OR
            (ABlockHeader[LLen - 3] SHL 8) OR
            (ABlockHeader[LLen - 2] SHL 16) OR
            (ABlockHeader[LLen - 1] SHL 24);
@@ -694,36 +734,26 @@ end;
 
 function TRandomHashFast.Checksum(const AInput: TBytes; AOffset, ALength: Integer): UInt32;
 begin
+  FMurmurHash3_x86_32.Initialize;
   FMurmurHash3_x86_32.TransformBytes(AInput, AOffset, ALength);
   Result := FMurmurHash3_x86_32.TransformFinal.GetUInt32();
 end;
 
-function TRandomHashFast.Checksum(const AInput : TArray<TBytes>): UInt32;
-var
-  i: Int32;
-begin
-  FMurmurHash3_x86_32.Initialize;
-  for i := Low(AInput) to High(AInput) do
-  begin
-    FMurmurHash3_x86_32.TransformBytes(AInput[i]);
-  end;
-  Result := FMurmurHash3_x86_32.TransformFinal.GetUInt32;
-end;
-
-function TRandomHashFast.Compress(const AInputs : TArray<TBytes>): TBytes;
+function TRandomHashFast.Compress(const AInputs : TChecksummedByteCollection): TBytes;
 var
   i: Int32;
-  LSeed: UInt32;
+  LSeed, LInputCount: UInt32;
   LSource: TBytes;
   LGen: TMersenne32;
   LDisposables : TDisposables;
 begin
   SetLength(Result, 100);
-  LSeed := Checksum(AInputs);
+  LSeed := AInputs.Checksum;
   LGen := LDisposables.AddObject( TMersenne32.Create( LSeed ) ) as TMersenne32;
+  LInputCount := AInputs.Count;
   for i := 0 to 99 do
   begin
-    LSource := AInputs[LGen.NextUInt32 mod Length(AInputs)];
+    LSource := AInputs.Get(LGen.NextUInt32 mod LInputCount);
     Result[i] := LSource[LGen.NextUInt32 mod Length(LSource)];
   end;
 end;
@@ -904,7 +934,6 @@ begin
   LReadEnd := LInputSize - 1;
   LCopyLen := LInputSize;
 
-
   while LReadEnd < Pred(Length(LOutput)) do
   begin
     if (LReadEnd + 1 + LCopyLen) > Length(LOutput) then
@@ -935,6 +964,115 @@ begin
   end;
 end;
 
+{ TChecksummedByteCollection }
+
+constructor TRandomHashFast.TChecksummedByteCollection.Create;
+begin
+ Self.Create(nil);
+end;
+
+constructor TRandomHashFast.TChecksummedByteCollection.Create(const AManyBytes : TArray<TBytes>);
+begin
+  {$IFDEF FPC}
+  Inc(FInstances);
+  {$ENDIF}
+  FBytes := TList<TBytes>.Create;
+  FComputedIndex := -1;
+  FChecksum := 0;
+  FMurMur3 := THashFactory.THash32.CreateMurmurHash3_x86_32();
+  AddRange(AManyBytes);
+end;
+
+destructor TRandomHashFast.TChecksummedByteCollection.Destroy;
+begin
+  FreeAndNil(FBytes);
+  FMurMur3 := nil;
+  {$IFDEF FPC}
+  Dec(FInstances);
+  {$ENDIF}
+  inherited Destroy;
+end;
+
+function TRandomHashFast.TChecksummedByteCollection.Clone : TChecksummedByteCollection;
+begin
+  Result := TChecksummedByteCollection.Create;
+  Result.FBytes := TList<TBytes>.Create(Self.FBytes);
+  Result.FComputedIndex:= Self.FComputedIndex;
+  Result.FChecksum:= Self.FChecksum;
+  Result.FMurMur3 := Self.FMurMur3.Clone();
+end;
+
+function TRandomHashFast.TChecksummedByteCollection.GetCount : Integer;
+begin
+  Result := FBytes.Count;
+end;
+
+function TRandomHashFast.TChecksummedByteCollection.Get(AIndex : Integer) : TBytes;
+begin
+  Result := FBytes[AIndex];
+end;
+
+procedure TRandomHashFast.TChecksummedByteCollection.Add(const ABytes : TBytes);
+begin
+  FBytes.Add(ABytes);
+end;
+
+procedure TRandomHashFast.TChecksummedByteCollection.AddRange(const AManyBytes : TArray<TBytes>);
+var LArr : TBytes; i : integer;
+begin
+  FBytes.AddRange(AManyBytes);
+end;
+
+procedure TRandomHashFast.TChecksummedByteCollection.AddRange(ACollection : TChecksummedByteCollection);
+begin
+  if FBytes.Count = 0 then begin
+    // Is empty so just copy checksum from argument
+    FComputedIndex := ACollection.FComputedIndex;
+    FChecksum := ACollection.FChecksum;
+    FMurMur3 := ACollection.FMurMur3.Clone;
+  end;
+  FBytes.AddRange(ACollection.FBytes);
+end;
+
+function TRandomHashFast.TChecksummedByteCollection.CalculateChecksum : UInt32;
+var
+  i : integer;
+  LClonedMurMur3 : IHash;
+begin
+  if (FComputedIndex = FBytes.Count - 1) then
+    Exit(FChecksum); // already computed
+
+  for i := (FComputedIndex + 1) to Pred(FBytes.Count) do begin
+    FMurMur3.TransformBytes(FBytes[i]);
+    Inc(FComputedIndex);
+  end;
+
+  LClonedMurMur3 := FMurMur3.Clone;
+  FChecksum := FMurMur3.TransformFinal().GetUInt32();
+  FMurMur3 := LClonedMurMur3; // note: original instance should collect with implicit dereference
+  Result := FChecksum;
+end;
+
+procedure TRandomHashFast.TChecksummedByteCollection.Clear;
+begin
+  FBytes.Clear;
+  FComputedIndex := -1;
+  FChecksum := 0;
+  FMurMur3 := THashFactory.THash32.CreateMurmurHash3_x86_32(); // note: original instance should collect with implicit dereference
+end;
+
+function TRandomHashFast.TChecksummedByteCollection.ToByteArray : TBytes;
+var
+  LList : TList<Byte>;
+  LBytes : TBytes;
+  LDisposables : TDisposables;
+begin
+  LList := LDisposables.AddObject( TList<Byte>.Create ) as TList<Byte>;
+  for LBytes in FBytes do
+    LList.AddRange(LBytes);
+  Result := LList.ToArray;
+end;
+
 { TMersenne32 }
 
 constructor TMersenne32.Create(ASeed: UInt32);

+ 1 - 1
src/pascalcoin_miner.lpi

@@ -55,7 +55,7 @@
     </SearchPaths>
     <CodeGeneration>
       <Optimizations>
-        <OptimizationLevel Value="3"/>
+        <OptimizationLevel Value="4"/>
       </Optimizations>
     </CodeGeneration>
   </CompilerOptions>

+ 27 - 24
src/pascalcoin_miner.pp

@@ -271,15 +271,16 @@ var
   end;
 
   Procedure DoWaitAndLog;
-  Var tc : TTickCount;
-    gs,ms : TMinerStats;
+  var
+    LTickCount : TTickCount;
+    LGlobalStats, LMinerStats : TMinerStats;
     hrReal,hrHashing, glhrHashing, glhrReal : Real;
     i : Integer;
     devt : TCustomMinerDeviceThread;
-    s : String;
-    kpressed : Char;
+    LStr : String;
+    LKeyPressed : Char;
   Begin
-    tc := TPlatform.GetTickCount;
+    LTickCount := TPlatform.GetTickCount;
     repeat
       If FPoolMinerThread.PoolMinerClient.Connected then begin
         for i:=0 to FDeviceThreads.Count-1 do begin
@@ -288,34 +289,36 @@ var
       end;
       while (Not Terminated) do begin
         sleep(100);
-        If TPlatform.GetElapsedMilliseconds(tc)>1000 then begin
-          tc := GetTickCount64;
+        If TPlatform.GetElapsedMilliseconds(LTickCount)>1000 then begin
+          LTickCount := GetTickCount64;
           For i:=0 to FDeviceThreads.Count-1 do begin
             devt := TCustomMinerDeviceThread(FDeviceThreads[i]);
-            ms := devt.DeviceStats;
-            if ms.WorkingMillisecondsHashing>0 then hrHashing := (((ms.RoundsCount DIV Int64(ms.WorkingMillisecondsHashing)))/(1000))
-            else hrHashing := 0;
-            gs := devt.GlobalDeviceStats;
-            If ms.RoundsCount>0 then begin
-              s := FormatDateTime('hh:nn:ss',now)+Format(' Miner:"%s" at %0.2f MH/s - Rounds: %0.2f G Found: %d',[devt.MinerValuesForWork.payload_start,hrHashing, gs.RoundsCount/1000000000, gs.WinsCount]);
-              If (gs.Invalids>0) then s := s +' '+inttostr(gs.Invalids)+' ERRORS!';
-              WriteLine(CT_Line_MiningStatus+i,s);
+            LMinerStats := devt.DeviceStats;
+            if LMinerStats.WorkingMillisecondsHashing>0 then
+              hrHashing := LMinerStats.RoundsCount / (LMinerStats.WorkingMillisecondsHashing / 1000)
+            else
+              hrHashing := 0;
+            LGlobalStats := devt.GlobalDeviceStats;
+            If LMinerStats.RoundsCount>0 then begin
+              LStr := FormatDateTime('hh:nn:ss',now)+Format(' Miner:"%s" at %0.2f H/s - Rounds: %d Found: %d',[devt.MinerValuesForWork.payload_start,hrHashing, LGlobalStats.RoundsCount, LGlobalStats.WinsCount]);
+              If (LGlobalStats.Invalids>0) then LStr := LStr +' '+inttostr(LGlobalStats.Invalids)+' ERRORS!';
+              WriteLine(CT_Line_MiningStatus+i,LStr);
             end else begin
-              If gs.RoundsCount>0 then begin
-                s := FormatDateTime('hh:nn:ss',now)+Format(' Miner:"%s" **NOT MINING** - Rounds: %0.2f G Found: %d',[devt.MinerValuesForWork.payload_start,gs.RoundsCount/1000000000, gs.WinsCount]);
-                If (gs.Invalids>0) then s := s +' '+inttostr(gs.Invalids)+' ERRORS!';
+              If LGlobalStats.RoundsCount>0 then begin
+                LStr := FormatDateTime('hh:nn:ss',now)+Format(' Miner:"%s" **NOT MINING** - Rounds: %d Found: %d',[devt.MinerValuesForWork.payload_start,LGlobalStats.RoundsCount, LGlobalStats.WinsCount]);
+                If (LGlobalStats.Invalids>0) then LStr := LStr +' '+inttostr(LGlobalStats.Invalids)+' ERRORS!';
               end else begin
-                s := FormatDateTime('hh:nn:ss',now)+' Not mining...';
+                LStr := FormatDateTime('hh:nn:ss',now)+' Not mining...';
               end;
-              WriteLine(CT_Line_MiningStatus+i,s);
+              WriteLine(CT_Line_MiningStatus+i,LStr);
             end;
           end;
-          WriteLine(CT_Line_LastFound+FDeviceThreads.Count-1,'MY VALID BLOCKS FOUND: '+IntToStr(gs.WinsCount) +' Working time: '+IntToStr(Trunc(now - FAppStartTime))+'d '+FormatDateTime('hh:nn:ss',Now-FAppStartTime) );
+          WriteLine(CT_Line_LastFound+FDeviceThreads.Count-1,'MY VALID BLOCKS FOUND: '+IntToStr(LGlobalStats.WinsCount) +' Working time: '+IntToStr(Trunc(now - FAppStartTime))+'d '+FormatDateTime('hh:nn:ss',Now-FAppStartTime) );
         end;
         If KeyPressed then begin
-          kpressed := ReadKey;
-          If kpressed in ['c','C','q','Q'] then begin
-            TLog.NewLog(ltinfo,ClassName,'Finalizing by keypressing '+kpressed);
+          LKeyPressed := ReadKey;
+          If LKeyPressed in ['c','C','q','Q'] then begin
+            TLog.NewLog(ltinfo,ClassName,'Finalizing by keypressing '+LKeyPressed);
             WriteLine(CT_Line_Logs+FDeviceThreads.Count+CT_MaxLogs,'Finalizing...');
             terminate;
           end;

+ 1 - 0
src/tests/PascalCoinUnitTests.lpi

@@ -49,6 +49,7 @@
       <Unit3>
         <Filename Value="..\libraries\generics.collections\generics.collections.pas"/>
         <IsPartOfProject Value="True"/>
+        <UnitName Value="Generics.Collections"/>
       </Unit3>
       <Unit4>
         <Filename Value="..\libraries\generics.collections\generics.defaults.pas"/>

+ 3 - 3
src/tests/URandomHashTests.Delphi.pas

@@ -30,9 +30,9 @@ const
   );
 
   DATA_RANDOMHASH_STANDARD_EXPECTED : array[1..3] of String = (
-    '0x291ef6d7f9babe3d2d4fd6560c7eefc7a9937126fd13d5af6fd0474b6dfac215',
-    '0xb0806f69c78d5129bca60e35902ddd88a24bda32cbd32612828e797163221013',
-    '0x1a0b8000d591a948e038c43af0abb4ee504f08fcf869e55a9e4dda4384719eb7'
+    '0x675c62c74c313647e95e820bbf540c6d4453482b745e62016404424323b69e09',
+    '0xaa5b8597f00bdb1c1953e668e6fdd6b5b0df3731b09a7777893d7fc5554b1e3a',
+    '0xff4f832020dc4eac07868e9f180f256c9b1d5513b35cd24db5af7da6526bb50f'
   );
 
 { TRandomHashTest }

+ 193 - 11
src/tests/URandomHashTests.pas

@@ -56,6 +56,7 @@ type
     procedure TestRADIOGATUN32;
     procedure TestWHIRLPOOL;
     procedure TestMURMUR3_32;
+    procedure TestMURMUR3_32_Clone;
   end;
 
   { TRandomHashFastTest }
@@ -72,10 +73,12 @@ type
     procedure TestRandomHash_CachedHeaderConsistency;
     procedure TestRandomHash_NonceOptimization;
     procedure TestRandomHash_OptimalNonceSet;
+    procedure TestRandomHash_MemoryLeak_SingleRound;
+    procedure TestRandomHash_MemoryLeaks;
     procedure TestExpand;
     procedure TestCompress;
     procedure TestChecksum_1;
-    procedure TestChecksum_2;
+    procedure TestChecksummedByteCollection_Checksum;
 
     procedure TestMemTransform1_Padding;
     procedure TestMemTransform2_Padding;
@@ -95,6 +98,17 @@ type
     procedure TestMemTransform8;
   end;
 
+  { TRandomHashFast_TChecksummedByteCollectionTest }
+
+  TRandomHashFast_TChecksummedByteCollectionTest = class(TPascalCoinUnitTest)
+  published
+    procedure TestSingle;
+    procedure TestDouble;
+    procedure TestComplex;
+    procedure TestToByteArray_Simple;
+    procedure TestToByteArray_Complex;
+  end;
+
   { TRandomHashStressTest }
 
   TRandomHashStressTest = class(TPascalCoinUnitTest)
@@ -106,7 +120,9 @@ type
 
 implementation
 
-uses variants, UCommon, UCommon.Collections, UMemory, URandomHash, HlpHashFactory, HlpBitConverter, strutils;
+uses
+  variants, UCommon, UCommon.Collections, UMemory, URandomHash, HlpHashFactory,
+  HlpBitConverter, strutils, Generics.Collections;
 
 const
 
@@ -1077,6 +1093,28 @@ begin
   TestSubHash(THashFactory.THash32.CreateMurmurHash3_x86_32(), DATA_MURMUR3_32);
 end;
 
+procedure TRandomHashTest.TestMURMUR3_32_Clone;
+var
+  LInput : TBytes;
+  LCase : TTestItem<Integer, String>;
+  LHasher, LClone1, LClone2 : IHash;
+begin
+  LHasher := THashFactory.THash32.CreateMurmurHash3_x86_32();
+  for LCase in DATA_MURMUR3_32 do begin
+    LInput := TArrayTool<byte>.Copy(ParseBytes(DATA_BYTES), 0, LCase.Input);
+    LHasher.TransformBytes(LInput);
+    LClone1 := LHasher.Clone();
+    AssertEquals(ParseBytes(LCase.Expected), LHasher.TransformFinal().GetBytes);
+    LClone2 := LClone1.Clone();
+    AssertEquals(ParseBytes(LCase.Expected), LClone1.TransformFinal().GetBytes);
+    AssertEquals(ParseBytes(LCase.Expected), LClone2.TransformFinal().GetBytes);
+  end;
+end;
+
+{begin
+  TestSubHash(THashFactory.THash32.CreateMurmurHash3_x86_32(), DATA_MURMUR3_32);
+end;}
+
 procedure TRandomHashTest.TestSubHash(AHasher : IHash; const ATestData : array of TTestItem<Integer, String>);
 var
   LInput : TBytes;
@@ -1168,6 +1206,48 @@ begin
   end;
 end;
 
+procedure TRandomHashFastTest.TestRandomHash_MemoryLeak_SingleRound;
+
+  procedure RunTest;
+  const
+    NUM_ITER = 100;
+  var
+    i : Integer;
+    LHasher : TRandomHashFast;
+  begin
+    LHasher := TRandomHashFast.Create;
+    LHasher.Hash(ParseBytes(DATA_BYTES));
+    LHasher.Free;
+  end;
+
+begin
+  AssertEquals(0, TRandomHashFast.TChecksummedByteCollection.Instances);
+  RunTest;
+  AssertEquals(0, TRandomHashFast.TChecksummedByteCollection.Instances);
+end;
+
+procedure TRandomHashFastTest.TestRandomHash_MemoryLeaks;
+
+  procedure RunTest;
+    const
+      NUM_ITER = 100;
+    var
+      i : Integer;
+      LHasher : TRandomHashFast;
+    begin
+      LHasher := TRandomHashFast.Create;
+      LHasher.Hash(ParseBytes(DATA_BYTES));
+      for i := 1 to Pred(NUM_ITER) do
+        LHasher.Hash(LHasher.NextHeader);
+      LHasher.Free;
+    end;
+
+begin
+  AssertEquals(0, TRandomHashFast.TChecksummedByteCollection.Instances);
+  RunTest;
+  AssertEquals(0, TRandomHashFast.TChecksummedByteCollection.Instances);
+end;
+
 procedure TRandomHashFastTest.TestExpand;
 var
   LCase : TTestItem<UInt32, UInt32, UInt32>;
@@ -1194,7 +1274,8 @@ var
   LCase : TTestItem<String, String>;
   LHasher : TRandomHashFast;
   LDisposables : TDisposables;
-  LInputs : TArray<TBytes>;
+  LInputs : TRandomHashFast.TChecksummedByteCollection;
+
 
   function ParseHex(constref AHex : String) : TBytes;
   begin
@@ -1204,7 +1285,7 @@ var
 begin
   LHasher := LDisposables.AddObject( TRandomHashFast.Create ) as TRandomHashFast;
   for LCase in DATA_COMPRESS do begin
-    LInputs := TListTool<String, TBytes>.Transform(LCase.Input.Split([';']), ParseHex);
+    LInputs := LDisposables.AddObject( TRandomHashFast.TChecksummedByteCollection.Create( TListTool<String, TBytes>.Transform(LCase.Input.Split([';']), ParseHex) ) ) as TRandomHashFast.TChecksummedByteCollection;
     AssertEquals(Hex2Bytes(LCase.Expected), LHasher.Compress(LInputs));
    //WriteLn(Bytes2Hex(LHasher.Compress(LInputs)));
   end;
@@ -1224,25 +1305,25 @@ begin
   end;
 end;
 
-procedure TRandomHashFastTest.TestChecksum_2;
+procedure TRandomHashFastTest.TestChecksummedByteCollection_Checksum;
 var
   LInput : TBytes;
-  LInputs : TArray<TBytes>;
+  LInputs : TRandomHashFast.TChecksummedByteCollection;
   LCase : TTestItem<Integer, UInt32>;
   LHasher : TRandomHashFast;
   LDisposables : TDisposables;
   i : UInt32;
 begin
   LHasher := LDisposables.AddObject( TRandomHashFast.Create ) as TRandomHashFast;
+  LInputs := LDisposables.AddObject( TRandomHashFast.TChecksummedByteCollection.Create) as TRandomHashFast.TChecksummedByteCollection;
   for LCase in DATA_CHECKSUM do begin
     LInput := TArrayTool<byte>.Copy(ParseBytes(DATA_BYTES), 0, LCase.Input);
     // Split into arrays of 1 byte
-    SetLength(LInputs, Length(LInput));
     for i := 0 to Pred(Length(LInput)) do begin
-      SetLength(LInputs[i], 1);
-      LInputs[i][0] := LInput[i];
+      LInputs.Add(TBytes.Create(LInput[i]));
     end;
-    AssertEquals(LCase.Expected, LHasher.CheckSum(LInputs));
+    AssertEquals(LCase.Expected, LInputs.Checksum);
+    LInputs.Clear;
   end;
 end;
 
@@ -1466,6 +1547,105 @@ begin
   end;
 end;
 
+{ TRandomHashFast_TChecksummedByteCollectionTest }
+
+procedure TRandomHashFast_TChecksummedByteCollectionTest.TestSingle;
+var
+  LReference : TRandomHash;
+  LTest : TRandomHashFast.TChecksummedByteCollection;
+  LDisposables : TDisposables;
+  LBytes : TBytes;
+begin
+  LReference := LDisposables.AddObject( TRandomHash.Create ) as TRandomHash;
+  LTest := LDisposables.AddObject( TRandomHashFast.TChecksummedByteCollection.Create ) as TRandomHashFast.TChecksummedByteCollection;
+  LBytes := ParseBytes(DATA_BYTES);
+  LTest.Add(LBytes);
+  AssertEquals(LReference.Checksum(LBytes), LTest.Checksum);
+end;
+
+procedure TRandomHashFast_TChecksummedByteCollectionTest.TestDouble;
+var
+  LReference : TRandomHash;
+  LTest : TRandomHashFast.TChecksummedByteCollection;
+  LDisposables : TDisposables;
+  LBytes : TBytes;
+  LDummy : UInt32;
+begin
+  LReference := LDisposables.AddObject( TRandomHash.Create ) as TRandomHash;
+  LTest := LDisposables.AddObject( TRandomHashFast.TChecksummedByteCollection.Create ) as TRandomHashFast.TChecksummedByteCollection;
+  LBytes := ParseBytes(DATA_BYTES);
+  LTest.Add(LBytes);
+  LDummy := LTest.Checksum;
+  LTest.Add(LBytes);
+  AssertEquals(LReference.Checksum(TArray<TBytes>.Create(LBytes, LBytes)), LTest.Checksum);
+end;
+
+procedure TRandomHashFast_TChecksummedByteCollectionTest.TestComplex;
+var
+  LReference : TRandomHash;
+  LTest : TRandomHashFast.TChecksummedByteCollection;
+  LDisposables : TDisposables;
+  LBytes : TBytes;
+  i, LDummy : UInt32;
+  LTempBytes : TList<Byte>;
+begin
+  LReference := LDisposables.AddObject( TRandomHash.Create ) as TRandomHash;
+  LTempBytes := LDisposables.AddObject( TList<Byte>.Create ) as TList<Byte>;
+  LTest := LDisposables.AddObject( TRandomHashFast.TChecksummedByteCollection.Create ) as TRandomHashFast.TChecksummedByteCollection;
+  LBytes := ParseBytes(DATA_BYTES);
+
+  // break up input into escalating size arrays [A], [B,C], [D,E,F], ....
+  for i := 0 to Pred(Length(LBytes)) do begin
+    LTempBytes.Add(LBytes[i]);
+    if (LTest.Count = 0) OR (LTempBytes.Count > Length(LTest.Get(LTest.Count - 1))) then begin
+      LTest.Add(LTempBytes.ToArray);
+      LTempBytes.Clear;
+      LDummy := LTest.Checksum;  // call checksum to force partial evaluation
+    end;
+  end;
+  LTest.Add(LTempBytes.ToArray); // add last incomplete chunk
+  AssertEquals(LReference.Checksum(LBytes), LTest.Checksum);
+end;
+
+procedure TRandomHashFast_TChecksummedByteCollectionTest.TestToByteArray_Simple;
+var
+  LTest : TRandomHashFast.TChecksummedByteCollection;
+  LDisposables : TDisposables;
+  LBytes : TBytes;
+begin
+  LTest := LDisposables.AddObject( TRandomHashFast.TChecksummedByteCollection.Create ) as TRandomHashFast.TChecksummedByteCollection;
+  LTest.Add(nil);
+  LTest.Add(TBytes.Create(1));
+  LTest.Add(nil);
+  LTest.AddRange( TArray<TBytes>.Create(nil, TBytes.Create(2), nil, TBytes.Create(3) ));
+  LBytes := LTest.ToByteArray;
+  AssertEquals(TBytes.Create(1,2,3), LBytes);
+end;
+
+procedure TRandomHashFast_TChecksummedByteCollectionTest.TestToByteArray_Complex;
+var
+  LTest : TRandomHashFast.TChecksummedByteCollection;
+  LDisposables : TDisposables;
+  LBytes : TBytes;
+  LDummy, i : UInt32;
+  LTempBytes : TList<Byte>;
+begin
+  LTempBytes := LDisposables.AddObject( TList<Byte>.Create ) as TList<Byte>;
+  LTest := LDisposables.AddObject( TRandomHashFast.TChecksummedByteCollection.Create ) as TRandomHashFast.TChecksummedByteCollection;
+  LBytes := ParseBytes(DATA_BYTES);
+
+  // break up input into escalating size arrays [A], [B,C], [D,E,F], ....
+  for i := 0 to Pred(Length(LBytes)) do begin
+    LTempBytes.Add(LBytes[i]);
+    if (LTest.Count = 0) OR (LTempBytes.Count > Length(LTest.Get(LTest.Count - 1))) then begin
+      LTest.Add(LTempBytes.ToArray);
+      LTempBytes.Clear;
+    end;
+  end;
+  LTest.Add(LTempBytes.ToArray); // add last incomplete chunk
+  AssertEquals(LBytes, LTest.ToByteArray);
+end;
+
 { TRandomHashStressTest }
 
 procedure TRandomHashStressTest.Reference1000;
@@ -1522,10 +1702,12 @@ initialization
 {$IFDEF FPC}
   RegisterTest(TRandomHashTest);
   RegisterTest(TRandomHashFastTest);
-  //RegisterTest(TRandomHashStressTest);
+  RegisterTest(TRandomHashFast_TChecksummedByteCollectionTest);
+ // RegisterTest(TRandomHashStressTest);
 {$ELSE}
   TDUnitX.RegisterTextFixture(TRandomHashTest);
   TDUnitX.RegisterTextFixture(TRandomHashFastTest);
+  TDUnitX.RegisterTextFixture(TRandomHashFast_TChecksummedByteCollectionTest);
   //TDUnitX.RegisterTextFixture(TRandomHashStressTest);
 {$ENDIF FPC}