@@ -10,6 +10,10 @@ final class FastSet
1010
1111 private readonly string $ indexPath ;
1212
13+ private readonly int $ fingerprintByteLength ;
14+
15+ private readonly int $ storedTailByteLength ;
16+
1317 private bool $ isInitialized = false ;
1418
1519 private string $ blob = '' ;
@@ -33,18 +37,27 @@ final class FastSet
3337 */
3438 private array $ prefixOffsets = [];
3539
36- public function __construct (private readonly string $ directory )
37- {
38- $ this ->hashesPath = $ this ->directory .'/hashes.bin ' ;
39- $ this ->indexPath = $ this ->directory .'/index.bin ' ;
40-
40+ public function __construct (
41+ private readonly string $ directory ,
42+ private readonly string $ hashAlgorithm = 'xxh64 ' ,
43+ ) {
4144 if (!is_dir ($ this ->directory )) {
4245 throw new \InvalidArgumentException ('Directory does not exist. ' );
4346 }
4447
45- if (!\in_array ('xxh128 ' , hash_algos (), true )) {
46- throw new \LogicException ('Requires xxh128 hash algorithm. ' );
48+ if (!\in_array ($ this ->hashAlgorithm , ['xxh64 ' , 'xxh128 ' ], true )) {
49+ throw new \LogicException (\sprintf ('Unsupported hash algorithm "%s". Use "xxh64" or "xxh128". ' , $ this ->hashAlgorithm ));
50+ }
51+
52+ if (!\in_array ($ this ->hashAlgorithm , hash_algos (), true )) {
53+ throw new \LogicException ('Desired hash algorithm is not available. ' );
4754 }
55+
56+ $ this ->fingerprintByteLength = 'xxh128 ' === $ this ->hashAlgorithm ? 16 : 8 ;
57+ $ this ->storedTailByteLength = $ this ->fingerprintByteLength - 2 ;
58+
59+ $ this ->indexPath = $ this ->directory .'/index_ ' .$ this ->hashAlgorithm .'.bin ' ;
60+ $ this ->hashesPath = $ this ->directory .'/hashes_ ' .$ this ->hashAlgorithm .'.bin ' ;
4861 }
4962
5063 public function has (string $ entry ): bool
@@ -68,15 +81,19 @@ public function has(string $entry): bool
6881 return false ;
6982 }
7083
84+ $ queryFingerprintTailBytes = substr ($ fingerprint , 2 , $ this ->storedTailByteLength );
85+
7186 // Binary search within that bucket
7287 $ low = $ startIndex ;
7388 $ high = $ endIndex - 1 ;
7489
7590 while ($ low <= $ high ) {
7691 $ mid = $ low + $ high >> 1 ;
77- $ midFingerprint = substr ($ this ->blob , $ mid * 16 , 16 );
7892
79- $ cmp = strcmp ($ midFingerprint , $ fingerprint );
93+ $ middleTailByteOffset = $ mid * $ this ->storedTailByteLength ;
94+ $ middleFingerprintTailBytes = substr ($ this ->blob , $ middleTailByteOffset , $ this ->storedTailByteLength );
95+
96+ $ cmp = strcmp ($ middleFingerprintTailBytes , $ queryFingerprintTailBytes );
8097 if (0 === $ cmp ) {
8198 return true ;
8299 }
@@ -118,8 +135,9 @@ function (string $entry) use (&$fingerPrints): void {
118135 $ prefixKey = $ this ->getPrefixKey ($ fingerprint );
119136 ++$ prefixCounts [$ prefixKey ];
120137
121- // Each fingerprint is exactly 16 bytes
122- fwrite ($ hashFile , $ fingerprint );
138+ // Skip the first 2 bytes in our hashes.bin - they are already part of the index.bin
139+ // so we can save 2 bytes per fingerprint to reduce our memory footprint even more
140+ fwrite ($ hashFile , substr ($ fingerprint , 2 , $ this ->storedTailByteLength ));
123141 }
124142
125143 fclose ($ hashFile );
@@ -168,7 +186,7 @@ public function getPrefixKey(string $fingerprint): int
168186
169187 private function getFingerPrintForEntry (string $ entry ): string
170188 {
171- return hash (' xxh128 ' , $ entry , true );
189+ return hash ($ this -> hashAlgorithm , $ entry , true );
172190 }
173191
174192 /**
0 commit comments