From 9b1529aaa16b59901b6078ce0826e433888a163e Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Fri, 27 Feb 2026 02:29:00 -0300 Subject: [PATCH 01/49] trying out the machine --- app/Parser.php | 191 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 188 insertions(+), 3 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index b74cf7b9f7..72bce07009 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -1,13 +1,198 @@ ($year % 4 === 0) ? 29 : 28, + 4, 6, 9, 11 => 30, + default => 31, + }; + + $monthStr = $month < 10 ? "0{$month}" : (string) $month; + $datePrefix = "{$year}-{$monthStr}-"; + + for ($day = 1; $day <= $daysInMonth; $day++) { + $key = $datePrefix . ($day < 10 ? "0{$day}" : (string) $day); + $dateChars[$key] = \chr($numDates & 0xFF) . \chr($numDates >> 8); + $dateLabels[$numDates] = $key; + $numDates++; + } + } + } + + $fileHandle = \fopen($inputPath, 'rb'); + \stream_set_read_buffer($fileHandle, 0); + $sample = \fread($fileHandle, \min($fileSize, self::PROBE_SIZE)); + \fclose($fileHandle); + + $slugIndex = []; + $slugLabels = []; + $numSlugs = 0; + $bound = \strrpos($sample, "\n"); + + for ($pos = 0; $pos < $bound;) { + $newlinePos = \strpos($sample, "\n", $pos + 52); + + if ($newlinePos === false) { + break; + } + + $slug = \substr($sample, $pos + 25, $newlinePos - $pos - 51); + + if (!isset($slugIndex[$slug])) { + $slugIndex[$slug] = $numSlugs; + $slugLabels[$numSlugs] = $slug; + $numSlugs++; + } + + $pos = $newlinePos + 1; + } + + unset($sample); + + foreach (Visit::all() as $visit) { + $slug = \substr($visit->uri, 25); + + if (!isset($slugIndex[$slug])) { + $slugIndex[$slug] = $numSlugs; + $slugLabels[$numSlugs] = $slug; + $numSlugs++; + } + } + + $bins = \array_fill(0, $numSlugs, ''); + + $fileHandle = \fopen($inputPath, 'rb'); + \stream_set_read_buffer($fileHandle, 0); + $remaining = $fileSize; + + while ($remaining > 0) { + $chunk = \fread($fileHandle, $remaining > self::CHUNK_SIZE ? self::CHUNK_SIZE : $remaining); + $chunkLength = \strlen($chunk); + + if ($chunkLength === 0) { + break; + } + + $remaining -= $chunkLength; + + $lastNl = \strrpos($chunk, "\n"); + + if ($lastNl === false) { + break; + } + + $over = $chunkLength - $lastNl - 1; + if ($over > 0) { + \fseek($fileHandle, -$over, \SEEK_CUR); + $remaining += $over; + } + + $pos = 0; + $safe = $lastNl - 320; + while ($pos < $safe) { + $newlinePos = \strpos($chunk, "\n", $pos + 52); + $bins[$slugIndex[\substr($chunk, $pos + 25, $newlinePos - $pos - 51)]] .= $dateChars[\substr($chunk, $newlinePos - 23, 8)]; + $pos = $newlinePos + 1; + + $newlinePos = \strpos($chunk, "\n", $pos + 52); + $bins[$slugIndex[\substr($chunk, $pos + 25, $newlinePos - $pos - 51)]] .= $dateChars[\substr($chunk, $newlinePos - 23, 8)]; + $pos = $newlinePos + 1; + + $newlinePos = \strpos($chunk, "\n", $pos + 52); + $bins[$slugIndex[\substr($chunk, $pos + 25, $newlinePos - $pos - 51)]] .= $dateChars[\substr($chunk, $newlinePos - 23, 8)]; + $pos = $newlinePos + 1; + + $newlinePos = \strpos($chunk, "\n", $pos + 52); + $bins[$slugIndex[\substr($chunk, $pos + 25, $newlinePos - $pos - 51)]] .= $dateChars[\substr($chunk, $newlinePos - 23, 8)]; + $pos = $newlinePos + 1; + } + + while ($pos < $lastNl) { + $newlinePos = \strpos($chunk, "\n", $pos + 52); + if ($newlinePos === false) break; + $bins[$slugIndex[\substr($chunk, $pos + 25, $newlinePos - $pos - 51)]] .= $dateChars[\substr($chunk, $newlinePos - 23, 8)]; + $pos = $newlinePos + 1; + } + } + + \fclose($fileHandle); + + $grid = \array_fill(0, $numSlugs * $numDates, 0); + for ($slugId = 0; $slugId < $numSlugs; $slugId++) { + if ($bins[$slugId] === '') { + continue; + } + + $base = $slugId * $numDates; + foreach (\array_count_values(\unpack('v*', $bins[$slugId])) as $dateId => $count) { + $grid[$base + $dateId] = $count; + } + } + unset($bins); + + $outputHandle = \fopen($outputPath, 'wb'); + \stream_set_write_buffer($outputHandle, self::WRITE_BUF); + + $datePfx = []; + for ($dateId = 0; $dateId < $numDates; $dateId++) { + $datePfx[$dateId] = ' "20' . $dateLabels[$dateId] . '": '; + } + + $slugHdr = []; + for ($slugId = 0; $slugId < $numSlugs; $slugId++) { + $slugHdr[$slugId] = '"\\/blog\\/' . \str_replace('/', '\\/', $slugLabels[$slugId]) . '"'; + } + + \fwrite($outputHandle, '{'); + $first = true; + + for ($slugId = 0; $slugId < $numSlugs; $slugId++) { + $base = $slugId * $numDates; + $body = ''; + $separator = ''; + + for ($dateId = 0; $dateId < $numDates; $dateId++) { + $count = $grid[$base + $dateId]; + + if ($count === 0) { + continue; + } + + $body .= $separator . $datePfx[$dateId] . $count; + $separator = ",\n"; + } + + if ($body === '') { + continue; + } + + \fwrite($outputHandle, ($first ? '' : ',') . "\n " . $slugHdr[$slugId] . ": {\n" . $body . "\n }"); + $first = false; + } + + \fwrite($outputHandle, "\n}"); + \fclose($outputHandle); } -} \ No newline at end of file +} From 0c3b04ff1ef37c535954f8be7d52ebbb97eeea14 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Sat, 28 Feb 2026 01:09:33 -0300 Subject: [PATCH 02/49] comma search, 8x unroll --- app/Parser.php | 68 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 20 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index 72bce07009..8b5071ad0c 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -15,6 +15,7 @@ final class Parser public function parse(string $inputPath, string $outputPath): void { \gc_disable(); + $fileSize = \filesize($inputPath); $dateChars = []; @@ -49,7 +50,8 @@ public function parse(string $inputPath, string $outputPath): void $slugIndex = []; $slugLabels = []; $numSlugs = 0; - $bound = \strrpos($sample, "\n"); + + $bound = \strrpos($sample, "\n"); for ($pos = 0; $pos < $bound;) { $newlinePos = \strpos($sample, "\n", $pos + 52); @@ -109,47 +111,73 @@ public function parse(string $inputPath, string $outputPath): void $remaining += $over; } - $pos = 0; - $safe = $lastNl - 320; + $pos = 25; + + $safe = $lastNl - 480; + while ($pos < $safe) { - $newlinePos = \strpos($chunk, "\n", $pos + 52); - $bins[$slugIndex[\substr($chunk, $pos + 25, $newlinePos - $pos - 51)]] .= $dateChars[\substr($chunk, $newlinePos - 23, 8)]; - $pos = $newlinePos + 1; + $sep = \strpos($chunk, ',', $pos); + $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $pos = $sep + 52; + + $sep = \strpos($chunk, ',', $pos); + $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $pos = $sep + 52; + + $sep = \strpos($chunk, ',', $pos); + $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $pos = $sep + 52; + + $sep = \strpos($chunk, ',', $pos); + $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $pos = $sep + 52; + + $sep = \strpos($chunk, ',', $pos); + $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $pos = $sep + 52; - $newlinePos = \strpos($chunk, "\n", $pos + 52); - $bins[$slugIndex[\substr($chunk, $pos + 25, $newlinePos - $pos - 51)]] .= $dateChars[\substr($chunk, $newlinePos - 23, 8)]; - $pos = $newlinePos + 1; + $sep = \strpos($chunk, ',', $pos); + $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $pos = $sep + 52; - $newlinePos = \strpos($chunk, "\n", $pos + 52); - $bins[$slugIndex[\substr($chunk, $pos + 25, $newlinePos - $pos - 51)]] .= $dateChars[\substr($chunk, $newlinePos - 23, 8)]; - $pos = $newlinePos + 1; + $sep = \strpos($chunk, ',', $pos); + $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $pos = $sep + 52; - $newlinePos = \strpos($chunk, "\n", $pos + 52); - $bins[$slugIndex[\substr($chunk, $pos + 25, $newlinePos - $pos - 51)]] .= $dateChars[\substr($chunk, $newlinePos - 23, 8)]; - $pos = $newlinePos + 1; + $sep = \strpos($chunk, ',', $pos); + $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $pos = $sep + 52; } while ($pos < $lastNl) { - $newlinePos = \strpos($chunk, "\n", $pos + 52); - if ($newlinePos === false) break; - $bins[$slugIndex[\substr($chunk, $pos + 25, $newlinePos - $pos - 51)]] .= $dateChars[\substr($chunk, $newlinePos - 23, 8)]; - $pos = $newlinePos + 1; + $sep = \strpos($chunk, ',', $pos); + + if ($sep === false || $sep >= $lastNl) { + break; + } + + $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + + $pos = $sep + 52; } } \fclose($fileHandle); $grid = \array_fill(0, $numSlugs * $numDates, 0); + for ($slugId = 0; $slugId < $numSlugs; $slugId++) { if ($bins[$slugId] === '') { continue; } $base = $slugId * $numDates; + foreach (\array_count_values(\unpack('v*', $bins[$slugId])) as $dateId => $count) { $grid[$base + $dateId] = $count; } } + unset($bins); $outputHandle = \fopen($outputPath, 'wb'); @@ -162,7 +190,7 @@ public function parse(string $inputPath, string $outputPath): void $slugHdr = []; for ($slugId = 0; $slugId < $numSlugs; $slugId++) { - $slugHdr[$slugId] = '"\\/blog\\/' . \str_replace('/', '\\/', $slugLabels[$slugId]) . '"'; + $slugHdr[$slugId] = '"\/blog\/' . \str_replace('/', '\/', $slugLabels[$slugId]) . '"'; } \fwrite($outputHandle, '{'); From 70b7278f504690cb442be7e13be592d32be3ca6c Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Sat, 28 Feb 2026 12:19:39 -0300 Subject: [PATCH 03/49] skips composer autoloader and tempest boot --- tempest | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tempest b/tempest index 69a85f55c9..28683e31c7 100755 --- a/tempest +++ b/tempest @@ -1,10 +1,21 @@ #!/usr/bin/env php run(); -exit; \ No newline at end of file +exit(); From f1bb82831471976a81d7e1ce7482fa47e387f39c Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Sat, 28 Feb 2026 12:21:40 -0300 Subject: [PATCH 04/49] bucket accumulation to direct counting --- app/Parser.php | 51 ++++++++++++++++---------------------------------- 1 file changed, 16 insertions(+), 35 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index 8b5071ad0c..fa7630a15a 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -12,13 +12,13 @@ final class Parser private const int PROBE_SIZE = 2_097_152; private const int WRITE_BUF = 1_048_576; - public function parse(string $inputPath, string $outputPath): void + public static function parse(string $inputPath, string $outputPath): void { \gc_disable(); $fileSize = \filesize($inputPath); - $dateChars = []; + $dateIds = []; $dateLabels = []; $numDates = 0; @@ -35,7 +35,7 @@ public function parse(string $inputPath, string $outputPath): void for ($day = 1; $day <= $daysInMonth; $day++) { $key = $datePrefix . ($day < 10 ? "0{$day}" : (string) $day); - $dateChars[$key] = \chr($numDates & 0xFF) . \chr($numDates >> 8); + $dateIds[$key] = $numDates; $dateLabels[$numDates] = $key; $numDates++; } @@ -50,8 +50,7 @@ public function parse(string $inputPath, string $outputPath): void $slugIndex = []; $slugLabels = []; $numSlugs = 0; - - $bound = \strrpos($sample, "\n"); + $bound = \strrpos($sample, "\n"); for ($pos = 0; $pos < $bound;) { $newlinePos = \strpos($sample, "\n", $pos + 52); @@ -83,7 +82,7 @@ public function parse(string $inputPath, string $outputPath): void } } - $bins = \array_fill(0, $numSlugs, ''); + $counts = \array_fill(0, $numSlugs * $numDates, 0); $fileHandle = \fopen($inputPath, 'rb'); \stream_set_read_buffer($fileHandle, 0); @@ -111,41 +110,40 @@ public function parse(string $inputPath, string $outputPath): void $remaining += $over; } - $pos = 25; - + $pos = 25; $safe = $lastNl - 480; while ($pos < $safe) { $sep = \strpos($chunk, ',', $pos); - $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; $sep = \strpos($chunk, ',', $pos); - $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; $sep = \strpos($chunk, ',', $pos); - $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; $sep = \strpos($chunk, ',', $pos); - $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; $sep = \strpos($chunk, ',', $pos); - $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; $sep = \strpos($chunk, ',', $pos); - $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; $sep = \strpos($chunk, ',', $pos); - $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; $sep = \strpos($chunk, ',', $pos); - $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; } @@ -156,30 +154,13 @@ public function parse(string $inputPath, string $outputPath): void break; } - $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; - + $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; } } \fclose($fileHandle); - $grid = \array_fill(0, $numSlugs * $numDates, 0); - - for ($slugId = 0; $slugId < $numSlugs; $slugId++) { - if ($bins[$slugId] === '') { - continue; - } - - $base = $slugId * $numDates; - - foreach (\array_count_values(\unpack('v*', $bins[$slugId])) as $dateId => $count) { - $grid[$base + $dateId] = $count; - } - } - - unset($bins); - $outputHandle = \fopen($outputPath, 'wb'); \stream_set_write_buffer($outputHandle, self::WRITE_BUF); @@ -202,7 +183,7 @@ public function parse(string $inputPath, string $outputPath): void $separator = ''; for ($dateId = 0; $dateId < $numDates; $dateId++) { - $count = $grid[$base + $dateId]; + $count = $counts[$base + $dateId]; if ($count === 0) { continue; From f6bf4e82335ab3a7f0377a037a639359856e969e Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Sat, 28 Feb 2026 18:07:11 -0300 Subject: [PATCH 05/49] pre-multiply slug base offsets, widen unroll fence to 600 --- app/Parser.php | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index fa7630a15a..9404c5915f 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -16,8 +16,7 @@ public static function parse(string $inputPath, string $outputPath): void { \gc_disable(); - $fileSize = \filesize($inputPath); - + $fileSize = \filesize($inputPath); $dateIds = []; $dateLabels = []; $numDates = 0; @@ -34,7 +33,7 @@ public static function parse(string $inputPath, string $outputPath): void $datePrefix = "{$year}-{$monthStr}-"; for ($day = 1; $day <= $daysInMonth; $day++) { - $key = $datePrefix . ($day < 10 ? "0{$day}" : (string) $day); + $key = $datePrefix . ($day < 10 ? "0{$day}" : (string) $day); $dateIds[$key] = $numDates; $dateLabels[$numDates] = $key; $numDates++; @@ -47,7 +46,7 @@ public static function parse(string $inputPath, string $outputPath): void $sample = \fread($fileHandle, \min($fileSize, self::PROBE_SIZE)); \fclose($fileHandle); - $slugIndex = []; + $slugBase = []; $slugLabels = []; $numSlugs = 0; $bound = \strrpos($sample, "\n"); @@ -61,8 +60,8 @@ public static function parse(string $inputPath, string $outputPath): void $slug = \substr($sample, $pos + 25, $newlinePos - $pos - 51); - if (!isset($slugIndex[$slug])) { - $slugIndex[$slug] = $numSlugs; + if (!isset($slugBase[$slug])) { + $slugBase[$slug] = $numSlugs * $numDates; $slugLabels[$numSlugs] = $slug; $numSlugs++; } @@ -75,18 +74,17 @@ public static function parse(string $inputPath, string $outputPath): void foreach (Visit::all() as $visit) { $slug = \substr($visit->uri, 25); - if (!isset($slugIndex[$slug])) { - $slugIndex[$slug] = $numSlugs; + if (!isset($slugBase[$slug])) { + $slugBase[$slug] = $numSlugs * $numDates; $slugLabels[$numSlugs] = $slug; $numSlugs++; } } - $counts = \array_fill(0, $numSlugs * $numDates, 0); - + $counts = \array_fill(0, $numSlugs * $numDates, 0); $fileHandle = \fopen($inputPath, 'rb'); \stream_set_read_buffer($fileHandle, 0); - $remaining = $fileSize; + $remaining = $fileSize; while ($remaining > 0) { $chunk = \fread($fileHandle, $remaining > self::CHUNK_SIZE ? self::CHUNK_SIZE : $remaining); @@ -111,39 +109,39 @@ public static function parse(string $inputPath, string $outputPath): void } $pos = 25; - $safe = $lastNl - 480; + $safe = $lastNl - 600; while ($pos < $safe) { $sep = \strpos($chunk, ',', $pos); - $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; $sep = \strpos($chunk, ',', $pos); - $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; $sep = \strpos($chunk, ',', $pos); - $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; $sep = \strpos($chunk, ',', $pos); - $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; $sep = \strpos($chunk, ',', $pos); - $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; $sep = \strpos($chunk, ',', $pos); - $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; $sep = \strpos($chunk, ',', $pos); - $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; $sep = \strpos($chunk, ',', $pos); - $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; } @@ -154,7 +152,7 @@ public static function parse(string $inputPath, string $outputPath): void break; } - $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; } } @@ -162,6 +160,7 @@ public static function parse(string $inputPath, string $outputPath): void \fclose($fileHandle); $outputHandle = \fopen($outputPath, 'wb'); + \stream_set_write_buffer($outputHandle, self::WRITE_BUF); $datePfx = []; From 8eb38bf371250e22fc3ba7fdf744fbba7b332a42 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Sun, 1 Mar 2026 14:01:39 -0300 Subject: [PATCH 06/49] no 2020 --- app/Parser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Parser.php b/app/Parser.php index 9404c5915f..f0220fb8b7 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -21,7 +21,7 @@ public static function parse(string $inputPath, string $outputPath): void $dateLabels = []; $numDates = 0; - for ($year = 20; $year <= 26; $year++) { + for ($year = 21; $year <= 26; $year++) { for ($month = 1; $month <= 12; $month++) { $daysInMonth = match ($month) { 2 => ($year % 4 === 0) ? 29 : 28, From 3cff60028fb2f52fe36b1e40612f033c012b5c90 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Sun, 1 Mar 2026 14:25:56 -0300 Subject: [PATCH 07/49] then I guess yes 2020? --- app/Parser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Parser.php b/app/Parser.php index f0220fb8b7..9404c5915f 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -21,7 +21,7 @@ public static function parse(string $inputPath, string $outputPath): void $dateLabels = []; $numDates = 0; - for ($year = 21; $year <= 26; $year++) { + for ($year = 20; $year <= 26; $year++) { for ($month = 1; $month <= 12; $month++) { $daysInMonth = match ($month) { 2 => ($year % 4 === 0) ? 29 : 28, From f8a864c33b91d2314e20aa206b8d067bf2819413 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Sun, 1 Mar 2026 20:57:52 -0300 Subject: [PATCH 08/49] no 2020, smaller chunk size --- app/Parser.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index 9404c5915f..34040ed310 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -8,7 +8,7 @@ final class Parser { - private const int CHUNK_SIZE = 536_870_912; + private const int CHUNK_SIZE = 262_144; private const int PROBE_SIZE = 2_097_152; private const int WRITE_BUF = 1_048_576; @@ -21,7 +21,7 @@ public static function parse(string $inputPath, string $outputPath): void $dateLabels = []; $numDates = 0; - for ($year = 20; $year <= 26; $year++) { + for ($year = 21; $year <= 26; $year++) { for ($month = 1; $month <= 12; $month++) { $daysInMonth = match ($month) { 2 => ($year % 4 === 0) ? 29 : 28, From 7ac2f009554aa139a2dccb826bf14860d97d71d5 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Sun, 1 Mar 2026 21:05:06 -0300 Subject: [PATCH 09/49] skips php shutdown sequence --- tempest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tempest b/tempest index 28683e31c7..8d0248534f 100755 --- a/tempest +++ b/tempest @@ -9,7 +9,7 @@ if (($argv[1] ?? null) === "data:parse") { $inputPath = \substr($argv[2] ?? "", 13); $outputPath = \substr($argv[3] ?? "", 14); \App\Parser::parse($inputPath, $outputPath); - return; + exit(0); } use Tempest\Console\ConsoleApplication; From 12072f32f63c0eec89bf0c3abbceb70d5fcfc62b Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Mon, 9 Mar 2026 12:03:33 -0300 Subject: [PATCH 10/49] less obj instantiation, datePfxC --- app/Commands/Visit.php | 547 +++++++++++++++++++++-------------------- app/Parser.php | 40 ++- 2 files changed, 305 insertions(+), 282 deletions(-) diff --git a/app/Commands/Visit.php b/app/Commands/Visit.php index c1ea538d5c..12372328b3 100644 --- a/app/Commands/Visit.php +++ b/app/Commands/Visit.php @@ -6,278 +6,283 @@ final class Visit { public function __construct(public string $uri) {} + public const array SLUGS = [ + 'which-editor-to-choose', + 'tackling_responsive_images-part_1', + 'tackling_responsive_images-part_2', + 'image_optimizers', + 'static_sites_vs_caching', + 'stitcher-alpha-4', + 'simplest-plugin-support', + 'stitcher-alpha-5', + 'php-generics-and-why-we-need-them', + 'stitcher-beta-1', + 'array-objects-with-fixed-types', + 'performance-101-building-the-better-web', + 'process-forks', + 'object-oriented-generators', + 'responsive-images-as-css-background', + 'a-programmers-cognitive-load', + 'mastering-key-bindings', + 'stitcher-beta-2', + 'phpstorm-performance', + 'optimised-uuids-in-mysql', + 'asynchronous-php', + 'mysql-import-json-binary-character-set', + 'where-a-curly-bracket-belongs', + 'mysql-query-logging', + 'mysql-show-foreign-key-errors', + 'responsive-images-done-right', + 'phpstorm-tips-for-power-users', + 'what-php-can-be', + 'phpstorm-performance-issues-on-osx', + 'dependency-injection-for-beginners', + 'liskov-and-type-safety', + 'acquisition-by-giants', + 'visual-perception-of-code', + 'service-locator-anti-pattern', + 'the-web-in-2045', + 'eloquent-mysql-views', + 'laravel-view-models', + 'laravel-view-models-vs-view-composers', + 'organise-by-domain', + 'array-merge-vs + ', + 'share-a-blog-assertchris-io', + 'phpstorm-performance-october-2018', + 'structuring-unstructured-data', + 'share-a-blog-codingwriter-com', + 'new-in-php-73', + 'share-a-blog-betterwebtype-com', + 'have-you-thought-about-casing', + 'comparing-dates', + 'share-a-blog-sebastiandedeyne-com', + 'analytics-for-developers', + 'announcing-aggregate', + 'php-jit', + 'craftsmen-know-their-tools', + 'laravel-queueable-actions', + 'php-73-upgrade-mac', + 'array-destructuring-with-list-in-php', + 'unsafe-sql-functions-in-laravel', + 'starting-a-newsletter', + 'short-closures-in-php', + 'solid-interfaces-and-final-rant-with-brent', + 'php-in-2019', + 'starting-a-podcast', + 'a-project-at-spatie', + 'what-are-objects-anyway-rant-with-brent', + 'tests-and-types', + 'typed-properties-in-php-74', + 'preloading-in-php-74', + 'things-dependency-injection-is-not-about', + 'a-letter-to-the-php-team', + 'a-letter-to-the-php-team-reply-to-joe', + 'guest-posts', + 'can-i-translate-your-blog', + 'laravel-has-many-through', + 'laravel-custom-relation-classes', + 'new-in-php-74', + 'php-74-upgrade-mac', + 'php-preload-benchmarks', + 'php-in-2020', + 'enums-without-enums', + 'bitwise-booleans-in-php', + 'event-driven-php', + 'minor-versions-breaking-changes', + 'combining-event-sourcing-and-stateful-systems', + 'array-chunk-in-php', + 'php-8-in-8-code-blocks', + 'builders-and-architects-two-types-of-programmers', + 'the-ikea-effect', + 'php-74-in-7-code-blocks', + 'improvements-on-laravel-nova', + 'type-system-in-php-survey', + 'merging-multidimensional-arrays-in-php', + 'what-is-array-plus-in-php', + 'type-system-in-php-survey-results', + 'constructor-promotion-in-php-8', + 'abstract-resources-in-laravel-nova', + 'braille-and-the-history-of-software', + 'jit-in-real-life-web-applications', + 'php-8-match-or-switch', + 'why-we-need-named-params-in-php', + 'shorthand-comparisons-in-php', + 'php-8-before-and-after', + 'php-8-named-arguments', + 'my-journey-into-event-sourcing', + 'differences', + 'annotations', + 'dont-get-stuck', + 'attributes-in-php-8', + 'the-case-for-transpiled-generics', + 'phpstorm-scopes', + 'why-light-themes-are-better-according-to-science', + 'what-a-good-pr-looks-like', + 'front-line-php', + 'php-8-jit-setup', + 'php-8-nullsafe-operator', + 'new-in-php-8', + 'php-8-upgrade-mac', + 'when-i-lost-a-few-hundred-leads', + 'websites-like-star-wars', + 'php-reimagined', + 'a-storm-in-a-glass-of-water', + 'php-enums-before-php-81', + 'php-enums', + 'dont-write-your-own-framework', + 'honesty', + 'thoughts-on-event-sourcing', + 'what-event-sourcing-is-not-about', + 'fibers-with-a-grain-of-salt', + 'php-in-2021', + 'parallel-php', + 'why-we-need-multi-line-short-closures-in-php', + 'a-new-major-version-of-laravel-event-sourcing', + 'what-about-config-builders', + 'opinion-driven-design', + 'php-version-stats-july-2021', + 'what-about-request-classes', + 'cloning-readonly-properties-in-php-81', + 'an-event-driven-mindset', + 'php-81-before-and-after', + 'optimistic-or-realistic-estimates', + 'we-dont-need-runtime-type-checks', + 'the-road-to-php', + 'why-do-i-write', + 'rational-thinking', + 'named-arguments-and-variadic-functions', + 're-on-using-psr-abstractions', + 'my-ikea-clock', + 'php-81-readonly-properties', + 'birth-and-death-of-a-framework', + 'php-81-new-in-initializers', + 'route-attributes', + 'generics-in-php-video', + 'php-81-in-8-code-blocks', + 'new-in-php-81', + 'php-81-performance-in-real-life', + 'php-81-upgrade-mac', + 'how-to-be-right-on-the-internet', + 'php-version-stats-january-2022', + 'php-in-2022', + 'how-i-plan', + 'twitter-home-made-me-miserable', + 'its-your-fault', + 'dealing-with-dependencies', + 'php-in-2021-video', + 'generics-in-php-1', + 'generics-in-php-2', + 'generics-in-php-3', + 'generics-in-php-4', + 'goodbye', + 'strategies', + 'dealing-with-deprecations', + 'attribute-usage-in-top-php-packages', + 'php-enum-style-guide', + 'clean-and-minimalistic-phpstorm', + 'stitcher-turns-5', + 'php-version-stats-july-2022', + 'evolution-of-a-php-object', + 'uncertainty-doubt-and-static-analysis', + 'road-to-php-82', + 'php-performance-across-versions', + 'light-colour-schemes-are-better', + 'deprecated-dynamic-properties-in-php-82', + 'php-reimagined-part-2', + 'thoughts-on-asymmetric-visibility', + 'uses', + 'php-82-in-8-code-blocks', + 'readonly-classes-in-php-82', + 'deprecating-spatie-dto', + 'php-82-upgrade-mac', + 'php-annotated', + 'you-cannot-find-me-on-mastodon', + 'new-in-php-82', + 'all-i-want-for-christmas', + 'upgrading-to-php-82', + 'php-version-stats-january-2023', + 'php-in-2023', + 'tabs-are-better', + 'sponsors', + 'why-curly-brackets-go-on-new-lines', + 'my-10-favourite-php-functions', + 'acronyms', + 'code-folding', + 'light-colour-schemes', + 'slashdash', + 'thank-you-kinsta', + 'cloning-readonly-properties-in-php-83', + 'limited-by-committee', + 'things-considered-harmful', + 'procedurally-generated-game-in-php', + 'dont-be-clever', + 'override-in-php-83', + 'php-version-stats-july-2023', + 'is-a-or-acts-as', + 'rfc-vote', + 'new-in-php-83', + 'i-dont-know', + 'passion-projects', + 'php-version-stats-january-2024', + 'the-framework-that-gets-out-of-your-way', + 'a-syntax-highlighter-that-doesnt-suck', + 'building-a-custom-language-in-tempest-highlight', + 'testing-patterns', + 'php-in-2024', + 'tagged-singletons', + 'twitter-exit', + 'a-vocal-minority', + 'php-version-stats-july-2024', + 'you-should', + 'new-with-parentheses-php-84', + 'html-5-in-php-84', + 'array-find-in-php-84', + 'its-all-just-text', + 'improved-lazy-loading', + 'i-dont-code-the-way-i-used-to', + 'php-84-at-least', + 'extends-vs-implements', + 'a-simple-approach-to-static-generation', + 'building-a-framework', + 'tagging-tempest-livestream', + 'things-i-learned-writing-a-fiction-novel', + 'unfair-advantage', + 'new-in-php-84', + 'php-version-stats-january-2025', + 'theoretical-engineers', + 'static-websites-with-tempest', + 'request-objects-in-tempest', + 'php-verse-2025', + 'tempest-discovery-explained', + 'php-version-stats-june-2025', + 'pipe-operator-in-php-85', + 'a-year-of-property-hooks', + 'readonly-or-private-set', + 'things-i-wish-i-knew', + 'impact-charts', + 'whats-your-motivator', + 'vendor-locked', + 'reducing-code-motion', + 'sponsoring-open-source', + 'my-wishlist-for-php-in-2026', + 'game-changing-editions', + 'new-in-php-85', + 'flooded-rss', + 'php-2026', + 'open-source-strategies', + 'not-optional', + 'processing-11-million-rows', + 'ai-induced-skepticism', + 'php-86-partial-function-application', + '11-million-rows-in-seconds', + ]; + /** @return self[] */ public static function all(): array { - return [ - new self('https://stitcher.io/blog/which-editor-to-choose'), - new self('https://stitcher.io/blog/tackling_responsive_images-part_1'), - new self('https://stitcher.io/blog/tackling_responsive_images-part_2'), - new self('https://stitcher.io/blog/image_optimizers'), - new self('https://stitcher.io/blog/static_sites_vs_caching'), - new self('https://stitcher.io/blog/stitcher-alpha-4'), - new self('https://stitcher.io/blog/simplest-plugin-support'), - new self('https://stitcher.io/blog/stitcher-alpha-5'), - new self('https://stitcher.io/blog/php-generics-and-why-we-need-them'), - new self('https://stitcher.io/blog/stitcher-beta-1'), - new self('https://stitcher.io/blog/array-objects-with-fixed-types'), - new self('https://stitcher.io/blog/performance-101-building-the-better-web'), - new self('https://stitcher.io/blog/process-forks'), - new self('https://stitcher.io/blog/object-oriented-generators'), - new self('https://stitcher.io/blog/responsive-images-as-css-background'), - new self('https://stitcher.io/blog/a-programmers-cognitive-load'), - new self('https://stitcher.io/blog/mastering-key-bindings'), - new self('https://stitcher.io/blog/stitcher-beta-2'), - new self('https://stitcher.io/blog/phpstorm-performance'), - new self('https://stitcher.io/blog/optimised-uuids-in-mysql'), - new self('https://stitcher.io/blog/asynchronous-php'), - new self('https://stitcher.io/blog/mysql-import-json-binary-character-set'), - new self('https://stitcher.io/blog/where-a-curly-bracket-belongs'), - new self('https://stitcher.io/blog/mysql-query-logging'), - new self('https://stitcher.io/blog/mysql-show-foreign-key-errors'), - new self('https://stitcher.io/blog/responsive-images-done-right'), - new self('https://stitcher.io/blog/phpstorm-tips-for-power-users'), - new self('https://stitcher.io/blog/what-php-can-be'), - new self('https://stitcher.io/blog/phpstorm-performance-issues-on-osx'), - new self('https://stitcher.io/blog/dependency-injection-for-beginners'), - new self('https://stitcher.io/blog/liskov-and-type-safety'), - new self('https://stitcher.io/blog/acquisition-by-giants'), - new self('https://stitcher.io/blog/visual-perception-of-code'), - new self('https://stitcher.io/blog/service-locator-anti-pattern'), - new self('https://stitcher.io/blog/the-web-in-2045'), - new self('https://stitcher.io/blog/eloquent-mysql-views'), - new self('https://stitcher.io/blog/laravel-view-models'), - new self('https://stitcher.io/blog/laravel-view-models-vs-view-composers'), - new self('https://stitcher.io/blog/organise-by-domain'), - new self('https://stitcher.io/blog/array-merge-vs + '), - new self('https://stitcher.io/blog/share-a-blog-assertchris-io'), - new self('https://stitcher.io/blog/phpstorm-performance-october-2018'), - new self('https://stitcher.io/blog/structuring-unstructured-data'), - new self('https://stitcher.io/blog/share-a-blog-codingwriter-com'), - new self('https://stitcher.io/blog/new-in-php-73'), - new self('https://stitcher.io/blog/share-a-blog-betterwebtype-com'), - new self('https://stitcher.io/blog/have-you-thought-about-casing'), - new self('https://stitcher.io/blog/comparing-dates'), - new self('https://stitcher.io/blog/share-a-blog-sebastiandedeyne-com'), - new self('https://stitcher.io/blog/analytics-for-developers'), - new self('https://stitcher.io/blog/announcing-aggregate'), - new self('https://stitcher.io/blog/php-jit'), - new self('https://stitcher.io/blog/craftsmen-know-their-tools'), - new self('https://stitcher.io/blog/laravel-queueable-actions'), - new self('https://stitcher.io/blog/php-73-upgrade-mac'), - new self('https://stitcher.io/blog/array-destructuring-with-list-in-php'), - new self('https://stitcher.io/blog/unsafe-sql-functions-in-laravel'), - new self('https://stitcher.io/blog/starting-a-newsletter'), - new self('https://stitcher.io/blog/short-closures-in-php'), - new self('https://stitcher.io/blog/solid-interfaces-and-final-rant-with-brent'), - new self('https://stitcher.io/blog/php-in-2019'), - new self('https://stitcher.io/blog/starting-a-podcast'), - new self('https://stitcher.io/blog/a-project-at-spatie'), - new self('https://stitcher.io/blog/what-are-objects-anyway-rant-with-brent'), - new self('https://stitcher.io/blog/tests-and-types'), - new self('https://stitcher.io/blog/typed-properties-in-php-74'), - new self('https://stitcher.io/blog/preloading-in-php-74'), - new self('https://stitcher.io/blog/things-dependency-injection-is-not-about'), - new self('https://stitcher.io/blog/a-letter-to-the-php-team'), - new self('https://stitcher.io/blog/a-letter-to-the-php-team-reply-to-joe'), - new self('https://stitcher.io/blog/guest-posts'), - new self('https://stitcher.io/blog/can-i-translate-your-blog'), - new self('https://stitcher.io/blog/laravel-has-many-through'), - new self('https://stitcher.io/blog/laravel-custom-relation-classes'), - new self('https://stitcher.io/blog/new-in-php-74'), - new self('https://stitcher.io/blog/php-74-upgrade-mac'), - new self('https://stitcher.io/blog/php-preload-benchmarks'), - new self('https://stitcher.io/blog/php-in-2020'), - new self('https://stitcher.io/blog/enums-without-enums'), - new self('https://stitcher.io/blog/bitwise-booleans-in-php'), - new self('https://stitcher.io/blog/event-driven-php'), - new self('https://stitcher.io/blog/minor-versions-breaking-changes'), - new self('https://stitcher.io/blog/combining-event-sourcing-and-stateful-systems'), - new self('https://stitcher.io/blog/array-chunk-in-php'), - new self('https://stitcher.io/blog/php-8-in-8-code-blocks'), - new self('https://stitcher.io/blog/builders-and-architects-two-types-of-programmers'), - new self('https://stitcher.io/blog/the-ikea-effect'), - new self('https://stitcher.io/blog/php-74-in-7-code-blocks'), - new self('https://stitcher.io/blog/improvements-on-laravel-nova'), - new self('https://stitcher.io/blog/type-system-in-php-survey'), - new self('https://stitcher.io/blog/merging-multidimensional-arrays-in-php'), - new self('https://stitcher.io/blog/what-is-array-plus-in-php'), - new self('https://stitcher.io/blog/type-system-in-php-survey-results'), - new self('https://stitcher.io/blog/constructor-promotion-in-php-8'), - new self('https://stitcher.io/blog/abstract-resources-in-laravel-nova'), - new self('https://stitcher.io/blog/braille-and-the-history-of-software'), - new self('https://stitcher.io/blog/jit-in-real-life-web-applications'), - new self('https://stitcher.io/blog/php-8-match-or-switch'), - new self('https://stitcher.io/blog/why-we-need-named-params-in-php'), - new self('https://stitcher.io/blog/shorthand-comparisons-in-php'), - new self('https://stitcher.io/blog/php-8-before-and-after'), - new self('https://stitcher.io/blog/php-8-named-arguments'), - new self('https://stitcher.io/blog/my-journey-into-event-sourcing'), - new self('https://stitcher.io/blog/differences'), - new self('https://stitcher.io/blog/annotations'), - new self('https://stitcher.io/blog/dont-get-stuck'), - new self('https://stitcher.io/blog/attributes-in-php-8'), - new self('https://stitcher.io/blog/the-case-for-transpiled-generics'), - new self('https://stitcher.io/blog/phpstorm-scopes'), - new self('https://stitcher.io/blog/why-light-themes-are-better-according-to-science'), - new self('https://stitcher.io/blog/what-a-good-pr-looks-like'), - new self('https://stitcher.io/blog/front-line-php'), - new self('https://stitcher.io/blog/php-8-jit-setup'), - new self('https://stitcher.io/blog/php-8-nullsafe-operator'), - new self('https://stitcher.io/blog/new-in-php-8'), - new self('https://stitcher.io/blog/php-8-upgrade-mac'), - new self('https://stitcher.io/blog/when-i-lost-a-few-hundred-leads'), - new self('https://stitcher.io/blog/websites-like-star-wars'), - new self('https://stitcher.io/blog/php-reimagined'), - new self('https://stitcher.io/blog/a-storm-in-a-glass-of-water'), - new self('https://stitcher.io/blog/php-enums-before-php-81'), - new self('https://stitcher.io/blog/php-enums'), - new self('https://stitcher.io/blog/dont-write-your-own-framework'), - new self('https://stitcher.io/blog/honesty'), - new self('https://stitcher.io/blog/thoughts-on-event-sourcing'), - new self('https://stitcher.io/blog/what-event-sourcing-is-not-about'), - new self('https://stitcher.io/blog/fibers-with-a-grain-of-salt'), - new self('https://stitcher.io/blog/php-in-2021'), - new self('https://stitcher.io/blog/parallel-php'), - new self('https://stitcher.io/blog/why-we-need-multi-line-short-closures-in-php'), - new self('https://stitcher.io/blog/a-new-major-version-of-laravel-event-sourcing'), - new self('https://stitcher.io/blog/what-about-config-builders'), - new self('https://stitcher.io/blog/opinion-driven-design'), - new self('https://stitcher.io/blog/php-version-stats-july-2021'), - new self('https://stitcher.io/blog/what-about-request-classes'), - new self('https://stitcher.io/blog/cloning-readonly-properties-in-php-81'), - new self('https://stitcher.io/blog/an-event-driven-mindset'), - new self('https://stitcher.io/blog/php-81-before-and-after'), - new self('https://stitcher.io/blog/optimistic-or-realistic-estimates'), - new self('https://stitcher.io/blog/we-dont-need-runtime-type-checks'), - new self('https://stitcher.io/blog/the-road-to-php'), - new self('https://stitcher.io/blog/why-do-i-write'), - new self('https://stitcher.io/blog/rational-thinking'), - new self('https://stitcher.io/blog/named-arguments-and-variadic-functions'), - new self('https://stitcher.io/blog/re-on-using-psr-abstractions'), - new self('https://stitcher.io/blog/my-ikea-clock'), - new self('https://stitcher.io/blog/php-81-readonly-properties'), - new self('https://stitcher.io/blog/birth-and-death-of-a-framework'), - new self('https://stitcher.io/blog/php-81-new-in-initializers'), - new self('https://stitcher.io/blog/route-attributes'), - new self('https://stitcher.io/blog/generics-in-php-video'), - new self('https://stitcher.io/blog/php-81-in-8-code-blocks'), - new self('https://stitcher.io/blog/new-in-php-81'), - new self('https://stitcher.io/blog/php-81-performance-in-real-life'), - new self('https://stitcher.io/blog/php-81-upgrade-mac'), - new self('https://stitcher.io/blog/how-to-be-right-on-the-internet'), - new self('https://stitcher.io/blog/php-version-stats-january-2022'), - new self('https://stitcher.io/blog/php-in-2022'), - new self('https://stitcher.io/blog/how-i-plan'), - new self('https://stitcher.io/blog/twitter-home-made-me-miserable'), - new self('https://stitcher.io/blog/its-your-fault'), - new self('https://stitcher.io/blog/dealing-with-dependencies'), - new self('https://stitcher.io/blog/php-in-2021-video'), - new self('https://stitcher.io/blog/generics-in-php-1'), - new self('https://stitcher.io/blog/generics-in-php-2'), - new self('https://stitcher.io/blog/generics-in-php-3'), - new self('https://stitcher.io/blog/generics-in-php-4'), - new self('https://stitcher.io/blog/goodbye'), - new self('https://stitcher.io/blog/strategies'), - new self('https://stitcher.io/blog/dealing-with-deprecations'), - new self('https://stitcher.io/blog/attribute-usage-in-top-php-packages'), - new self('https://stitcher.io/blog/php-enum-style-guide'), - new self('https://stitcher.io/blog/clean-and-minimalistic-phpstorm'), - new self('https://stitcher.io/blog/stitcher-turns-5'), - new self('https://stitcher.io/blog/php-version-stats-july-2022'), - new self('https://stitcher.io/blog/evolution-of-a-php-object'), - new self('https://stitcher.io/blog/uncertainty-doubt-and-static-analysis'), - new self('https://stitcher.io/blog/road-to-php-82'), - new self('https://stitcher.io/blog/php-performance-across-versions'), - new self('https://stitcher.io/blog/light-colour-schemes-are-better'), - new self('https://stitcher.io/blog/deprecated-dynamic-properties-in-php-82'), - new self('https://stitcher.io/blog/php-reimagined-part-2'), - new self('https://stitcher.io/blog/thoughts-on-asymmetric-visibility'), - new self('https://stitcher.io/blog/uses'), - new self('https://stitcher.io/blog/php-82-in-8-code-blocks'), - new self('https://stitcher.io/blog/readonly-classes-in-php-82'), - new self('https://stitcher.io/blog/deprecating-spatie-dto'), - new self('https://stitcher.io/blog/php-82-upgrade-mac'), - new self('https://stitcher.io/blog/php-annotated'), - new self('https://stitcher.io/blog/you-cannot-find-me-on-mastodon'), - new self('https://stitcher.io/blog/new-in-php-82'), - new self('https://stitcher.io/blog/all-i-want-for-christmas'), - new self('https://stitcher.io/blog/upgrading-to-php-82'), - new self('https://stitcher.io/blog/php-version-stats-january-2023'), - new self('https://stitcher.io/blog/php-in-2023'), - new self('https://stitcher.io/blog/tabs-are-better'), - new self('https://stitcher.io/blog/sponsors'), - new self('https://stitcher.io/blog/why-curly-brackets-go-on-new-lines'), - new self('https://stitcher.io/blog/my-10-favourite-php-functions'), - new self('https://stitcher.io/blog/acronyms'), - new self('https://stitcher.io/blog/code-folding'), - new self('https://stitcher.io/blog/light-colour-schemes'), - new self('https://stitcher.io/blog/slashdash'), - new self('https://stitcher.io/blog/thank-you-kinsta'), - new self('https://stitcher.io/blog/cloning-readonly-properties-in-php-83'), - new self('https://stitcher.io/blog/limited-by-committee'), - new self('https://stitcher.io/blog/things-considered-harmful'), - new self('https://stitcher.io/blog/procedurally-generated-game-in-php'), - new self('https://stitcher.io/blog/dont-be-clever'), - new self('https://stitcher.io/blog/override-in-php-83'), - new self('https://stitcher.io/blog/php-version-stats-july-2023'), - new self('https://stitcher.io/blog/is-a-or-acts-as'), - new self('https://stitcher.io/blog/rfc-vote'), - new self('https://stitcher.io/blog/new-in-php-83'), - new self('https://stitcher.io/blog/i-dont-know'), - new self('https://stitcher.io/blog/passion-projects'), - new self('https://stitcher.io/blog/php-version-stats-january-2024'), - new self('https://stitcher.io/blog/the-framework-that-gets-out-of-your-way'), - new self('https://stitcher.io/blog/a-syntax-highlighter-that-doesnt-suck'), - new self('https://stitcher.io/blog/building-a-custom-language-in-tempest-highlight'), - new self('https://stitcher.io/blog/testing-patterns'), - new self('https://stitcher.io/blog/php-in-2024'), - new self('https://stitcher.io/blog/tagged-singletons'), - new self('https://stitcher.io/blog/twitter-exit'), - new self('https://stitcher.io/blog/a-vocal-minority'), - new self('https://stitcher.io/blog/php-version-stats-july-2024'), - new self('https://stitcher.io/blog/you-should'), - new self('https://stitcher.io/blog/new-with-parentheses-php-84'), - new self('https://stitcher.io/blog/html-5-in-php-84'), - new self('https://stitcher.io/blog/array-find-in-php-84'), - new self('https://stitcher.io/blog/its-all-just-text'), - new self('https://stitcher.io/blog/improved-lazy-loading'), - new self('https://stitcher.io/blog/i-dont-code-the-way-i-used-to'), - new self('https://stitcher.io/blog/php-84-at-least'), - new self('https://stitcher.io/blog/extends-vs-implements'), - new self('https://stitcher.io/blog/a-simple-approach-to-static-generation'), - new self('https://stitcher.io/blog/building-a-framework'), - new self('https://stitcher.io/blog/tagging-tempest-livestream'), - new self('https://stitcher.io/blog/things-i-learned-writing-a-fiction-novel'), - new self('https://stitcher.io/blog/unfair-advantage'), - new self('https://stitcher.io/blog/new-in-php-84'), - new self('https://stitcher.io/blog/php-version-stats-january-2025'), - new self('https://stitcher.io/blog/theoretical-engineers'), - new self('https://stitcher.io/blog/static-websites-with-tempest'), - new self('https://stitcher.io/blog/request-objects-in-tempest'), - new self('https://stitcher.io/blog/php-verse-2025'), - new self('https://stitcher.io/blog/tempest-discovery-explained'), - new self('https://stitcher.io/blog/php-version-stats-june-2025'), - new self('https://stitcher.io/blog/pipe-operator-in-php-85'), - new self('https://stitcher.io/blog/a-year-of-property-hooks'), - new self('https://stitcher.io/blog/readonly-or-private-set'), - new self('https://stitcher.io/blog/things-i-wish-i-knew'), - new self('https://stitcher.io/blog/impact-charts'), - new self('https://stitcher.io/blog/whats-your-motivator'), - new self('https://stitcher.io/blog/vendor-locked'), - new self('https://stitcher.io/blog/reducing-code-motion'), - new self('https://stitcher.io/blog/sponsoring-open-source'), - new self('https://stitcher.io/blog/my-wishlist-for-php-in-2026'), - new self('https://stitcher.io/blog/game-changing-editions'), - new self('https://stitcher.io/blog/new-in-php-85'), - new self('https://stitcher.io/blog/flooded-rss'), - new self('https://stitcher.io/blog/php-2026'), - new self('https://stitcher.io/blog/open-source-strategies'), - new self('https://stitcher.io/blog/not-optional'), - new self('https://stitcher.io/blog/processing-11-million-rows'), - new self('https://stitcher.io/blog/ai-induced-skepticism'), - new self('https://stitcher.io/blog/php-86-partial-function-application'), - new self('https://stitcher.io/blog/11-million-rows-in-seconds'), - ]; + return \array_map( + static fn(string $slug) => new self('https://stitcher.io/blog/' . $slug), + self::SLUGS, + ); } -} \ No newline at end of file +} diff --git a/app/Parser.php b/app/Parser.php index 34040ed310..f640763b3f 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -41,6 +41,8 @@ public static function parse(string $inputPath, string $outputPath): void } } + $numExpected = \count(Visit::SLUGS); + $fileHandle = \fopen($inputPath, 'rb'); \stream_set_read_buffer($fileHandle, 0); $sample = \fread($fileHandle, \min($fileSize, self::PROBE_SIZE)); @@ -64,6 +66,10 @@ public static function parse(string $inputPath, string $outputPath): void $slugBase[$slug] = $numSlugs * $numDates; $slugLabels[$numSlugs] = $slug; $numSlugs++; + + if ($numSlugs === $numExpected) { + break; + } } $pos = $newlinePos + 1; @@ -71,9 +77,7 @@ public static function parse(string $inputPath, string $outputPath): void unset($sample); - foreach (Visit::all() as $visit) { - $slug = \substr($visit->uri, 25); - + foreach (Visit::SLUGS as $slug) { if (!isset($slugBase[$slug])) { $slugBase[$slug] = $numSlugs * $numDates; $slugLabels[$numSlugs] = $slug; @@ -163,9 +167,12 @@ public static function parse(string $inputPath, string $outputPath): void \stream_set_write_buffer($outputHandle, self::WRITE_BUF); - $datePfx = []; + $datePfx = []; + $datePfxC = []; for ($dateId = 0; $dateId < $numDates; $dateId++) { - $datePfx[$dateId] = ' "20' . $dateLabels[$dateId] . '": '; + $entry = ' "20' . $dateLabels[$dateId] . '": '; + $datePfx[$dateId] = $entry; + $datePfxC[$dateId] = ",\n" . $entry; } $slugHdr = []; @@ -177,9 +184,9 @@ public static function parse(string $inputPath, string $outputPath): void $first = true; for ($slugId = 0; $slugId < $numSlugs; $slugId++) { - $base = $slugId * $numDates; - $body = ''; - $separator = ''; + $base = $slugId * $numDates; + $firstD = -1; + $body = ''; for ($dateId = 0; $dateId < $numDates; $dateId++) { $count = $counts[$base + $dateId]; @@ -188,14 +195,25 @@ public static function parse(string $inputPath, string $outputPath): void continue; } - $body .= $separator . $datePfx[$dateId] . $count; - $separator = ",\n"; + $firstD = $dateId; + $body = $datePfx[$dateId] . $count; + break; } - if ($body === '') { + if ($firstD === -1) { continue; } + for ($dateId = $firstD + 1; $dateId < $numDates; $dateId++) { + $count = $counts[$base + $dateId]; + + if ($count === 0) { + continue; + } + + $body .= $datePfxC[$dateId] . $count; + } + \fwrite($outputHandle, ($first ? '' : ',') . "\n " . $slugHdr[$slugId] . ": {\n" . $body . "\n }"); $first = false; } From 281e5194a6175b4cd1b0935c490c667c606fdbd6 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Mon, 9 Mar 2026 12:26:35 -0300 Subject: [PATCH 11/49] composer optimization, skip first 4 bytes in comma search, shorten date key --- app/Parser.php | 40 ++++++++++++++++++++-------------------- composer.json | 5 +++++ 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index f640763b3f..311242d3c3 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -30,7 +30,7 @@ public static function parse(string $inputPath, string $outputPath): void }; $monthStr = $month < 10 ? "0{$month}" : (string) $month; - $datePrefix = "{$year}-{$monthStr}-"; + $datePrefix = ($year % 10) . "-{$monthStr}-"; for ($day = 1; $day <= $daysInMonth; $day++) { $key = $datePrefix . ($day < 10 ? "0{$day}" : (string) $day); @@ -116,47 +116,47 @@ public static function parse(string $inputPath, string $outputPath): void $safe = $lastNl - 600; while ($pos < $safe) { - $sep = \strpos($chunk, ',', $pos); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $sep = \strpos($chunk, ',', $pos + 4); + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; $pos = $sep + 52; - $sep = \strpos($chunk, ',', $pos); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $sep = \strpos($chunk, ',', $pos + 4); + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; $pos = $sep + 52; - $sep = \strpos($chunk, ',', $pos); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $sep = \strpos($chunk, ',', $pos + 4); + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; $pos = $sep + 52; - $sep = \strpos($chunk, ',', $pos); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $sep = \strpos($chunk, ',', $pos + 4); + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; $pos = $sep + 52; - $sep = \strpos($chunk, ',', $pos); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $sep = \strpos($chunk, ',', $pos + 4); + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; $pos = $sep + 52; - $sep = \strpos($chunk, ',', $pos); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $sep = \strpos($chunk, ',', $pos + 4); + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; $pos = $sep + 52; - $sep = \strpos($chunk, ',', $pos); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $sep = \strpos($chunk, ',', $pos + 4); + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; $pos = $sep + 52; - $sep = \strpos($chunk, ',', $pos); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $sep = \strpos($chunk, ',', $pos + 4); + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; $pos = $sep + 52; } while ($pos < $lastNl) { - $sep = \strpos($chunk, ',', $pos); + $sep = \strpos($chunk, ',', $pos + 4); if ($sep === false || $sep >= $lastNl) { break; } - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; $pos = $sep + 52; } } @@ -170,7 +170,7 @@ public static function parse(string $inputPath, string $outputPath): void $datePfx = []; $datePfxC = []; for ($dateId = 0; $dateId < $numDates; $dateId++) { - $entry = ' "20' . $dateLabels[$dateId] . '": '; + $entry = ' "202' . $dateLabels[$dateId] . '": '; $datePfx[$dateId] = $entry; $datePfxC[$dateId] = ",\n" . $entry; } diff --git a/composer.json b/composer.json index 0a04b79688..c144cb6cb1 100644 --- a/composer.json +++ b/composer.json @@ -12,5 +12,10 @@ "prefer-stable": true, "require-dev": { "phpunit/phpunit": "^12.5" + }, + "config": { + "optimize-autoloader": true, + "classmap-authoritative": true, + "apcu-autoloader": true } } From e1051764d361b613d3257854658549aad1ad3d39 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Mon, 9 Mar 2026 12:35:16 -0300 Subject: [PATCH 12/49] AnthonySchuijlenburg is right, let's try 1MB CHUNK_SIZE --- app/Parser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Parser.php b/app/Parser.php index 311242d3c3..76e12490d3 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -8,7 +8,7 @@ final class Parser { - private const int CHUNK_SIZE = 262_144; + private const int CHUNK_SIZE = 1_048_576; private const int PROBE_SIZE = 2_097_152; private const int WRITE_BUF = 1_048_576; From 18fad9f58f52ce67006f74ad1f88e02504aea66f Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Mon, 9 Mar 2026 20:03:18 -0300 Subject: [PATCH 13/49] composer optimization actually makes no sense --- composer.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/composer.json b/composer.json index c144cb6cb1..0a04b79688 100644 --- a/composer.json +++ b/composer.json @@ -12,10 +12,5 @@ "prefer-stable": true, "require-dev": { "phpunit/phpunit": "^12.5" - }, - "config": { - "optimize-autoloader": true, - "classmap-authoritative": true, - "apcu-autoloader": true } } From 2647f99f5c920d33175d211b50b4c2fedc392cad Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Mon, 9 Mar 2026 20:44:51 -0300 Subject: [PATCH 14/49] drop probe+discovery phase, 512KB chunks, 4MB write buffer --- app/Parser.php | 47 +++++------------------------------------------ 1 file changed, 5 insertions(+), 42 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index 76e12490d3..cf1e895148 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -8,9 +8,8 @@ final class Parser { - private const int CHUNK_SIZE = 1_048_576; - private const int PROBE_SIZE = 2_097_152; - private const int WRITE_BUF = 1_048_576; + private const int CHUNK_SIZE = 524_288; + private const int WRITE_BUF = 4_194_304; public static function parse(string $inputPath, string $outputPath): void { @@ -41,48 +40,13 @@ public static function parse(string $inputPath, string $outputPath): void } } - $numExpected = \count(Visit::SLUGS); - - $fileHandle = \fopen($inputPath, 'rb'); - \stream_set_read_buffer($fileHandle, 0); - $sample = \fread($fileHandle, \min($fileSize, self::PROBE_SIZE)); - \fclose($fileHandle); - $slugBase = []; $slugLabels = []; $numSlugs = 0; - $bound = \strrpos($sample, "\n"); - - for ($pos = 0; $pos < $bound;) { - $newlinePos = \strpos($sample, "\n", $pos + 52); - - if ($newlinePos === false) { - break; - } - - $slug = \substr($sample, $pos + 25, $newlinePos - $pos - 51); - - if (!isset($slugBase[$slug])) { - $slugBase[$slug] = $numSlugs * $numDates; - $slugLabels[$numSlugs] = $slug; - $numSlugs++; - - if ($numSlugs === $numExpected) { - break; - } - } - - $pos = $newlinePos + 1; - } - - unset($sample); - foreach (Visit::SLUGS as $slug) { - if (!isset($slugBase[$slug])) { - $slugBase[$slug] = $numSlugs * $numDates; - $slugLabels[$numSlugs] = $slug; - $numSlugs++; - } + $slugBase[$slug] = $numSlugs * $numDates; + $slugLabels[$numSlugs] = $slug; + $numSlugs++; } $counts = \array_fill(0, $numSlugs * $numDates, 0); @@ -164,7 +128,6 @@ public static function parse(string $inputPath, string $outputPath): void \fclose($fileHandle); $outputHandle = \fopen($outputPath, 'wb'); - \stream_set_write_buffer($outputHandle, self::WRITE_BUF); $datePfx = []; From 03161175a174e20c16c58c59ac54889d53ad6d8e Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Mon, 9 Mar 2026 21:54:47 -0300 Subject: [PATCH 15/49] microptimizations --- app/Parser.php | 36 +++++++++++------------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index cf1e895148..a9587d1a4c 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -8,9 +8,6 @@ final class Parser { - private const int CHUNK_SIZE = 524_288; - private const int WRITE_BUF = 4_194_304; - public static function parse(string $inputPath, string $outputPath): void { \gc_disable(); @@ -28,11 +25,11 @@ public static function parse(string $inputPath, string $outputPath): void default => 31, }; - $monthStr = $month < 10 ? "0{$month}" : (string) $month; - $datePrefix = ($year % 10) . "-{$monthStr}-"; + $monthStr = $month < 10 ? '0' . $month : (string) $month; + $datePrefix = ($year % 10) . '-' . $monthStr . '-'; for ($day = 1; $day <= $daysInMonth; $day++) { - $key = $datePrefix . ($day < 10 ? "0{$day}" : (string) $day); + $key = $datePrefix . ($day < 10 ? '0' . $day : (string) $day); $dateIds[$key] = $numDates; $dateLabels[$numDates] = $key; $numDates++; @@ -55,23 +52,12 @@ public static function parse(string $inputPath, string $outputPath): void $remaining = $fileSize; while ($remaining > 0) { - $chunk = \fread($fileHandle, $remaining > self::CHUNK_SIZE ? self::CHUNK_SIZE : $remaining); + $chunk = \fread($fileHandle, $remaining > 524_288 ? 524_288 : $remaining); $chunkLength = \strlen($chunk); + $remaining -= $chunkLength; + $lastNl = \strrpos($chunk, "\n"); - if ($chunkLength === 0) { - break; - } - - $remaining -= $chunkLength; - - $lastNl = \strrpos($chunk, "\n"); - - if ($lastNl === false) { - break; - } - - $over = $chunkLength - $lastNl - 1; - if ($over > 0) { + if ($over = $chunkLength - $lastNl - 1) { \fseek($fileHandle, -$over, \SEEK_CUR); $remaining += $over; } @@ -128,7 +114,7 @@ public static function parse(string $inputPath, string $outputPath): void \fclose($fileHandle); $outputHandle = \fopen($outputPath, 'wb'); - \stream_set_write_buffer($outputHandle, self::WRITE_BUF); + \stream_set_write_buffer($outputHandle, 4_194_304); $datePfx = []; $datePfxC = []; @@ -138,9 +124,9 @@ public static function parse(string $inputPath, string $outputPath): void $datePfxC[$dateId] = ",\n" . $entry; } - $slugHdr = []; + $slugOpen = []; for ($slugId = 0; $slugId < $numSlugs; $slugId++) { - $slugHdr[$slugId] = '"\/blog\/' . \str_replace('/', '\/', $slugLabels[$slugId]) . '"'; + $slugOpen[$slugId] = "\n \"\/blog\/" . \str_replace('/', '\/', $slugLabels[$slugId]) . "\": {\n"; } \fwrite($outputHandle, '{'); @@ -177,7 +163,7 @@ public static function parse(string $inputPath, string $outputPath): void $body .= $datePfxC[$dateId] . $count; } - \fwrite($outputHandle, ($first ? '' : ',') . "\n " . $slugHdr[$slugId] . ": {\n" . $body . "\n }"); + \fwrite($outputHandle, ($first ? '' : ',') . $slugOpen[$slugId] . $body . "\n }"); $first = false; } From c9a12a3c62fd5f5ae1427fb418632f3dae085d28 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Mon, 9 Mar 2026 22:52:09 -0300 Subject: [PATCH 16/49] 10x unroll --- app/Parser.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/Parser.php b/app/Parser.php index a9587d1a4c..eb9b6dbcce 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -63,7 +63,7 @@ public static function parse(string $inputPath, string $outputPath): void } $pos = 25; - $safe = $lastNl - 600; + $safe = $lastNl - 1000; while ($pos < $safe) { $sep = \strpos($chunk, ',', $pos + 4); @@ -97,6 +97,14 @@ public static function parse(string $inputPath, string $outputPath): void $sep = \strpos($chunk, ',', $pos + 4); $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; $pos = $sep + 52; + + $sep = \strpos($chunk, ',', $pos + 4); + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; + $pos = $sep + 52; + + $sep = \strpos($chunk, ',', $pos + 4); + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; + $pos = $sep + 52; } while ($pos < $lastNl) { From 1b95730c0bb94aac48dbfdb856accf709afa4f5b Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Tue, 10 Mar 2026 00:06:38 -0300 Subject: [PATCH 17/49] collapse two-pass date loop into on, less typey, single quotes --- app/Parser.php | 31 ++++++------------------------- tempest | 16 ++++++++-------- 2 files changed, 14 insertions(+), 33 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index eb9b6dbcce..7973ae4395 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -1,14 +1,12 @@ run(); From 4fe5802eeae66202020e70209278f4fbadc01ccd Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Tue, 10 Mar 2026 02:09:57 -0300 Subject: [PATCH 18/49] I think it wasn't passing validation? --- app/Commands/Visit.php | 545 ++++++++++++++++++++--------------------- app/Parser.php | 305 ++++++++++++++++++++++- tempest | 1 - 3 files changed, 571 insertions(+), 280 deletions(-) diff --git a/app/Commands/Visit.php b/app/Commands/Visit.php index 12372328b3..5b6dc1274e 100644 --- a/app/Commands/Visit.php +++ b/app/Commands/Visit.php @@ -6,283 +6,278 @@ final class Visit { public function __construct(public string $uri) {} - public const array SLUGS = [ - 'which-editor-to-choose', - 'tackling_responsive_images-part_1', - 'tackling_responsive_images-part_2', - 'image_optimizers', - 'static_sites_vs_caching', - 'stitcher-alpha-4', - 'simplest-plugin-support', - 'stitcher-alpha-5', - 'php-generics-and-why-we-need-them', - 'stitcher-beta-1', - 'array-objects-with-fixed-types', - 'performance-101-building-the-better-web', - 'process-forks', - 'object-oriented-generators', - 'responsive-images-as-css-background', - 'a-programmers-cognitive-load', - 'mastering-key-bindings', - 'stitcher-beta-2', - 'phpstorm-performance', - 'optimised-uuids-in-mysql', - 'asynchronous-php', - 'mysql-import-json-binary-character-set', - 'where-a-curly-bracket-belongs', - 'mysql-query-logging', - 'mysql-show-foreign-key-errors', - 'responsive-images-done-right', - 'phpstorm-tips-for-power-users', - 'what-php-can-be', - 'phpstorm-performance-issues-on-osx', - 'dependency-injection-for-beginners', - 'liskov-and-type-safety', - 'acquisition-by-giants', - 'visual-perception-of-code', - 'service-locator-anti-pattern', - 'the-web-in-2045', - 'eloquent-mysql-views', - 'laravel-view-models', - 'laravel-view-models-vs-view-composers', - 'organise-by-domain', - 'array-merge-vs + ', - 'share-a-blog-assertchris-io', - 'phpstorm-performance-october-2018', - 'structuring-unstructured-data', - 'share-a-blog-codingwriter-com', - 'new-in-php-73', - 'share-a-blog-betterwebtype-com', - 'have-you-thought-about-casing', - 'comparing-dates', - 'share-a-blog-sebastiandedeyne-com', - 'analytics-for-developers', - 'announcing-aggregate', - 'php-jit', - 'craftsmen-know-their-tools', - 'laravel-queueable-actions', - 'php-73-upgrade-mac', - 'array-destructuring-with-list-in-php', - 'unsafe-sql-functions-in-laravel', - 'starting-a-newsletter', - 'short-closures-in-php', - 'solid-interfaces-and-final-rant-with-brent', - 'php-in-2019', - 'starting-a-podcast', - 'a-project-at-spatie', - 'what-are-objects-anyway-rant-with-brent', - 'tests-and-types', - 'typed-properties-in-php-74', - 'preloading-in-php-74', - 'things-dependency-injection-is-not-about', - 'a-letter-to-the-php-team', - 'a-letter-to-the-php-team-reply-to-joe', - 'guest-posts', - 'can-i-translate-your-blog', - 'laravel-has-many-through', - 'laravel-custom-relation-classes', - 'new-in-php-74', - 'php-74-upgrade-mac', - 'php-preload-benchmarks', - 'php-in-2020', - 'enums-without-enums', - 'bitwise-booleans-in-php', - 'event-driven-php', - 'minor-versions-breaking-changes', - 'combining-event-sourcing-and-stateful-systems', - 'array-chunk-in-php', - 'php-8-in-8-code-blocks', - 'builders-and-architects-two-types-of-programmers', - 'the-ikea-effect', - 'php-74-in-7-code-blocks', - 'improvements-on-laravel-nova', - 'type-system-in-php-survey', - 'merging-multidimensional-arrays-in-php', - 'what-is-array-plus-in-php', - 'type-system-in-php-survey-results', - 'constructor-promotion-in-php-8', - 'abstract-resources-in-laravel-nova', - 'braille-and-the-history-of-software', - 'jit-in-real-life-web-applications', - 'php-8-match-or-switch', - 'why-we-need-named-params-in-php', - 'shorthand-comparisons-in-php', - 'php-8-before-and-after', - 'php-8-named-arguments', - 'my-journey-into-event-sourcing', - 'differences', - 'annotations', - 'dont-get-stuck', - 'attributes-in-php-8', - 'the-case-for-transpiled-generics', - 'phpstorm-scopes', - 'why-light-themes-are-better-according-to-science', - 'what-a-good-pr-looks-like', - 'front-line-php', - 'php-8-jit-setup', - 'php-8-nullsafe-operator', - 'new-in-php-8', - 'php-8-upgrade-mac', - 'when-i-lost-a-few-hundred-leads', - 'websites-like-star-wars', - 'php-reimagined', - 'a-storm-in-a-glass-of-water', - 'php-enums-before-php-81', - 'php-enums', - 'dont-write-your-own-framework', - 'honesty', - 'thoughts-on-event-sourcing', - 'what-event-sourcing-is-not-about', - 'fibers-with-a-grain-of-salt', - 'php-in-2021', - 'parallel-php', - 'why-we-need-multi-line-short-closures-in-php', - 'a-new-major-version-of-laravel-event-sourcing', - 'what-about-config-builders', - 'opinion-driven-design', - 'php-version-stats-july-2021', - 'what-about-request-classes', - 'cloning-readonly-properties-in-php-81', - 'an-event-driven-mindset', - 'php-81-before-and-after', - 'optimistic-or-realistic-estimates', - 'we-dont-need-runtime-type-checks', - 'the-road-to-php', - 'why-do-i-write', - 'rational-thinking', - 'named-arguments-and-variadic-functions', - 're-on-using-psr-abstractions', - 'my-ikea-clock', - 'php-81-readonly-properties', - 'birth-and-death-of-a-framework', - 'php-81-new-in-initializers', - 'route-attributes', - 'generics-in-php-video', - 'php-81-in-8-code-blocks', - 'new-in-php-81', - 'php-81-performance-in-real-life', - 'php-81-upgrade-mac', - 'how-to-be-right-on-the-internet', - 'php-version-stats-january-2022', - 'php-in-2022', - 'how-i-plan', - 'twitter-home-made-me-miserable', - 'its-your-fault', - 'dealing-with-dependencies', - 'php-in-2021-video', - 'generics-in-php-1', - 'generics-in-php-2', - 'generics-in-php-3', - 'generics-in-php-4', - 'goodbye', - 'strategies', - 'dealing-with-deprecations', - 'attribute-usage-in-top-php-packages', - 'php-enum-style-guide', - 'clean-and-minimalistic-phpstorm', - 'stitcher-turns-5', - 'php-version-stats-july-2022', - 'evolution-of-a-php-object', - 'uncertainty-doubt-and-static-analysis', - 'road-to-php-82', - 'php-performance-across-versions', - 'light-colour-schemes-are-better', - 'deprecated-dynamic-properties-in-php-82', - 'php-reimagined-part-2', - 'thoughts-on-asymmetric-visibility', - 'uses', - 'php-82-in-8-code-blocks', - 'readonly-classes-in-php-82', - 'deprecating-spatie-dto', - 'php-82-upgrade-mac', - 'php-annotated', - 'you-cannot-find-me-on-mastodon', - 'new-in-php-82', - 'all-i-want-for-christmas', - 'upgrading-to-php-82', - 'php-version-stats-january-2023', - 'php-in-2023', - 'tabs-are-better', - 'sponsors', - 'why-curly-brackets-go-on-new-lines', - 'my-10-favourite-php-functions', - 'acronyms', - 'code-folding', - 'light-colour-schemes', - 'slashdash', - 'thank-you-kinsta', - 'cloning-readonly-properties-in-php-83', - 'limited-by-committee', - 'things-considered-harmful', - 'procedurally-generated-game-in-php', - 'dont-be-clever', - 'override-in-php-83', - 'php-version-stats-july-2023', - 'is-a-or-acts-as', - 'rfc-vote', - 'new-in-php-83', - 'i-dont-know', - 'passion-projects', - 'php-version-stats-january-2024', - 'the-framework-that-gets-out-of-your-way', - 'a-syntax-highlighter-that-doesnt-suck', - 'building-a-custom-language-in-tempest-highlight', - 'testing-patterns', - 'php-in-2024', - 'tagged-singletons', - 'twitter-exit', - 'a-vocal-minority', - 'php-version-stats-july-2024', - 'you-should', - 'new-with-parentheses-php-84', - 'html-5-in-php-84', - 'array-find-in-php-84', - 'its-all-just-text', - 'improved-lazy-loading', - 'i-dont-code-the-way-i-used-to', - 'php-84-at-least', - 'extends-vs-implements', - 'a-simple-approach-to-static-generation', - 'building-a-framework', - 'tagging-tempest-livestream', - 'things-i-learned-writing-a-fiction-novel', - 'unfair-advantage', - 'new-in-php-84', - 'php-version-stats-january-2025', - 'theoretical-engineers', - 'static-websites-with-tempest', - 'request-objects-in-tempest', - 'php-verse-2025', - 'tempest-discovery-explained', - 'php-version-stats-june-2025', - 'pipe-operator-in-php-85', - 'a-year-of-property-hooks', - 'readonly-or-private-set', - 'things-i-wish-i-knew', - 'impact-charts', - 'whats-your-motivator', - 'vendor-locked', - 'reducing-code-motion', - 'sponsoring-open-source', - 'my-wishlist-for-php-in-2026', - 'game-changing-editions', - 'new-in-php-85', - 'flooded-rss', - 'php-2026', - 'open-source-strategies', - 'not-optional', - 'processing-11-million-rows', - 'ai-induced-skepticism', - 'php-86-partial-function-application', - '11-million-rows-in-seconds', - ]; - /** @return self[] */ public static function all(): array { - return \array_map( - static fn(string $slug) => new self('https://stitcher.io/blog/' . $slug), - self::SLUGS, - ); + return [ + new self('https://stitcher.io/blog/which-editor-to-choose'), + new self('https://stitcher.io/blog/tackling_responsive_images-part_1'), + new self('https://stitcher.io/blog/tackling_responsive_images-part_2'), + new self('https://stitcher.io/blog/image_optimizers'), + new self('https://stitcher.io/blog/static_sites_vs_caching'), + new self('https://stitcher.io/blog/stitcher-alpha-4'), + new self('https://stitcher.io/blog/simplest-plugin-support'), + new self('https://stitcher.io/blog/stitcher-alpha-5'), + new self('https://stitcher.io/blog/php-generics-and-why-we-need-them'), + new self('https://stitcher.io/blog/stitcher-beta-1'), + new self('https://stitcher.io/blog/array-objects-with-fixed-types'), + new self('https://stitcher.io/blog/performance-101-building-the-better-web'), + new self('https://stitcher.io/blog/process-forks'), + new self('https://stitcher.io/blog/object-oriented-generators'), + new self('https://stitcher.io/blog/responsive-images-as-css-background'), + new self('https://stitcher.io/blog/a-programmers-cognitive-load'), + new self('https://stitcher.io/blog/mastering-key-bindings'), + new self('https://stitcher.io/blog/stitcher-beta-2'), + new self('https://stitcher.io/blog/phpstorm-performance'), + new self('https://stitcher.io/blog/optimised-uuids-in-mysql'), + new self('https://stitcher.io/blog/asynchronous-php'), + new self('https://stitcher.io/blog/mysql-import-json-binary-character-set'), + new self('https://stitcher.io/blog/where-a-curly-bracket-belongs'), + new self('https://stitcher.io/blog/mysql-query-logging'), + new self('https://stitcher.io/blog/mysql-show-foreign-key-errors'), + new self('https://stitcher.io/blog/responsive-images-done-right'), + new self('https://stitcher.io/blog/phpstorm-tips-for-power-users'), + new self('https://stitcher.io/blog/what-php-can-be'), + new self('https://stitcher.io/blog/phpstorm-performance-issues-on-osx'), + new self('https://stitcher.io/blog/dependency-injection-for-beginners'), + new self('https://stitcher.io/blog/liskov-and-type-safety'), + new self('https://stitcher.io/blog/acquisition-by-giants'), + new self('https://stitcher.io/blog/visual-perception-of-code'), + new self('https://stitcher.io/blog/service-locator-anti-pattern'), + new self('https://stitcher.io/blog/the-web-in-2045'), + new self('https://stitcher.io/blog/eloquent-mysql-views'), + new self('https://stitcher.io/blog/laravel-view-models'), + new self('https://stitcher.io/blog/laravel-view-models-vs-view-composers'), + new self('https://stitcher.io/blog/organise-by-domain'), + new self('https://stitcher.io/blog/array-merge-vs + '), + new self('https://stitcher.io/blog/share-a-blog-assertchris-io'), + new self('https://stitcher.io/blog/phpstorm-performance-october-2018'), + new self('https://stitcher.io/blog/structuring-unstructured-data'), + new self('https://stitcher.io/blog/share-a-blog-codingwriter-com'), + new self('https://stitcher.io/blog/new-in-php-73'), + new self('https://stitcher.io/blog/share-a-blog-betterwebtype-com'), + new self('https://stitcher.io/blog/have-you-thought-about-casing'), + new self('https://stitcher.io/blog/comparing-dates'), + new self('https://stitcher.io/blog/share-a-blog-sebastiandedeyne-com'), + new self('https://stitcher.io/blog/analytics-for-developers'), + new self('https://stitcher.io/blog/announcing-aggregate'), + new self('https://stitcher.io/blog/php-jit'), + new self('https://stitcher.io/blog/craftsmen-know-their-tools'), + new self('https://stitcher.io/blog/laravel-queueable-actions'), + new self('https://stitcher.io/blog/php-73-upgrade-mac'), + new self('https://stitcher.io/blog/array-destructuring-with-list-in-php'), + new self('https://stitcher.io/blog/unsafe-sql-functions-in-laravel'), + new self('https://stitcher.io/blog/starting-a-newsletter'), + new self('https://stitcher.io/blog/short-closures-in-php'), + new self('https://stitcher.io/blog/solid-interfaces-and-final-rant-with-brent'), + new self('https://stitcher.io/blog/php-in-2019'), + new self('https://stitcher.io/blog/starting-a-podcast'), + new self('https://stitcher.io/blog/a-project-at-spatie'), + new self('https://stitcher.io/blog/what-are-objects-anyway-rant-with-brent'), + new self('https://stitcher.io/blog/tests-and-types'), + new self('https://stitcher.io/blog/typed-properties-in-php-74'), + new self('https://stitcher.io/blog/preloading-in-php-74'), + new self('https://stitcher.io/blog/things-dependency-injection-is-not-about'), + new self('https://stitcher.io/blog/a-letter-to-the-php-team'), + new self('https://stitcher.io/blog/a-letter-to-the-php-team-reply-to-joe'), + new self('https://stitcher.io/blog/guest-posts'), + new self('https://stitcher.io/blog/can-i-translate-your-blog'), + new self('https://stitcher.io/blog/laravel-has-many-through'), + new self('https://stitcher.io/blog/laravel-custom-relation-classes'), + new self('https://stitcher.io/blog/new-in-php-74'), + new self('https://stitcher.io/blog/php-74-upgrade-mac'), + new self('https://stitcher.io/blog/php-preload-benchmarks'), + new self('https://stitcher.io/blog/php-in-2020'), + new self('https://stitcher.io/blog/enums-without-enums'), + new self('https://stitcher.io/blog/bitwise-booleans-in-php'), + new self('https://stitcher.io/blog/event-driven-php'), + new self('https://stitcher.io/blog/minor-versions-breaking-changes'), + new self('https://stitcher.io/blog/combining-event-sourcing-and-stateful-systems'), + new self('https://stitcher.io/blog/array-chunk-in-php'), + new self('https://stitcher.io/blog/php-8-in-8-code-blocks'), + new self('https://stitcher.io/blog/builders-and-architects-two-types-of-programmers'), + new self('https://stitcher.io/blog/the-ikea-effect'), + new self('https://stitcher.io/blog/php-74-in-7-code-blocks'), + new self('https://stitcher.io/blog/improvements-on-laravel-nova'), + new self('https://stitcher.io/blog/type-system-in-php-survey'), + new self('https://stitcher.io/blog/merging-multidimensional-arrays-in-php'), + new self('https://stitcher.io/blog/what-is-array-plus-in-php'), + new self('https://stitcher.io/blog/type-system-in-php-survey-results'), + new self('https://stitcher.io/blog/constructor-promotion-in-php-8'), + new self('https://stitcher.io/blog/abstract-resources-in-laravel-nova'), + new self('https://stitcher.io/blog/braille-and-the-history-of-software'), + new self('https://stitcher.io/blog/jit-in-real-life-web-applications'), + new self('https://stitcher.io/blog/php-8-match-or-switch'), + new self('https://stitcher.io/blog/why-we-need-named-params-in-php'), + new self('https://stitcher.io/blog/shorthand-comparisons-in-php'), + new self('https://stitcher.io/blog/php-8-before-and-after'), + new self('https://stitcher.io/blog/php-8-named-arguments'), + new self('https://stitcher.io/blog/my-journey-into-event-sourcing'), + new self('https://stitcher.io/blog/differences'), + new self('https://stitcher.io/blog/annotations'), + new self('https://stitcher.io/blog/dont-get-stuck'), + new self('https://stitcher.io/blog/attributes-in-php-8'), + new self('https://stitcher.io/blog/the-case-for-transpiled-generics'), + new self('https://stitcher.io/blog/phpstorm-scopes'), + new self('https://stitcher.io/blog/why-light-themes-are-better-according-to-science'), + new self('https://stitcher.io/blog/what-a-good-pr-looks-like'), + new self('https://stitcher.io/blog/front-line-php'), + new self('https://stitcher.io/blog/php-8-jit-setup'), + new self('https://stitcher.io/blog/php-8-nullsafe-operator'), + new self('https://stitcher.io/blog/new-in-php-8'), + new self('https://stitcher.io/blog/php-8-upgrade-mac'), + new self('https://stitcher.io/blog/when-i-lost-a-few-hundred-leads'), + new self('https://stitcher.io/blog/websites-like-star-wars'), + new self('https://stitcher.io/blog/php-reimagined'), + new self('https://stitcher.io/blog/a-storm-in-a-glass-of-water'), + new self('https://stitcher.io/blog/php-enums-before-php-81'), + new self('https://stitcher.io/blog/php-enums'), + new self('https://stitcher.io/blog/dont-write-your-own-framework'), + new self('https://stitcher.io/blog/honesty'), + new self('https://stitcher.io/blog/thoughts-on-event-sourcing'), + new self('https://stitcher.io/blog/what-event-sourcing-is-not-about'), + new self('https://stitcher.io/blog/fibers-with-a-grain-of-salt'), + new self('https://stitcher.io/blog/php-in-2021'), + new self('https://stitcher.io/blog/parallel-php'), + new self('https://stitcher.io/blog/why-we-need-multi-line-short-closures-in-php'), + new self('https://stitcher.io/blog/a-new-major-version-of-laravel-event-sourcing'), + new self('https://stitcher.io/blog/what-about-config-builders'), + new self('https://stitcher.io/blog/opinion-driven-design'), + new self('https://stitcher.io/blog/php-version-stats-july-2021'), + new self('https://stitcher.io/blog/what-about-request-classes'), + new self('https://stitcher.io/blog/cloning-readonly-properties-in-php-81'), + new self('https://stitcher.io/blog/an-event-driven-mindset'), + new self('https://stitcher.io/blog/php-81-before-and-after'), + new self('https://stitcher.io/blog/optimistic-or-realistic-estimates'), + new self('https://stitcher.io/blog/we-dont-need-runtime-type-checks'), + new self('https://stitcher.io/blog/the-road-to-php'), + new self('https://stitcher.io/blog/why-do-i-write'), + new self('https://stitcher.io/blog/rational-thinking'), + new self('https://stitcher.io/blog/named-arguments-and-variadic-functions'), + new self('https://stitcher.io/blog/re-on-using-psr-abstractions'), + new self('https://stitcher.io/blog/my-ikea-clock'), + new self('https://stitcher.io/blog/php-81-readonly-properties'), + new self('https://stitcher.io/blog/birth-and-death-of-a-framework'), + new self('https://stitcher.io/blog/php-81-new-in-initializers'), + new self('https://stitcher.io/blog/route-attributes'), + new self('https://stitcher.io/blog/generics-in-php-video'), + new self('https://stitcher.io/blog/php-81-in-8-code-blocks'), + new self('https://stitcher.io/blog/new-in-php-81'), + new self('https://stitcher.io/blog/php-81-performance-in-real-life'), + new self('https://stitcher.io/blog/php-81-upgrade-mac'), + new self('https://stitcher.io/blog/how-to-be-right-on-the-internet'), + new self('https://stitcher.io/blog/php-version-stats-january-2022'), + new self('https://stitcher.io/blog/php-in-2022'), + new self('https://stitcher.io/blog/how-i-plan'), + new self('https://stitcher.io/blog/twitter-home-made-me-miserable'), + new self('https://stitcher.io/blog/its-your-fault'), + new self('https://stitcher.io/blog/dealing-with-dependencies'), + new self('https://stitcher.io/blog/php-in-2021-video'), + new self('https://stitcher.io/blog/generics-in-php-1'), + new self('https://stitcher.io/blog/generics-in-php-2'), + new self('https://stitcher.io/blog/generics-in-php-3'), + new self('https://stitcher.io/blog/generics-in-php-4'), + new self('https://stitcher.io/blog/goodbye'), + new self('https://stitcher.io/blog/strategies'), + new self('https://stitcher.io/blog/dealing-with-deprecations'), + new self('https://stitcher.io/blog/attribute-usage-in-top-php-packages'), + new self('https://stitcher.io/blog/php-enum-style-guide'), + new self('https://stitcher.io/blog/clean-and-minimalistic-phpstorm'), + new self('https://stitcher.io/blog/stitcher-turns-5'), + new self('https://stitcher.io/blog/php-version-stats-july-2022'), + new self('https://stitcher.io/blog/evolution-of-a-php-object'), + new self('https://stitcher.io/blog/uncertainty-doubt-and-static-analysis'), + new self('https://stitcher.io/blog/road-to-php-82'), + new self('https://stitcher.io/blog/php-performance-across-versions'), + new self('https://stitcher.io/blog/light-colour-schemes-are-better'), + new self('https://stitcher.io/blog/deprecated-dynamic-properties-in-php-82'), + new self('https://stitcher.io/blog/php-reimagined-part-2'), + new self('https://stitcher.io/blog/thoughts-on-asymmetric-visibility'), + new self('https://stitcher.io/blog/uses'), + new self('https://stitcher.io/blog/php-82-in-8-code-blocks'), + new self('https://stitcher.io/blog/readonly-classes-in-php-82'), + new self('https://stitcher.io/blog/deprecating-spatie-dto'), + new self('https://stitcher.io/blog/php-82-upgrade-mac'), + new self('https://stitcher.io/blog/php-annotated'), + new self('https://stitcher.io/blog/you-cannot-find-me-on-mastodon'), + new self('https://stitcher.io/blog/new-in-php-82'), + new self('https://stitcher.io/blog/all-i-want-for-christmas'), + new self('https://stitcher.io/blog/upgrading-to-php-82'), + new self('https://stitcher.io/blog/php-version-stats-january-2023'), + new self('https://stitcher.io/blog/php-in-2023'), + new self('https://stitcher.io/blog/tabs-are-better'), + new self('https://stitcher.io/blog/sponsors'), + new self('https://stitcher.io/blog/why-curly-brackets-go-on-new-lines'), + new self('https://stitcher.io/blog/my-10-favourite-php-functions'), + new self('https://stitcher.io/blog/acronyms'), + new self('https://stitcher.io/blog/code-folding'), + new self('https://stitcher.io/blog/light-colour-schemes'), + new self('https://stitcher.io/blog/slashdash'), + new self('https://stitcher.io/blog/thank-you-kinsta'), + new self('https://stitcher.io/blog/cloning-readonly-properties-in-php-83'), + new self('https://stitcher.io/blog/limited-by-committee'), + new self('https://stitcher.io/blog/things-considered-harmful'), + new self('https://stitcher.io/blog/procedurally-generated-game-in-php'), + new self('https://stitcher.io/blog/dont-be-clever'), + new self('https://stitcher.io/blog/override-in-php-83'), + new self('https://stitcher.io/blog/php-version-stats-july-2023'), + new self('https://stitcher.io/blog/is-a-or-acts-as'), + new self('https://stitcher.io/blog/rfc-vote'), + new self('https://stitcher.io/blog/new-in-php-83'), + new self('https://stitcher.io/blog/i-dont-know'), + new self('https://stitcher.io/blog/passion-projects'), + new self('https://stitcher.io/blog/php-version-stats-january-2024'), + new self('https://stitcher.io/blog/the-framework-that-gets-out-of-your-way'), + new self('https://stitcher.io/blog/a-syntax-highlighter-that-doesnt-suck'), + new self('https://stitcher.io/blog/building-a-custom-language-in-tempest-highlight'), + new self('https://stitcher.io/blog/testing-patterns'), + new self('https://stitcher.io/blog/php-in-2024'), + new self('https://stitcher.io/blog/tagged-singletons'), + new self('https://stitcher.io/blog/twitter-exit'), + new self('https://stitcher.io/blog/a-vocal-minority'), + new self('https://stitcher.io/blog/php-version-stats-july-2024'), + new self('https://stitcher.io/blog/you-should'), + new self('https://stitcher.io/blog/new-with-parentheses-php-84'), + new self('https://stitcher.io/blog/html-5-in-php-84'), + new self('https://stitcher.io/blog/array-find-in-php-84'), + new self('https://stitcher.io/blog/its-all-just-text'), + new self('https://stitcher.io/blog/improved-lazy-loading'), + new self('https://stitcher.io/blog/i-dont-code-the-way-i-used-to'), + new self('https://stitcher.io/blog/php-84-at-least'), + new self('https://stitcher.io/blog/extends-vs-implements'), + new self('https://stitcher.io/blog/a-simple-approach-to-static-generation'), + new self('https://stitcher.io/blog/building-a-framework'), + new self('https://stitcher.io/blog/tagging-tempest-livestream'), + new self('https://stitcher.io/blog/things-i-learned-writing-a-fiction-novel'), + new self('https://stitcher.io/blog/unfair-advantage'), + new self('https://stitcher.io/blog/new-in-php-84'), + new self('https://stitcher.io/blog/php-version-stats-january-2025'), + new self('https://stitcher.io/blog/theoretical-engineers'), + new self('https://stitcher.io/blog/static-websites-with-tempest'), + new self('https://stitcher.io/blog/request-objects-in-tempest'), + new self('https://stitcher.io/blog/php-verse-2025'), + new self('https://stitcher.io/blog/tempest-discovery-explained'), + new self('https://stitcher.io/blog/php-version-stats-june-2025'), + new self('https://stitcher.io/blog/pipe-operator-in-php-85'), + new self('https://stitcher.io/blog/a-year-of-property-hooks'), + new self('https://stitcher.io/blog/readonly-or-private-set'), + new self('https://stitcher.io/blog/things-i-wish-i-knew'), + new self('https://stitcher.io/blog/impact-charts'), + new self('https://stitcher.io/blog/whats-your-motivator'), + new self('https://stitcher.io/blog/vendor-locked'), + new self('https://stitcher.io/blog/reducing-code-motion'), + new self('https://stitcher.io/blog/sponsoring-open-source'), + new self('https://stitcher.io/blog/my-wishlist-for-php-in-2026'), + new self('https://stitcher.io/blog/game-changing-editions'), + new self('https://stitcher.io/blog/new-in-php-85'), + new self('https://stitcher.io/blog/flooded-rss'), + new self('https://stitcher.io/blog/php-2026'), + new self('https://stitcher.io/blog/open-source-strategies'), + new self('https://stitcher.io/blog/not-optional'), + new self('https://stitcher.io/blog/processing-11-million-rows'), + new self('https://stitcher.io/blog/ai-induced-skepticism'), + new self('https://stitcher.io/blog/php-86-partial-function-application'), + new self('https://stitcher.io/blog/11-million-rows-in-seconds'), + ]; } } diff --git a/app/Parser.php b/app/Parser.php index 7973ae4395..89d13de10a 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -2,8 +2,6 @@ namespace App; -use App\Commands\Visit; - final class Parser { public static function parse($inputPath, $outputPath): void @@ -36,10 +34,283 @@ public static function parse($inputPath, $outputPath): void } $slugBase = []; + $slugIds = []; $slugLabels = []; $numSlugs = 0; - foreach (Visit::SLUGS as $slug) { + foreach ( + [ + 'which-editor-to-choose', + 'tackling_responsive_images-part_1', + 'tackling_responsive_images-part_2', + 'image_optimizers', + 'static_sites_vs_caching', + 'stitcher-alpha-4', + 'simplest-plugin-support', + 'stitcher-alpha-5', + 'php-generics-and-why-we-need-them', + 'stitcher-beta-1', + 'array-objects-with-fixed-types', + 'performance-101-building-the-better-web', + 'process-forks', + 'object-oriented-generators', + 'responsive-images-as-css-background', + 'a-programmers-cognitive-load', + 'mastering-key-bindings', + 'stitcher-beta-2', + 'phpstorm-performance', + 'optimised-uuids-in-mysql', + 'asynchronous-php', + 'mysql-import-json-binary-character-set', + 'where-a-curly-bracket-belongs', + 'mysql-query-logging', + 'mysql-show-foreign-key-errors', + 'responsive-images-done-right', + 'phpstorm-tips-for-power-users', + 'what-php-can-be', + 'phpstorm-performance-issues-on-osx', + 'dependency-injection-for-beginners', + 'liskov-and-type-safety', + 'acquisition-by-giants', + 'visual-perception-of-code', + 'service-locator-anti-pattern', + 'the-web-in-2045', + 'eloquent-mysql-views', + 'laravel-view-models', + 'laravel-view-models-vs-view-composers', + 'organise-by-domain', + 'array-merge-vs + ', + 'share-a-blog-assertchris-io', + 'phpstorm-performance-october-2018', + 'structuring-unstructured-data', + 'share-a-blog-codingwriter-com', + 'new-in-php-73', + 'share-a-blog-betterwebtype-com', + 'have-you-thought-about-casing', + 'comparing-dates', + 'share-a-blog-sebastiandedeyne-com', + 'analytics-for-developers', + 'announcing-aggregate', + 'php-jit', + 'craftsmen-know-their-tools', + 'laravel-queueable-actions', + 'php-73-upgrade-mac', + 'array-destructuring-with-list-in-php', + 'unsafe-sql-functions-in-laravel', + 'starting-a-newsletter', + 'short-closures-in-php', + 'solid-interfaces-and-final-rant-with-brent', + 'php-in-2019', + 'starting-a-podcast', + 'a-project-at-spatie', + 'what-are-objects-anyway-rant-with-brent', + 'tests-and-types', + 'typed-properties-in-php-74', + 'preloading-in-php-74', + 'things-dependency-injection-is-not-about', + 'a-letter-to-the-php-team', + 'a-letter-to-the-php-team-reply-to-joe', + 'guest-posts', + 'can-i-translate-your-blog', + 'laravel-has-many-through', + 'laravel-custom-relation-classes', + 'new-in-php-74', + 'php-74-upgrade-mac', + 'php-preload-benchmarks', + 'php-in-2020', + 'enums-without-enums', + 'bitwise-booleans-in-php', + 'event-driven-php', + 'minor-versions-breaking-changes', + 'combining-event-sourcing-and-stateful-systems', + 'array-chunk-in-php', + 'php-8-in-8-code-blocks', + 'builders-and-architects-two-types-of-programmers', + 'the-ikea-effect', + 'php-74-in-7-code-blocks', + 'improvements-on-laravel-nova', + 'type-system-in-php-survey', + 'merging-multidimensional-arrays-in-php', + 'what-is-array-plus-in-php', + 'type-system-in-php-survey-results', + 'constructor-promotion-in-php-8', + 'abstract-resources-in-laravel-nova', + 'braille-and-the-history-of-software', + 'jit-in-real-life-web-applications', + 'php-8-match-or-switch', + 'why-we-need-named-params-in-php', + 'shorthand-comparisons-in-php', + 'php-8-before-and-after', + 'php-8-named-arguments', + 'my-journey-into-event-sourcing', + 'differences', + 'annotations', + 'dont-get-stuck', + 'attributes-in-php-8', + 'the-case-for-transpiled-generics', + 'phpstorm-scopes', + 'why-light-themes-are-better-according-to-science', + 'what-a-good-pr-looks-like', + 'front-line-php', + 'php-8-jit-setup', + 'php-8-nullsafe-operator', + 'new-in-php-8', + 'php-8-upgrade-mac', + 'when-i-lost-a-few-hundred-leads', + 'websites-like-star-wars', + 'php-reimagined', + 'a-storm-in-a-glass-of-water', + 'php-enums-before-php-81', + 'php-enums', + 'dont-write-your-own-framework', + 'honesty', + 'thoughts-on-event-sourcing', + 'what-event-sourcing-is-not-about', + 'fibers-with-a-grain-of-salt', + 'php-in-2021', + 'parallel-php', + 'why-we-need-multi-line-short-closures-in-php', + 'a-new-major-version-of-laravel-event-sourcing', + 'what-about-config-builders', + 'opinion-driven-design', + 'php-version-stats-july-2021', + 'what-about-request-classes', + 'cloning-readonly-properties-in-php-81', + 'an-event-driven-mindset', + 'php-81-before-and-after', + 'optimistic-or-realistic-estimates', + 'we-dont-need-runtime-type-checks', + 'the-road-to-php', + 'why-do-i-write', + 'rational-thinking', + 'named-arguments-and-variadic-functions', + 're-on-using-psr-abstractions', + 'my-ikea-clock', + 'php-81-readonly-properties', + 'birth-and-death-of-a-framework', + 'php-81-new-in-initializers', + 'route-attributes', + 'generics-in-php-video', + 'php-81-in-8-code-blocks', + 'new-in-php-81', + 'php-81-performance-in-real-life', + 'php-81-upgrade-mac', + 'how-to-be-right-on-the-internet', + 'php-version-stats-january-2022', + 'php-in-2022', + 'how-i-plan', + 'twitter-home-made-me-miserable', + 'its-your-fault', + 'dealing-with-dependencies', + 'php-in-2021-video', + 'generics-in-php-1', + 'generics-in-php-2', + 'generics-in-php-3', + 'generics-in-php-4', + 'goodbye', + 'strategies', + 'dealing-with-deprecations', + 'attribute-usage-in-top-php-packages', + 'php-enum-style-guide', + 'clean-and-minimalistic-phpstorm', + 'stitcher-turns-5', + 'php-version-stats-july-2022', + 'evolution-of-a-php-object', + 'uncertainty-doubt-and-static-analysis', + 'road-to-php-82', + 'php-performance-across-versions', + 'light-colour-schemes-are-better', + 'deprecated-dynamic-properties-in-php-82', + 'php-reimagined-part-2', + 'thoughts-on-asymmetric-visibility', + 'uses', + 'php-82-in-8-code-blocks', + 'readonly-classes-in-php-82', + 'deprecating-spatie-dto', + 'php-82-upgrade-mac', + 'php-annotated', + 'you-cannot-find-me-on-mastodon', + 'new-in-php-82', + 'all-i-want-for-christmas', + 'upgrading-to-php-82', + 'php-version-stats-january-2023', + 'php-in-2023', + 'tabs-are-better', + 'sponsors', + 'why-curly-brackets-go-on-new-lines', + 'my-10-favourite-php-functions', + 'acronyms', + 'code-folding', + 'light-colour-schemes', + 'slashdash', + 'thank-you-kinsta', + 'cloning-readonly-properties-in-php-83', + 'limited-by-committee', + 'things-considered-harmful', + 'procedurally-generated-game-in-php', + 'dont-be-clever', + 'override-in-php-83', + 'php-version-stats-july-2023', + 'is-a-or-acts-as', + 'rfc-vote', + 'new-in-php-83', + 'i-dont-know', + 'passion-projects', + 'php-version-stats-january-2024', + 'the-framework-that-gets-out-of-your-way', + 'a-syntax-highlighter-that-doesnt-suck', + 'building-a-custom-language-in-tempest-highlight', + 'testing-patterns', + 'php-in-2024', + 'tagged-singletons', + 'twitter-exit', + 'a-vocal-minority', + 'php-version-stats-july-2024', + 'you-should', + 'new-with-parentheses-php-84', + 'html-5-in-php-84', + 'array-find-in-php-84', + 'its-all-just-text', + 'improved-lazy-loading', + 'i-dont-code-the-way-i-used-to', + 'php-84-at-least', + 'extends-vs-implements', + 'a-simple-approach-to-static-generation', + 'building-a-framework', + 'tagging-tempest-livestream', + 'things-i-learned-writing-a-fiction-novel', + 'unfair-advantage', + 'new-in-php-84', + 'php-version-stats-january-2025', + 'theoretical-engineers', + 'static-websites-with-tempest', + 'request-objects-in-tempest', + 'php-verse-2025', + 'tempest-discovery-explained', + 'php-version-stats-june-2025', + 'pipe-operator-in-php-85', + 'a-year-of-property-hooks', + 'readonly-or-private-set', + 'things-i-wish-i-knew', + 'impact-charts', + 'whats-your-motivator', + 'vendor-locked', + 'reducing-code-motion', + 'sponsoring-open-source', + 'my-wishlist-for-php-in-2026', + 'game-changing-editions', + 'new-in-php-85', + 'flooded-rss', + 'php-2026', + 'open-source-strategies', + 'not-optional', + 'processing-11-million-rows', + 'ai-induced-skepticism', + 'php-86-partial-function-application', + '11-million-rows-in-seconds', + ] as $slug + ) { $slugBase[$slug] = $numSlugs * $numDates; + $slugIds[$slug] = $numSlugs; $slugLabels[$numSlugs] = $slug; $numSlugs++; } @@ -47,6 +318,32 @@ public static function parse($inputPath, $outputPath): void $counts = \array_fill(0, $numSlugs * $numDates, 0); $fileHandle = \fopen($inputPath, 'rb'); \stream_set_read_buffer($fileHandle, 0); + + $probeChunk = \fread($fileHandle, \min($fileSize, 524_288)); + $probeNl = \strrpos($probeChunk, "\n"); + $slugSeen = \array_fill(0, $numSlugs, false); + $slugOrder = []; + if ($probeNl !== false) { + $ppos = 25; + while ($ppos < $probeNl) { + $psep = \strpos($probeChunk, ',', $ppos + 4); + if ($psep === false || $psep > $probeNl) break; + $pslug = \substr($probeChunk, $ppos, $psep - $ppos); + if (isset($slugIds[$pslug])) { + $pid = $slugIds[$pslug]; + if (!$slugSeen[$pid]) { + $slugOrder[] = $pid; + $slugSeen[$pid] = true; + } + } + $ppos = $psep + 52; + } + } + for ($i = 0; $i < $numSlugs; $i++) { + if (!$slugSeen[$i]) $slugOrder[] = $i; + } + \fseek($fileHandle, 0); + $remaining = $fileSize; while ($remaining > 0) { @@ -138,7 +435,7 @@ public static function parse($inputPath, $outputPath): void \fwrite($outputHandle, '{'); $first = true; - for ($slugId = 0; $slugId < $numSlugs; $slugId++) { + foreach ($slugOrder as $slugId) { $base = $slugId * $numDates; $body = ''; diff --git a/tempest b/tempest index 65712ac43d..ee3abb3e94 100755 --- a/tempest +++ b/tempest @@ -4,7 +4,6 @@ $argv = $_SERVER['argv']; if (($argv[1] ?? null) === 'data:parse') { require __DIR__ . '/app/Parser.php'; - require __DIR__ . '/app/Commands/Visit.php'; $inputPath = \substr($argv[2] ?? '', 13); $outputPath = \substr($argv[3] ?? '', 14); From 78515c1e03a33cd0ef8e82baa3ee31d88176d7ea Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Tue, 10 Mar 2026 11:57:50 -0300 Subject: [PATCH 19/49] probe with early bail-out --- app/Parser.php | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index 89d13de10a..1ba6fc8184 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -323,17 +323,20 @@ public static function parse($inputPath, $outputPath): void $probeNl = \strrpos($probeChunk, "\n"); $slugSeen = \array_fill(0, $numSlugs, false); $slugOrder = []; + $probeFound = 0; if ($probeNl !== false) { $ppos = 25; while ($ppos < $probeNl) { $psep = \strpos($probeChunk, ',', $ppos + 4); - if ($psep === false || $psep > $probeNl) break; - $pslug = \substr($probeChunk, $ppos, $psep - $ppos); - if (isset($slugIds[$pslug])) { - $pid = $slugIds[$pslug]; - if (!$slugSeen[$pid]) { - $slugOrder[] = $pid; - $slugSeen[$pid] = true; + if ($psep === false || $psep > $probeNl) { + break; + } + $pid = $slugIds[\substr($probeChunk, $ppos, $psep - $ppos)]; + if (!$slugSeen[$pid]) { + $slugOrder[] = $pid; + $slugSeen[$pid] = true; + if (++$probeFound === $numSlugs) { + break; } } $ppos = $psep + 52; From 4bc8397edc9fdae4f3f06e69fe5e289330dc55c9 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Thu, 12 Mar 2026 17:26:50 -0300 Subject: [PATCH 20/49] I had actually tried 1MB already lol switching back to probe-based slug ordering with inline fallback to see how it goes --- app/Parser.php | 620 +++++++++++++++++++++++++------------------------ 1 file changed, 312 insertions(+), 308 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index 1ba6fc8184..693f2d7914 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -8,6 +8,277 @@ public static function parse($inputPath, $outputPath): void { \gc_disable(); + $slugs = [ + 'which-editor-to-choose', + 'tackling_responsive_images-part_1', + 'tackling_responsive_images-part_2', + 'image_optimizers', + 'static_sites_vs_caching', + 'stitcher-alpha-4', + 'simplest-plugin-support', + 'stitcher-alpha-5', + 'php-generics-and-why-we-need-them', + 'stitcher-beta-1', + 'array-objects-with-fixed-types', + 'performance-101-building-the-better-web', + 'process-forks', + 'object-oriented-generators', + 'responsive-images-as-css-background', + 'a-programmers-cognitive-load', + 'mastering-key-bindings', + 'stitcher-beta-2', + 'phpstorm-performance', + 'optimised-uuids-in-mysql', + 'asynchronous-php', + 'mysql-import-json-binary-character-set', + 'where-a-curly-bracket-belongs', + 'mysql-query-logging', + 'mysql-show-foreign-key-errors', + 'responsive-images-done-right', + 'phpstorm-tips-for-power-users', + 'what-php-can-be', + 'phpstorm-performance-issues-on-osx', + 'dependency-injection-for-beginners', + 'liskov-and-type-safety', + 'acquisition-by-giants', + 'visual-perception-of-code', + 'service-locator-anti-pattern', + 'the-web-in-2045', + 'eloquent-mysql-views', + 'laravel-view-models', + 'laravel-view-models-vs-view-composers', + 'organise-by-domain', + 'array-merge-vs + ', + 'share-a-blog-assertchris-io', + 'phpstorm-performance-october-2018', + 'structuring-unstructured-data', + 'share-a-blog-codingwriter-com', + 'new-in-php-73', + 'share-a-blog-betterwebtype-com', + 'have-you-thought-about-casing', + 'comparing-dates', + 'share-a-blog-sebastiandedeyne-com', + 'analytics-for-developers', + 'announcing-aggregate', + 'php-jit', + 'craftsmen-know-their-tools', + 'laravel-queueable-actions', + 'php-73-upgrade-mac', + 'array-destructuring-with-list-in-php', + 'unsafe-sql-functions-in-laravel', + 'starting-a-newsletter', + 'short-closures-in-php', + 'solid-interfaces-and-final-rant-with-brent', + 'php-in-2019', + 'starting-a-podcast', + 'a-project-at-spatie', + 'what-are-objects-anyway-rant-with-brent', + 'tests-and-types', + 'typed-properties-in-php-74', + 'preloading-in-php-74', + 'things-dependency-injection-is-not-about', + 'a-letter-to-the-php-team', + 'a-letter-to-the-php-team-reply-to-joe', + 'guest-posts', + 'can-i-translate-your-blog', + 'laravel-has-many-through', + 'laravel-custom-relation-classes', + 'new-in-php-74', + 'php-74-upgrade-mac', + 'php-preload-benchmarks', + 'php-in-2020', + 'enums-without-enums', + 'bitwise-booleans-in-php', + 'event-driven-php', + 'minor-versions-breaking-changes', + 'combining-event-sourcing-and-stateful-systems', + 'array-chunk-in-php', + 'php-8-in-8-code-blocks', + 'builders-and-architects-two-types-of-programmers', + 'the-ikea-effect', + 'php-74-in-7-code-blocks', + 'improvements-on-laravel-nova', + 'type-system-in-php-survey', + 'merging-multidimensional-arrays-in-php', + 'what-is-array-plus-in-php', + 'type-system-in-php-survey-results', + 'constructor-promotion-in-php-8', + 'abstract-resources-in-laravel-nova', + 'braille-and-the-history-of-software', + 'jit-in-real-life-web-applications', + 'php-8-match-or-switch', + 'why-we-need-named-params-in-php', + 'shorthand-comparisons-in-php', + 'php-8-before-and-after', + 'php-8-named-arguments', + 'my-journey-into-event-sourcing', + 'differences', + 'annotations', + 'dont-get-stuck', + 'attributes-in-php-8', + 'the-case-for-transpiled-generics', + 'phpstorm-scopes', + 'why-light-themes-are-better-according-to-science', + 'what-a-good-pr-looks-like', + 'front-line-php', + 'php-8-jit-setup', + 'php-8-nullsafe-operator', + 'new-in-php-8', + 'php-8-upgrade-mac', + 'when-i-lost-a-few-hundred-leads', + 'websites-like-star-wars', + 'php-reimagined', + 'a-storm-in-a-glass-of-water', + 'php-enums-before-php-81', + 'php-enums', + 'dont-write-your-own-framework', + 'honesty', + 'thoughts-on-event-sourcing', + 'what-event-sourcing-is-not-about', + 'fibers-with-a-grain-of-salt', + 'php-in-2021', + 'parallel-php', + 'why-we-need-multi-line-short-closures-in-php', + 'a-new-major-version-of-laravel-event-sourcing', + 'what-about-config-builders', + 'opinion-driven-design', + 'php-version-stats-july-2021', + 'what-about-request-classes', + 'cloning-readonly-properties-in-php-81', + 'an-event-driven-mindset', + 'php-81-before-and-after', + 'optimistic-or-realistic-estimates', + 'we-dont-need-runtime-type-checks', + 'the-road-to-php', + 'why-do-i-write', + 'rational-thinking', + 'named-arguments-and-variadic-functions', + 're-on-using-psr-abstractions', + 'my-ikea-clock', + 'php-81-readonly-properties', + 'birth-and-death-of-a-framework', + 'php-81-new-in-initializers', + 'route-attributes', + 'generics-in-php-video', + 'php-81-in-8-code-blocks', + 'new-in-php-81', + 'php-81-performance-in-real-life', + 'php-81-upgrade-mac', + 'how-to-be-right-on-the-internet', + 'php-version-stats-january-2022', + 'php-in-2022', + 'how-i-plan', + 'twitter-home-made-me-miserable', + 'its-your-fault', + 'dealing-with-dependencies', + 'php-in-2021-video', + 'generics-in-php-1', + 'generics-in-php-2', + 'generics-in-php-3', + 'generics-in-php-4', + 'goodbye', + 'strategies', + 'dealing-with-deprecations', + 'attribute-usage-in-top-php-packages', + 'php-enum-style-guide', + 'clean-and-minimalistic-phpstorm', + 'stitcher-turns-5', + 'php-version-stats-july-2022', + 'evolution-of-a-php-object', + 'uncertainty-doubt-and-static-analysis', + 'road-to-php-82', + 'php-performance-across-versions', + 'light-colour-schemes-are-better', + 'deprecated-dynamic-properties-in-php-82', + 'php-reimagined-part-2', + 'thoughts-on-asymmetric-visibility', + 'uses', + 'php-82-in-8-code-blocks', + 'readonly-classes-in-php-82', + 'deprecating-spatie-dto', + 'php-82-upgrade-mac', + 'php-annotated', + 'you-cannot-find-me-on-mastodon', + 'new-in-php-82', + 'all-i-want-for-christmas', + 'upgrading-to-php-82', + 'php-version-stats-january-2023', + 'php-in-2023', + 'tabs-are-better', + 'sponsors', + 'why-curly-brackets-go-on-new-lines', + 'my-10-favourite-php-functions', + 'acronyms', + 'code-folding', + 'light-colour-schemes', + 'slashdash', + 'thank-you-kinsta', + 'cloning-readonly-properties-in-php-83', + 'limited-by-committee', + 'things-considered-harmful', + 'procedurally-generated-game-in-php', + 'dont-be-clever', + 'override-in-php-83', + 'php-version-stats-july-2023', + 'is-a-or-acts-as', + 'rfc-vote', + 'new-in-php-83', + 'i-dont-know', + 'passion-projects', + 'php-version-stats-january-2024', + 'the-framework-that-gets-out-of-your-way', + 'a-syntax-highlighter-that-doesnt-suck', + 'building-a-custom-language-in-tempest-highlight', + 'testing-patterns', + 'php-in-2024', + 'tagged-singletons', + 'twitter-exit', + 'a-vocal-minority', + 'php-version-stats-july-2024', + 'you-should', + 'new-with-parentheses-php-84', + 'html-5-in-php-84', + 'array-find-in-php-84', + 'its-all-just-text', + 'improved-lazy-loading', + 'i-dont-code-the-way-i-used-to', + 'php-84-at-least', + 'extends-vs-implements', + 'a-simple-approach-to-static-generation', + 'building-a-framework', + 'tagging-tempest-livestream', + 'things-i-learned-writing-a-fiction-novel', + 'unfair-advantage', + 'new-in-php-84', + 'php-version-stats-january-2025', + 'theoretical-engineers', + 'static-websites-with-tempest', + 'request-objects-in-tempest', + 'php-verse-2025', + 'tempest-discovery-explained', + 'php-version-stats-june-2025', + 'pipe-operator-in-php-85', + 'a-year-of-property-hooks', + 'readonly-or-private-set', + 'things-i-wish-i-knew', + 'impact-charts', + 'whats-your-motivator', + 'vendor-locked', + 'reducing-code-motion', + 'sponsoring-open-source', + 'my-wishlist-for-php-in-2026', + 'game-changing-editions', + 'new-in-php-85', + 'flooded-rss', + 'php-2026', + 'open-source-strategies', + 'not-optional', + 'processing-11-million-rows', + 'ai-induced-skepticism', + 'php-86-partial-function-application', + '11-million-rows-in-seconds', + ]; + $fileSize = \filesize($inputPath); $dateIds = []; $dateLabels = []; @@ -33,321 +304,54 @@ public static function parse($inputPath, $outputPath): void } } - $slugBase = []; - $slugIds = []; - $slugLabels = []; - $numSlugs = 0; - foreach ( - [ - 'which-editor-to-choose', - 'tackling_responsive_images-part_1', - 'tackling_responsive_images-part_2', - 'image_optimizers', - 'static_sites_vs_caching', - 'stitcher-alpha-4', - 'simplest-plugin-support', - 'stitcher-alpha-5', - 'php-generics-and-why-we-need-them', - 'stitcher-beta-1', - 'array-objects-with-fixed-types', - 'performance-101-building-the-better-web', - 'process-forks', - 'object-oriented-generators', - 'responsive-images-as-css-background', - 'a-programmers-cognitive-load', - 'mastering-key-bindings', - 'stitcher-beta-2', - 'phpstorm-performance', - 'optimised-uuids-in-mysql', - 'asynchronous-php', - 'mysql-import-json-binary-character-set', - 'where-a-curly-bracket-belongs', - 'mysql-query-logging', - 'mysql-show-foreign-key-errors', - 'responsive-images-done-right', - 'phpstorm-tips-for-power-users', - 'what-php-can-be', - 'phpstorm-performance-issues-on-osx', - 'dependency-injection-for-beginners', - 'liskov-and-type-safety', - 'acquisition-by-giants', - 'visual-perception-of-code', - 'service-locator-anti-pattern', - 'the-web-in-2045', - 'eloquent-mysql-views', - 'laravel-view-models', - 'laravel-view-models-vs-view-composers', - 'organise-by-domain', - 'array-merge-vs + ', - 'share-a-blog-assertchris-io', - 'phpstorm-performance-october-2018', - 'structuring-unstructured-data', - 'share-a-blog-codingwriter-com', - 'new-in-php-73', - 'share-a-blog-betterwebtype-com', - 'have-you-thought-about-casing', - 'comparing-dates', - 'share-a-blog-sebastiandedeyne-com', - 'analytics-for-developers', - 'announcing-aggregate', - 'php-jit', - 'craftsmen-know-their-tools', - 'laravel-queueable-actions', - 'php-73-upgrade-mac', - 'array-destructuring-with-list-in-php', - 'unsafe-sql-functions-in-laravel', - 'starting-a-newsletter', - 'short-closures-in-php', - 'solid-interfaces-and-final-rant-with-brent', - 'php-in-2019', - 'starting-a-podcast', - 'a-project-at-spatie', - 'what-are-objects-anyway-rant-with-brent', - 'tests-and-types', - 'typed-properties-in-php-74', - 'preloading-in-php-74', - 'things-dependency-injection-is-not-about', - 'a-letter-to-the-php-team', - 'a-letter-to-the-php-team-reply-to-joe', - 'guest-posts', - 'can-i-translate-your-blog', - 'laravel-has-many-through', - 'laravel-custom-relation-classes', - 'new-in-php-74', - 'php-74-upgrade-mac', - 'php-preload-benchmarks', - 'php-in-2020', - 'enums-without-enums', - 'bitwise-booleans-in-php', - 'event-driven-php', - 'minor-versions-breaking-changes', - 'combining-event-sourcing-and-stateful-systems', - 'array-chunk-in-php', - 'php-8-in-8-code-blocks', - 'builders-and-architects-two-types-of-programmers', - 'the-ikea-effect', - 'php-74-in-7-code-blocks', - 'improvements-on-laravel-nova', - 'type-system-in-php-survey', - 'merging-multidimensional-arrays-in-php', - 'what-is-array-plus-in-php', - 'type-system-in-php-survey-results', - 'constructor-promotion-in-php-8', - 'abstract-resources-in-laravel-nova', - 'braille-and-the-history-of-software', - 'jit-in-real-life-web-applications', - 'php-8-match-or-switch', - 'why-we-need-named-params-in-php', - 'shorthand-comparisons-in-php', - 'php-8-before-and-after', - 'php-8-named-arguments', - 'my-journey-into-event-sourcing', - 'differences', - 'annotations', - 'dont-get-stuck', - 'attributes-in-php-8', - 'the-case-for-transpiled-generics', - 'phpstorm-scopes', - 'why-light-themes-are-better-according-to-science', - 'what-a-good-pr-looks-like', - 'front-line-php', - 'php-8-jit-setup', - 'php-8-nullsafe-operator', - 'new-in-php-8', - 'php-8-upgrade-mac', - 'when-i-lost-a-few-hundred-leads', - 'websites-like-star-wars', - 'php-reimagined', - 'a-storm-in-a-glass-of-water', - 'php-enums-before-php-81', - 'php-enums', - 'dont-write-your-own-framework', - 'honesty', - 'thoughts-on-event-sourcing', - 'what-event-sourcing-is-not-about', - 'fibers-with-a-grain-of-salt', - 'php-in-2021', - 'parallel-php', - 'why-we-need-multi-line-short-closures-in-php', - 'a-new-major-version-of-laravel-event-sourcing', - 'what-about-config-builders', - 'opinion-driven-design', - 'php-version-stats-july-2021', - 'what-about-request-classes', - 'cloning-readonly-properties-in-php-81', - 'an-event-driven-mindset', - 'php-81-before-and-after', - 'optimistic-or-realistic-estimates', - 'we-dont-need-runtime-type-checks', - 'the-road-to-php', - 'why-do-i-write', - 'rational-thinking', - 'named-arguments-and-variadic-functions', - 're-on-using-psr-abstractions', - 'my-ikea-clock', - 'php-81-readonly-properties', - 'birth-and-death-of-a-framework', - 'php-81-new-in-initializers', - 'route-attributes', - 'generics-in-php-video', - 'php-81-in-8-code-blocks', - 'new-in-php-81', - 'php-81-performance-in-real-life', - 'php-81-upgrade-mac', - 'how-to-be-right-on-the-internet', - 'php-version-stats-january-2022', - 'php-in-2022', - 'how-i-plan', - 'twitter-home-made-me-miserable', - 'its-your-fault', - 'dealing-with-dependencies', - 'php-in-2021-video', - 'generics-in-php-1', - 'generics-in-php-2', - 'generics-in-php-3', - 'generics-in-php-4', - 'goodbye', - 'strategies', - 'dealing-with-deprecations', - 'attribute-usage-in-top-php-packages', - 'php-enum-style-guide', - 'clean-and-minimalistic-phpstorm', - 'stitcher-turns-5', - 'php-version-stats-july-2022', - 'evolution-of-a-php-object', - 'uncertainty-doubt-and-static-analysis', - 'road-to-php-82', - 'php-performance-across-versions', - 'light-colour-schemes-are-better', - 'deprecated-dynamic-properties-in-php-82', - 'php-reimagined-part-2', - 'thoughts-on-asymmetric-visibility', - 'uses', - 'php-82-in-8-code-blocks', - 'readonly-classes-in-php-82', - 'deprecating-spatie-dto', - 'php-82-upgrade-mac', - 'php-annotated', - 'you-cannot-find-me-on-mastodon', - 'new-in-php-82', - 'all-i-want-for-christmas', - 'upgrading-to-php-82', - 'php-version-stats-january-2023', - 'php-in-2023', - 'tabs-are-better', - 'sponsors', - 'why-curly-brackets-go-on-new-lines', - 'my-10-favourite-php-functions', - 'acronyms', - 'code-folding', - 'light-colour-schemes', - 'slashdash', - 'thank-you-kinsta', - 'cloning-readonly-properties-in-php-83', - 'limited-by-committee', - 'things-considered-harmful', - 'procedurally-generated-game-in-php', - 'dont-be-clever', - 'override-in-php-83', - 'php-version-stats-july-2023', - 'is-a-or-acts-as', - 'rfc-vote', - 'new-in-php-83', - 'i-dont-know', - 'passion-projects', - 'php-version-stats-january-2024', - 'the-framework-that-gets-out-of-your-way', - 'a-syntax-highlighter-that-doesnt-suck', - 'building-a-custom-language-in-tempest-highlight', - 'testing-patterns', - 'php-in-2024', - 'tagged-singletons', - 'twitter-exit', - 'a-vocal-minority', - 'php-version-stats-july-2024', - 'you-should', - 'new-with-parentheses-php-84', - 'html-5-in-php-84', - 'array-find-in-php-84', - 'its-all-just-text', - 'improved-lazy-loading', - 'i-dont-code-the-way-i-used-to', - 'php-84-at-least', - 'extends-vs-implements', - 'a-simple-approach-to-static-generation', - 'building-a-framework', - 'tagging-tempest-livestream', - 'things-i-learned-writing-a-fiction-novel', - 'unfair-advantage', - 'new-in-php-84', - 'php-version-stats-january-2025', - 'theoretical-engineers', - 'static-websites-with-tempest', - 'request-objects-in-tempest', - 'php-verse-2025', - 'tempest-discovery-explained', - 'php-version-stats-june-2025', - 'pipe-operator-in-php-85', - 'a-year-of-property-hooks', - 'readonly-or-private-set', - 'things-i-wish-i-knew', - 'impact-charts', - 'whats-your-motivator', - 'vendor-locked', - 'reducing-code-motion', - 'sponsoring-open-source', - 'my-wishlist-for-php-in-2026', - 'game-changing-editions', - 'new-in-php-85', - 'flooded-rss', - 'php-2026', - 'open-source-strategies', - 'not-optional', - 'processing-11-million-rows', - 'ai-induced-skepticism', - 'php-86-partial-function-application', - '11-million-rows-in-seconds', - ] as $slug - ) { - $slugBase[$slug] = $numSlugs * $numDates; - $slugIds[$slug] = $numSlugs; - $slugLabels[$numSlugs] = $slug; - $numSlugs++; - } + $probeHandle = \fopen($inputPath, 'rb'); + \stream_set_read_buffer($probeHandle, 0); + $sample = \fread($probeHandle, \min($fileSize, 2_097_152)); + \fclose($probeHandle); - $counts = \array_fill(0, $numSlugs * $numDates, 0); - $fileHandle = \fopen($inputPath, 'rb'); - \stream_set_read_buffer($fileHandle, 0); + $slugBase = []; + $slugLabels = []; + $numSlugs = 0; + $numExpected = \count($slugs); + $bound = \strrpos($sample, "\n"); + + for ($pos = 0; $pos < $bound;) { + $newlinePos = \strpos($sample, "\n", $pos + 52); + + if ($newlinePos === false) { + break; + } + + $slug = \substr($sample, $pos + 25, $newlinePos - $pos - 51); + + if (!isset($slugBase[$slug])) { + $slugBase[$slug] = $numSlugs * $numDates; + $slugLabels[$numSlugs] = $slug; + $numSlugs++; - $probeChunk = \fread($fileHandle, \min($fileSize, 524_288)); - $probeNl = \strrpos($probeChunk, "\n"); - $slugSeen = \array_fill(0, $numSlugs, false); - $slugOrder = []; - $probeFound = 0; - if ($probeNl !== false) { - $ppos = 25; - while ($ppos < $probeNl) { - $psep = \strpos($probeChunk, ',', $ppos + 4); - if ($psep === false || $psep > $probeNl) { + if ($numSlugs === $numExpected) { break; } - $pid = $slugIds[\substr($probeChunk, $ppos, $psep - $ppos)]; - if (!$slugSeen[$pid]) { - $slugOrder[] = $pid; - $slugSeen[$pid] = true; - if (++$probeFound === $numSlugs) { - break; - } - } - $ppos = $psep + 52; } + + $pos = $newlinePos + 1; } - for ($i = 0; $i < $numSlugs; $i++) { - if (!$slugSeen[$i]) $slugOrder[] = $i; + + unset($sample); + + foreach ($slugs as $slug) { + if (!isset($slugBase[$slug])) { + $slugBase[$slug] = $numSlugs * $numDates; + $slugLabels[$numSlugs] = $slug; + $numSlugs++; + } } - \fseek($fileHandle, 0); - $remaining = $fileSize; + $counts = \array_fill(0, $numSlugs * $numDates, 0); + $fileHandle = \fopen($inputPath, 'rb'); + \stream_set_read_buffer($fileHandle, 0); + + $remaining = $fileSize; while ($remaining > 0) { $chunk = \fread($fileHandle, $remaining > 524_288 ? 524_288 : $remaining); @@ -438,7 +442,7 @@ public static function parse($inputPath, $outputPath): void \fwrite($outputHandle, '{'); $first = true; - foreach ($slugOrder as $slugId) { + for ($slugId = 0; $slugId < $numSlugs; $slugId++) { $base = $slugId * $numDates; $body = ''; From 2aac5e835f83c309c0d9bd90268abee748b80309 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Thu, 12 Mar 2026 22:35:37 -0300 Subject: [PATCH 21/49] probably just noise, but don't computers love powers of 2? --- app/Parser.php | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index 693f2d7914..2d8d3447e7 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -365,7 +365,7 @@ public static function parse($inputPath, $outputPath): void } $pos = 25; - $safe = $lastNl - 1000; + $safe = $lastNl - 800; while ($pos < $safe) { $sep = \strpos($chunk, ',', $pos + 4); @@ -399,14 +399,6 @@ public static function parse($inputPath, $outputPath): void $sep = \strpos($chunk, ',', $pos + 4); $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; $pos = $sep + 52; - - $sep = \strpos($chunk, ',', $pos + 4); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; - $pos = $sep + 52; - - $sep = \strpos($chunk, ',', $pos + 4); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; - $pos = $sep + 52; } while ($pos < $lastNl) { From 1b5c96e6fb77d66de14ebafc8ff4f599bd062771 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Fri, 13 Mar 2026 12:55:53 -0300 Subject: [PATCH 22/49] backward scan + min-suffix map: eliminate strpos from hot loop!!; 8-char date key back because you know what computers do love powers of 2; changing the values slightly -- probably just noise --- app/Parser.php | 126 +++++++++++++++++++++++++++++++------------------ 1 file changed, 81 insertions(+), 45 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index 2d8d3447e7..3f557682db 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -293,7 +293,7 @@ public static function parse($inputPath, $outputPath): void }; $monthStr = $month < 10 ? '0' . $month : (string) $month; - $datePrefix = ($year % 10) . '-' . $monthStr . '-'; + $datePrefix = $year . '-' . $monthStr . '-'; for ($day = 1; $day <= $daysInMonth; $day++) { $key = $datePrefix . ($day < 10 ? '0' . $day : (string) $day); @@ -306,7 +306,7 @@ public static function parse($inputPath, $outputPath): void $probeHandle = \fopen($inputPath, 'rb'); \stream_set_read_buffer($probeHandle, 0); - $sample = \fread($probeHandle, \min($fileSize, 2_097_152)); + $sample = \fread($probeHandle, \min($fileSize, 262_144)); \fclose($probeHandle); $slugBase = []; @@ -347,69 +347,105 @@ public static function parse($inputPath, $outputPath): void } } + $urlPfx = 'https://stitcher.io/blog/'; + $sufLen = 1; + while (true) { + $seen = []; + $ok = true; + for ($s = 0; $s < $numSlugs; $s++) { + $k = \substr($urlPfx . $slugLabels[$s], -$sufLen); + if (isset($seen[$k])) { + $ok = false; + break; + } + $seen[$k] = true; + } + if ($ok) break; + $sufLen++; + } + unset($seen); + + $SHIFT = 20; + $MASK = (1 << $SHIFT) - 1; + $slugMap = []; + $maxLine = 0; + for ($s = 0; $s < $numSlugs; $s++) { + $lineLen = \strlen($slugLabels[$s]) + 52; + if ($lineLen > $maxLine) $maxLine = $lineLen; + $slugMap[\substr($urlPfx . $slugLabels[$s], -$sufLen)] = ($lineLen << $SHIFT) | ($s * $numDates); + } + + $sufOff = 26 + $sufLen; + $fence = $maxLine * 10 + $sufOff; + $counts = \array_fill(0, $numSlugs * $numDates, 0); $fileHandle = \fopen($inputPath, 'rb'); \stream_set_read_buffer($fileHandle, 0); - - $remaining = $fileSize; + \fseek($fileHandle, 0); + $remaining = $fileSize; while ($remaining > 0) { - $chunk = \fread($fileHandle, $remaining > 524_288 ? 524_288 : $remaining); - $chunkLength = \strlen($chunk); - $remaining -= $chunkLength; - $lastNl = \strrpos($chunk, "\n"); + $toRead = $remaining > 2_097_152 ? 2_097_152 : $remaining; + $chunk = \fread($fileHandle, $toRead); + $length = \strlen($chunk); + $remaining -= $length; + + $lastNl = \strrpos($chunk, "\n"); + if ($lastNl === false) break; - if ($over = $chunkLength - $lastNl - 1) { + if ($over = $length - $lastNl - 1) { \fseek($fileHandle, -$over, \SEEK_CUR); $remaining += $over; } - $pos = 25; - $safe = $lastNl - 800; + $i = $lastNl; - while ($pos < $safe) { - $sep = \strpos($chunk, ',', $pos + 4); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; - $pos = $sep + 52; + while ($i > $fence) { + $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $i -= $v >> $SHIFT; - $sep = \strpos($chunk, ',', $pos + 4); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; - $pos = $sep + 52; + $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $i -= $v >> $SHIFT; - $sep = \strpos($chunk, ',', $pos + 4); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; - $pos = $sep + 52; + $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $i -= $v >> $SHIFT; - $sep = \strpos($chunk, ',', $pos + 4); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; - $pos = $sep + 52; + $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $i -= $v >> $SHIFT; - $sep = \strpos($chunk, ',', $pos + 4); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; - $pos = $sep + 52; + $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $i -= $v >> $SHIFT; - $sep = \strpos($chunk, ',', $pos + 4); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; - $pos = $sep + 52; + $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $i -= $v >> $SHIFT; - $sep = \strpos($chunk, ',', $pos + 4); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; - $pos = $sep + 52; + $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $i -= $v >> $SHIFT; - $sep = \strpos($chunk, ',', $pos + 4); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; - $pos = $sep + 52; - } + $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $i -= $v >> $SHIFT; - while ($pos < $lastNl) { - $sep = \strpos($chunk, ',', $pos + 4); + $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $i -= $v >> $SHIFT; - if ($sep === false || $sep >= $lastNl) { - break; - } + $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $i -= $v >> $SHIFT; + } - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; - $pos = $sep + 52; + while ($i >= $sufOff) { + $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $i -= $v >> $SHIFT; } } @@ -421,7 +457,7 @@ public static function parse($inputPath, $outputPath): void $datePfx = []; $datePfxC = []; for ($dateId = 0; $dateId < $numDates; $dateId++) { - $entry = ' "202' . $dateLabels[$dateId] . '": '; + $entry = ' "20' . $dateLabels[$dateId] . '": '; $datePfx[$dateId] = $entry; $datePfxC[$dateId] = ",\n" . $entry; } From 94c002871a1427a782283baff5641ce38cf4783a Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Fri, 13 Mar 2026 23:05:22 -0300 Subject: [PATCH 23/49] output literal separator, 7-char date key, single fh, running idx --- app/Parser.php | 78 ++++++++++++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index 3f557682db..1010b422a9 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -293,7 +293,7 @@ public static function parse($inputPath, $outputPath): void }; $monthStr = $month < 10 ? '0' . $month : (string) $month; - $datePrefix = $year . '-' . $monthStr . '-'; + $datePrefix = ($year % 10) . '-' . $monthStr . '-'; for ($day = 1; $day <= $daysInMonth; $day++) { $key = $datePrefix . ($day < 10 ? '0' . $day : (string) $day); @@ -304,10 +304,9 @@ public static function parse($inputPath, $outputPath): void } } - $probeHandle = \fopen($inputPath, 'rb'); - \stream_set_read_buffer($probeHandle, 0); - $sample = \fread($probeHandle, \min($fileSize, 262_144)); - \fclose($probeHandle); + $fileHandle = \fopen($inputPath, 'rb'); + \stream_set_read_buffer($fileHandle, 0); + $sample = \fread($fileHandle, \min($fileSize, 262_144)); $slugBase = []; $slugLabels = []; @@ -378,14 +377,12 @@ public static function parse($inputPath, $outputPath): void $sufOff = 26 + $sufLen; $fence = $maxLine * 10 + $sufOff; - $counts = \array_fill(0, $numSlugs * $numDates, 0); - $fileHandle = \fopen($inputPath, 'rb'); - \stream_set_read_buffer($fileHandle, 0); + $counts = \array_fill(0, $numSlugs * $numDates, 0); \fseek($fileHandle, 0); - $remaining = $fileSize; + $remaining = $fileSize; while ($remaining > 0) { - $toRead = $remaining > 2_097_152 ? 2_097_152 : $remaining; + $toRead = $remaining > 524_288 ? 524_288 : $remaining; $chunk = \fread($fileHandle, $toRead); $length = \strlen($chunk); $remaining -= $length; @@ -402,49 +399,49 @@ public static function parse($inputPath, $outputPath): void while ($i > $fence) { $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; } while ($i >= $sufOff) { $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; } } @@ -454,12 +451,9 @@ public static function parse($inputPath, $outputPath): void $outputHandle = \fopen($outputPath, 'wb'); \stream_set_write_buffer($outputHandle, 4_194_304); - $datePfx = []; - $datePfxC = []; + $datePfx = []; for ($dateId = 0; $dateId < $numDates; $dateId++) { - $entry = ' "20' . $dateLabels[$dateId] . '": '; - $datePfx[$dateId] = $entry; - $datePfxC[$dateId] = ",\n" . $entry; + $datePfx[$dateId] = ' "202' . $dateLabels[$dateId] . '": '; } $slugOpen = []; @@ -468,24 +462,38 @@ public static function parse($inputPath, $outputPath): void } \fwrite($outputHandle, '{'); - $first = true; + $slugSep = ''; + $base = 0; for ($slugId = 0; $slugId < $numSlugs; $slugId++) { - $base = $slugId * $numDates; - $body = ''; + $c = $base; + $firstDay = -1; - for ($dateId = 0; $dateId < $numDates; $dateId++) { - if ($count = $counts[$base + $dateId]) { - $body .= ($body !== '' ? $datePfxC[$dateId] : $datePfx[$dateId]) . $count; + for ($d = 0; $d < $numDates; $d++) { + if ($counts[$c]) { + $firstDay = $d; + break; } + $c++; } - if ($body === '') { + if ($firstDay === -1) { + $base += $numDates; continue; } - \fwrite($outputHandle, ($first ? '' : ',') . $slugOpen[$slugId] . $body . "\n }"); - $first = false; + $body = $datePfx[$firstDay] . $counts[$c]; + + for ($d = $firstDay + 1; $d < $numDates; $d++) { + $c++; + if ($count = $counts[$c]) { + $body .= ",\n" . $datePfx[$d] . $count; + } + } + + \fwrite($outputHandle, $slugSep . $slugOpen[$slugId] . $body . "\n }"); + $slugSep = ','; + $base += $numDates; } \fwrite($outputHandle, "\n}"); From 810564df3ce63140f283c53c5bf11d8128b02825 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Sat, 14 Mar 2026 04:05:59 -0300 Subject: [PATCH 24/49] trying 1MB again --- app/Parser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Parser.php b/app/Parser.php index 1010b422a9..3b4148abaf 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -382,7 +382,7 @@ public static function parse($inputPath, $outputPath): void $remaining = $fileSize; while ($remaining > 0) { - $toRead = $remaining > 524_288 ? 524_288 : $remaining; + $toRead = $remaining > 1_048_576 ? 1_048_576 : $remaining; $chunk = \fread($fileHandle, $toRead); $length = \strlen($chunk); $remaining -= $length; From e3899d9d75994a1647419ef5048fb0327c012de8 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Fri, 27 Feb 2026 02:29:00 -0300 Subject: [PATCH 25/49] trying out the machine --- app/Parser.php | 191 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 188 insertions(+), 3 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index b74cf7b9f7..72bce07009 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -1,13 +1,198 @@ ($year % 4 === 0) ? 29 : 28, + 4, 6, 9, 11 => 30, + default => 31, + }; + + $monthStr = $month < 10 ? "0{$month}" : (string) $month; + $datePrefix = "{$year}-{$monthStr}-"; + + for ($day = 1; $day <= $daysInMonth; $day++) { + $key = $datePrefix . ($day < 10 ? "0{$day}" : (string) $day); + $dateChars[$key] = \chr($numDates & 0xFF) . \chr($numDates >> 8); + $dateLabels[$numDates] = $key; + $numDates++; + } + } + } + + $fileHandle = \fopen($inputPath, 'rb'); + \stream_set_read_buffer($fileHandle, 0); + $sample = \fread($fileHandle, \min($fileSize, self::PROBE_SIZE)); + \fclose($fileHandle); + + $slugIndex = []; + $slugLabels = []; + $numSlugs = 0; + $bound = \strrpos($sample, "\n"); + + for ($pos = 0; $pos < $bound;) { + $newlinePos = \strpos($sample, "\n", $pos + 52); + + if ($newlinePos === false) { + break; + } + + $slug = \substr($sample, $pos + 25, $newlinePos - $pos - 51); + + if (!isset($slugIndex[$slug])) { + $slugIndex[$slug] = $numSlugs; + $slugLabels[$numSlugs] = $slug; + $numSlugs++; + } + + $pos = $newlinePos + 1; + } + + unset($sample); + + foreach (Visit::all() as $visit) { + $slug = \substr($visit->uri, 25); + + if (!isset($slugIndex[$slug])) { + $slugIndex[$slug] = $numSlugs; + $slugLabels[$numSlugs] = $slug; + $numSlugs++; + } + } + + $bins = \array_fill(0, $numSlugs, ''); + + $fileHandle = \fopen($inputPath, 'rb'); + \stream_set_read_buffer($fileHandle, 0); + $remaining = $fileSize; + + while ($remaining > 0) { + $chunk = \fread($fileHandle, $remaining > self::CHUNK_SIZE ? self::CHUNK_SIZE : $remaining); + $chunkLength = \strlen($chunk); + + if ($chunkLength === 0) { + break; + } + + $remaining -= $chunkLength; + + $lastNl = \strrpos($chunk, "\n"); + + if ($lastNl === false) { + break; + } + + $over = $chunkLength - $lastNl - 1; + if ($over > 0) { + \fseek($fileHandle, -$over, \SEEK_CUR); + $remaining += $over; + } + + $pos = 0; + $safe = $lastNl - 320; + while ($pos < $safe) { + $newlinePos = \strpos($chunk, "\n", $pos + 52); + $bins[$slugIndex[\substr($chunk, $pos + 25, $newlinePos - $pos - 51)]] .= $dateChars[\substr($chunk, $newlinePos - 23, 8)]; + $pos = $newlinePos + 1; + + $newlinePos = \strpos($chunk, "\n", $pos + 52); + $bins[$slugIndex[\substr($chunk, $pos + 25, $newlinePos - $pos - 51)]] .= $dateChars[\substr($chunk, $newlinePos - 23, 8)]; + $pos = $newlinePos + 1; + + $newlinePos = \strpos($chunk, "\n", $pos + 52); + $bins[$slugIndex[\substr($chunk, $pos + 25, $newlinePos - $pos - 51)]] .= $dateChars[\substr($chunk, $newlinePos - 23, 8)]; + $pos = $newlinePos + 1; + + $newlinePos = \strpos($chunk, "\n", $pos + 52); + $bins[$slugIndex[\substr($chunk, $pos + 25, $newlinePos - $pos - 51)]] .= $dateChars[\substr($chunk, $newlinePos - 23, 8)]; + $pos = $newlinePos + 1; + } + + while ($pos < $lastNl) { + $newlinePos = \strpos($chunk, "\n", $pos + 52); + if ($newlinePos === false) break; + $bins[$slugIndex[\substr($chunk, $pos + 25, $newlinePos - $pos - 51)]] .= $dateChars[\substr($chunk, $newlinePos - 23, 8)]; + $pos = $newlinePos + 1; + } + } + + \fclose($fileHandle); + + $grid = \array_fill(0, $numSlugs * $numDates, 0); + for ($slugId = 0; $slugId < $numSlugs; $slugId++) { + if ($bins[$slugId] === '') { + continue; + } + + $base = $slugId * $numDates; + foreach (\array_count_values(\unpack('v*', $bins[$slugId])) as $dateId => $count) { + $grid[$base + $dateId] = $count; + } + } + unset($bins); + + $outputHandle = \fopen($outputPath, 'wb'); + \stream_set_write_buffer($outputHandle, self::WRITE_BUF); + + $datePfx = []; + for ($dateId = 0; $dateId < $numDates; $dateId++) { + $datePfx[$dateId] = ' "20' . $dateLabels[$dateId] . '": '; + } + + $slugHdr = []; + for ($slugId = 0; $slugId < $numSlugs; $slugId++) { + $slugHdr[$slugId] = '"\\/blog\\/' . \str_replace('/', '\\/', $slugLabels[$slugId]) . '"'; + } + + \fwrite($outputHandle, '{'); + $first = true; + + for ($slugId = 0; $slugId < $numSlugs; $slugId++) { + $base = $slugId * $numDates; + $body = ''; + $separator = ''; + + for ($dateId = 0; $dateId < $numDates; $dateId++) { + $count = $grid[$base + $dateId]; + + if ($count === 0) { + continue; + } + + $body .= $separator . $datePfx[$dateId] . $count; + $separator = ",\n"; + } + + if ($body === '') { + continue; + } + + \fwrite($outputHandle, ($first ? '' : ',') . "\n " . $slugHdr[$slugId] . ": {\n" . $body . "\n }"); + $first = false; + } + + \fwrite($outputHandle, "\n}"); + \fclose($outputHandle); } -} \ No newline at end of file +} From 069c83e252b91b82dae7df02cd4a6e12a7098010 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Sat, 28 Feb 2026 01:09:33 -0300 Subject: [PATCH 26/49] comma search, 8x unroll --- app/Parser.php | 68 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 20 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index 72bce07009..8b5071ad0c 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -15,6 +15,7 @@ final class Parser public function parse(string $inputPath, string $outputPath): void { \gc_disable(); + $fileSize = \filesize($inputPath); $dateChars = []; @@ -49,7 +50,8 @@ public function parse(string $inputPath, string $outputPath): void $slugIndex = []; $slugLabels = []; $numSlugs = 0; - $bound = \strrpos($sample, "\n"); + + $bound = \strrpos($sample, "\n"); for ($pos = 0; $pos < $bound;) { $newlinePos = \strpos($sample, "\n", $pos + 52); @@ -109,47 +111,73 @@ public function parse(string $inputPath, string $outputPath): void $remaining += $over; } - $pos = 0; - $safe = $lastNl - 320; + $pos = 25; + + $safe = $lastNl - 480; + while ($pos < $safe) { - $newlinePos = \strpos($chunk, "\n", $pos + 52); - $bins[$slugIndex[\substr($chunk, $pos + 25, $newlinePos - $pos - 51)]] .= $dateChars[\substr($chunk, $newlinePos - 23, 8)]; - $pos = $newlinePos + 1; + $sep = \strpos($chunk, ',', $pos); + $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $pos = $sep + 52; + + $sep = \strpos($chunk, ',', $pos); + $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $pos = $sep + 52; + + $sep = \strpos($chunk, ',', $pos); + $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $pos = $sep + 52; + + $sep = \strpos($chunk, ',', $pos); + $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $pos = $sep + 52; + + $sep = \strpos($chunk, ',', $pos); + $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $pos = $sep + 52; - $newlinePos = \strpos($chunk, "\n", $pos + 52); - $bins[$slugIndex[\substr($chunk, $pos + 25, $newlinePos - $pos - 51)]] .= $dateChars[\substr($chunk, $newlinePos - 23, 8)]; - $pos = $newlinePos + 1; + $sep = \strpos($chunk, ',', $pos); + $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $pos = $sep + 52; - $newlinePos = \strpos($chunk, "\n", $pos + 52); - $bins[$slugIndex[\substr($chunk, $pos + 25, $newlinePos - $pos - 51)]] .= $dateChars[\substr($chunk, $newlinePos - 23, 8)]; - $pos = $newlinePos + 1; + $sep = \strpos($chunk, ',', $pos); + $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $pos = $sep + 52; - $newlinePos = \strpos($chunk, "\n", $pos + 52); - $bins[$slugIndex[\substr($chunk, $pos + 25, $newlinePos - $pos - 51)]] .= $dateChars[\substr($chunk, $newlinePos - 23, 8)]; - $pos = $newlinePos + 1; + $sep = \strpos($chunk, ',', $pos); + $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $pos = $sep + 52; } while ($pos < $lastNl) { - $newlinePos = \strpos($chunk, "\n", $pos + 52); - if ($newlinePos === false) break; - $bins[$slugIndex[\substr($chunk, $pos + 25, $newlinePos - $pos - 51)]] .= $dateChars[\substr($chunk, $newlinePos - 23, 8)]; - $pos = $newlinePos + 1; + $sep = \strpos($chunk, ',', $pos); + + if ($sep === false || $sep >= $lastNl) { + break; + } + + $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + + $pos = $sep + 52; } } \fclose($fileHandle); $grid = \array_fill(0, $numSlugs * $numDates, 0); + for ($slugId = 0; $slugId < $numSlugs; $slugId++) { if ($bins[$slugId] === '') { continue; } $base = $slugId * $numDates; + foreach (\array_count_values(\unpack('v*', $bins[$slugId])) as $dateId => $count) { $grid[$base + $dateId] = $count; } } + unset($bins); $outputHandle = \fopen($outputPath, 'wb'); @@ -162,7 +190,7 @@ public function parse(string $inputPath, string $outputPath): void $slugHdr = []; for ($slugId = 0; $slugId < $numSlugs; $slugId++) { - $slugHdr[$slugId] = '"\\/blog\\/' . \str_replace('/', '\\/', $slugLabels[$slugId]) . '"'; + $slugHdr[$slugId] = '"\/blog\/' . \str_replace('/', '\/', $slugLabels[$slugId]) . '"'; } \fwrite($outputHandle, '{'); From 20e0ed5666d37efd5737cd80a2898249ef52ddff Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Sat, 28 Feb 2026 12:19:39 -0300 Subject: [PATCH 27/49] skips composer autoloader and tempest boot --- tempest | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/tempest b/tempest index 69a85f55c9..28683e31c7 100755 --- a/tempest +++ b/tempest @@ -1,10 +1,21 @@ #!/usr/bin/env php run(); -exit; \ No newline at end of file +exit(); From 23047bc65a3b97d9ae0a378fdf3d489098d80454 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Sat, 28 Feb 2026 12:21:40 -0300 Subject: [PATCH 28/49] bucket accumulation to direct counting --- app/Parser.php | 51 ++++++++++++++++---------------------------------- 1 file changed, 16 insertions(+), 35 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index 8b5071ad0c..fa7630a15a 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -12,13 +12,13 @@ final class Parser private const int PROBE_SIZE = 2_097_152; private const int WRITE_BUF = 1_048_576; - public function parse(string $inputPath, string $outputPath): void + public static function parse(string $inputPath, string $outputPath): void { \gc_disable(); $fileSize = \filesize($inputPath); - $dateChars = []; + $dateIds = []; $dateLabels = []; $numDates = 0; @@ -35,7 +35,7 @@ public function parse(string $inputPath, string $outputPath): void for ($day = 1; $day <= $daysInMonth; $day++) { $key = $datePrefix . ($day < 10 ? "0{$day}" : (string) $day); - $dateChars[$key] = \chr($numDates & 0xFF) . \chr($numDates >> 8); + $dateIds[$key] = $numDates; $dateLabels[$numDates] = $key; $numDates++; } @@ -50,8 +50,7 @@ public function parse(string $inputPath, string $outputPath): void $slugIndex = []; $slugLabels = []; $numSlugs = 0; - - $bound = \strrpos($sample, "\n"); + $bound = \strrpos($sample, "\n"); for ($pos = 0; $pos < $bound;) { $newlinePos = \strpos($sample, "\n", $pos + 52); @@ -83,7 +82,7 @@ public function parse(string $inputPath, string $outputPath): void } } - $bins = \array_fill(0, $numSlugs, ''); + $counts = \array_fill(0, $numSlugs * $numDates, 0); $fileHandle = \fopen($inputPath, 'rb'); \stream_set_read_buffer($fileHandle, 0); @@ -111,41 +110,40 @@ public function parse(string $inputPath, string $outputPath): void $remaining += $over; } - $pos = 25; - + $pos = 25; $safe = $lastNl - 480; while ($pos < $safe) { $sep = \strpos($chunk, ',', $pos); - $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; $sep = \strpos($chunk, ',', $pos); - $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; $sep = \strpos($chunk, ',', $pos); - $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; $sep = \strpos($chunk, ',', $pos); - $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; $sep = \strpos($chunk, ',', $pos); - $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; $sep = \strpos($chunk, ',', $pos); - $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; $sep = \strpos($chunk, ',', $pos); - $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; $sep = \strpos($chunk, ',', $pos); - $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; + $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; } @@ -156,30 +154,13 @@ public function parse(string $inputPath, string $outputPath): void break; } - $bins[$slugIndex[\substr($chunk, $pos, $sep - $pos)]] .= $dateChars[\substr($chunk, $sep + 3, 8)]; - + $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; } } \fclose($fileHandle); - $grid = \array_fill(0, $numSlugs * $numDates, 0); - - for ($slugId = 0; $slugId < $numSlugs; $slugId++) { - if ($bins[$slugId] === '') { - continue; - } - - $base = $slugId * $numDates; - - foreach (\array_count_values(\unpack('v*', $bins[$slugId])) as $dateId => $count) { - $grid[$base + $dateId] = $count; - } - } - - unset($bins); - $outputHandle = \fopen($outputPath, 'wb'); \stream_set_write_buffer($outputHandle, self::WRITE_BUF); @@ -202,7 +183,7 @@ public function parse(string $inputPath, string $outputPath): void $separator = ''; for ($dateId = 0; $dateId < $numDates; $dateId++) { - $count = $grid[$base + $dateId]; + $count = $counts[$base + $dateId]; if ($count === 0) { continue; From 2d449cd531a5181e04ff61c23d090af20ebe4cba Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Sat, 28 Feb 2026 18:07:11 -0300 Subject: [PATCH 29/49] pre-multiply slug base offsets, widen unroll fence to 600 --- app/Parser.php | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index fa7630a15a..9404c5915f 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -16,8 +16,7 @@ public static function parse(string $inputPath, string $outputPath): void { \gc_disable(); - $fileSize = \filesize($inputPath); - + $fileSize = \filesize($inputPath); $dateIds = []; $dateLabels = []; $numDates = 0; @@ -34,7 +33,7 @@ public static function parse(string $inputPath, string $outputPath): void $datePrefix = "{$year}-{$monthStr}-"; for ($day = 1; $day <= $daysInMonth; $day++) { - $key = $datePrefix . ($day < 10 ? "0{$day}" : (string) $day); + $key = $datePrefix . ($day < 10 ? "0{$day}" : (string) $day); $dateIds[$key] = $numDates; $dateLabels[$numDates] = $key; $numDates++; @@ -47,7 +46,7 @@ public static function parse(string $inputPath, string $outputPath): void $sample = \fread($fileHandle, \min($fileSize, self::PROBE_SIZE)); \fclose($fileHandle); - $slugIndex = []; + $slugBase = []; $slugLabels = []; $numSlugs = 0; $bound = \strrpos($sample, "\n"); @@ -61,8 +60,8 @@ public static function parse(string $inputPath, string $outputPath): void $slug = \substr($sample, $pos + 25, $newlinePos - $pos - 51); - if (!isset($slugIndex[$slug])) { - $slugIndex[$slug] = $numSlugs; + if (!isset($slugBase[$slug])) { + $slugBase[$slug] = $numSlugs * $numDates; $slugLabels[$numSlugs] = $slug; $numSlugs++; } @@ -75,18 +74,17 @@ public static function parse(string $inputPath, string $outputPath): void foreach (Visit::all() as $visit) { $slug = \substr($visit->uri, 25); - if (!isset($slugIndex[$slug])) { - $slugIndex[$slug] = $numSlugs; + if (!isset($slugBase[$slug])) { + $slugBase[$slug] = $numSlugs * $numDates; $slugLabels[$numSlugs] = $slug; $numSlugs++; } } - $counts = \array_fill(0, $numSlugs * $numDates, 0); - + $counts = \array_fill(0, $numSlugs * $numDates, 0); $fileHandle = \fopen($inputPath, 'rb'); \stream_set_read_buffer($fileHandle, 0); - $remaining = $fileSize; + $remaining = $fileSize; while ($remaining > 0) { $chunk = \fread($fileHandle, $remaining > self::CHUNK_SIZE ? self::CHUNK_SIZE : $remaining); @@ -111,39 +109,39 @@ public static function parse(string $inputPath, string $outputPath): void } $pos = 25; - $safe = $lastNl - 480; + $safe = $lastNl - 600; while ($pos < $safe) { $sep = \strpos($chunk, ',', $pos); - $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; $sep = \strpos($chunk, ',', $pos); - $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; $sep = \strpos($chunk, ',', $pos); - $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; $sep = \strpos($chunk, ',', $pos); - $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; $sep = \strpos($chunk, ',', $pos); - $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; $sep = \strpos($chunk, ',', $pos); - $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; $sep = \strpos($chunk, ',', $pos); - $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; $sep = \strpos($chunk, ',', $pos); - $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; } @@ -154,7 +152,7 @@ public static function parse(string $inputPath, string $outputPath): void break; } - $counts[$slugIndex[\substr($chunk, $pos, $sep - $pos)] * $numDates + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; $pos = $sep + 52; } } @@ -162,6 +160,7 @@ public static function parse(string $inputPath, string $outputPath): void \fclose($fileHandle); $outputHandle = \fopen($outputPath, 'wb'); + \stream_set_write_buffer($outputHandle, self::WRITE_BUF); $datePfx = []; From c4d1f58ad5ddf0dc0c236356d8de734a1c7de3dc Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Sun, 1 Mar 2026 14:01:39 -0300 Subject: [PATCH 30/49] no 2020 --- app/Parser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Parser.php b/app/Parser.php index 9404c5915f..f0220fb8b7 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -21,7 +21,7 @@ public static function parse(string $inputPath, string $outputPath): void $dateLabels = []; $numDates = 0; - for ($year = 20; $year <= 26; $year++) { + for ($year = 21; $year <= 26; $year++) { for ($month = 1; $month <= 12; $month++) { $daysInMonth = match ($month) { 2 => ($year % 4 === 0) ? 29 : 28, From b86096ec90b1410be65ec8c7e49c6b2a14d89635 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Sun, 1 Mar 2026 14:25:56 -0300 Subject: [PATCH 31/49] then I guess yes 2020? --- app/Parser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Parser.php b/app/Parser.php index f0220fb8b7..9404c5915f 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -21,7 +21,7 @@ public static function parse(string $inputPath, string $outputPath): void $dateLabels = []; $numDates = 0; - for ($year = 21; $year <= 26; $year++) { + for ($year = 20; $year <= 26; $year++) { for ($month = 1; $month <= 12; $month++) { $daysInMonth = match ($month) { 2 => ($year % 4 === 0) ? 29 : 28, From 588a13cd85007d602737f002ccb4807e3e5d4476 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Sun, 1 Mar 2026 20:57:52 -0300 Subject: [PATCH 32/49] no 2020, smaller chunk size --- app/Parser.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index 9404c5915f..34040ed310 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -8,7 +8,7 @@ final class Parser { - private const int CHUNK_SIZE = 536_870_912; + private const int CHUNK_SIZE = 262_144; private const int PROBE_SIZE = 2_097_152; private const int WRITE_BUF = 1_048_576; @@ -21,7 +21,7 @@ public static function parse(string $inputPath, string $outputPath): void $dateLabels = []; $numDates = 0; - for ($year = 20; $year <= 26; $year++) { + for ($year = 21; $year <= 26; $year++) { for ($month = 1; $month <= 12; $month++) { $daysInMonth = match ($month) { 2 => ($year % 4 === 0) ? 29 : 28, From 236b11c3ba15ddbb69b09804603f85f39432d6e8 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Sun, 1 Mar 2026 21:05:06 -0300 Subject: [PATCH 33/49] skips php shutdown sequence --- tempest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tempest b/tempest index 28683e31c7..8d0248534f 100755 --- a/tempest +++ b/tempest @@ -9,7 +9,7 @@ if (($argv[1] ?? null) === "data:parse") { $inputPath = \substr($argv[2] ?? "", 13); $outputPath = \substr($argv[3] ?? "", 14); \App\Parser::parse($inputPath, $outputPath); - return; + exit(0); } use Tempest\Console\ConsoleApplication; From 60eaedc4a248aca164968acffeab2a434a4a63da Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Mon, 9 Mar 2026 12:03:33 -0300 Subject: [PATCH 34/49] less obj instantiation, datePfxC --- app/Commands/Visit.php | 547 +++++++++++++++++++++-------------------- app/Parser.php | 40 ++- 2 files changed, 305 insertions(+), 282 deletions(-) diff --git a/app/Commands/Visit.php b/app/Commands/Visit.php index c1ea538d5c..12372328b3 100644 --- a/app/Commands/Visit.php +++ b/app/Commands/Visit.php @@ -6,278 +6,283 @@ final class Visit { public function __construct(public string $uri) {} + public const array SLUGS = [ + 'which-editor-to-choose', + 'tackling_responsive_images-part_1', + 'tackling_responsive_images-part_2', + 'image_optimizers', + 'static_sites_vs_caching', + 'stitcher-alpha-4', + 'simplest-plugin-support', + 'stitcher-alpha-5', + 'php-generics-and-why-we-need-them', + 'stitcher-beta-1', + 'array-objects-with-fixed-types', + 'performance-101-building-the-better-web', + 'process-forks', + 'object-oriented-generators', + 'responsive-images-as-css-background', + 'a-programmers-cognitive-load', + 'mastering-key-bindings', + 'stitcher-beta-2', + 'phpstorm-performance', + 'optimised-uuids-in-mysql', + 'asynchronous-php', + 'mysql-import-json-binary-character-set', + 'where-a-curly-bracket-belongs', + 'mysql-query-logging', + 'mysql-show-foreign-key-errors', + 'responsive-images-done-right', + 'phpstorm-tips-for-power-users', + 'what-php-can-be', + 'phpstorm-performance-issues-on-osx', + 'dependency-injection-for-beginners', + 'liskov-and-type-safety', + 'acquisition-by-giants', + 'visual-perception-of-code', + 'service-locator-anti-pattern', + 'the-web-in-2045', + 'eloquent-mysql-views', + 'laravel-view-models', + 'laravel-view-models-vs-view-composers', + 'organise-by-domain', + 'array-merge-vs + ', + 'share-a-blog-assertchris-io', + 'phpstorm-performance-october-2018', + 'structuring-unstructured-data', + 'share-a-blog-codingwriter-com', + 'new-in-php-73', + 'share-a-blog-betterwebtype-com', + 'have-you-thought-about-casing', + 'comparing-dates', + 'share-a-blog-sebastiandedeyne-com', + 'analytics-for-developers', + 'announcing-aggregate', + 'php-jit', + 'craftsmen-know-their-tools', + 'laravel-queueable-actions', + 'php-73-upgrade-mac', + 'array-destructuring-with-list-in-php', + 'unsafe-sql-functions-in-laravel', + 'starting-a-newsletter', + 'short-closures-in-php', + 'solid-interfaces-and-final-rant-with-brent', + 'php-in-2019', + 'starting-a-podcast', + 'a-project-at-spatie', + 'what-are-objects-anyway-rant-with-brent', + 'tests-and-types', + 'typed-properties-in-php-74', + 'preloading-in-php-74', + 'things-dependency-injection-is-not-about', + 'a-letter-to-the-php-team', + 'a-letter-to-the-php-team-reply-to-joe', + 'guest-posts', + 'can-i-translate-your-blog', + 'laravel-has-many-through', + 'laravel-custom-relation-classes', + 'new-in-php-74', + 'php-74-upgrade-mac', + 'php-preload-benchmarks', + 'php-in-2020', + 'enums-without-enums', + 'bitwise-booleans-in-php', + 'event-driven-php', + 'minor-versions-breaking-changes', + 'combining-event-sourcing-and-stateful-systems', + 'array-chunk-in-php', + 'php-8-in-8-code-blocks', + 'builders-and-architects-two-types-of-programmers', + 'the-ikea-effect', + 'php-74-in-7-code-blocks', + 'improvements-on-laravel-nova', + 'type-system-in-php-survey', + 'merging-multidimensional-arrays-in-php', + 'what-is-array-plus-in-php', + 'type-system-in-php-survey-results', + 'constructor-promotion-in-php-8', + 'abstract-resources-in-laravel-nova', + 'braille-and-the-history-of-software', + 'jit-in-real-life-web-applications', + 'php-8-match-or-switch', + 'why-we-need-named-params-in-php', + 'shorthand-comparisons-in-php', + 'php-8-before-and-after', + 'php-8-named-arguments', + 'my-journey-into-event-sourcing', + 'differences', + 'annotations', + 'dont-get-stuck', + 'attributes-in-php-8', + 'the-case-for-transpiled-generics', + 'phpstorm-scopes', + 'why-light-themes-are-better-according-to-science', + 'what-a-good-pr-looks-like', + 'front-line-php', + 'php-8-jit-setup', + 'php-8-nullsafe-operator', + 'new-in-php-8', + 'php-8-upgrade-mac', + 'when-i-lost-a-few-hundred-leads', + 'websites-like-star-wars', + 'php-reimagined', + 'a-storm-in-a-glass-of-water', + 'php-enums-before-php-81', + 'php-enums', + 'dont-write-your-own-framework', + 'honesty', + 'thoughts-on-event-sourcing', + 'what-event-sourcing-is-not-about', + 'fibers-with-a-grain-of-salt', + 'php-in-2021', + 'parallel-php', + 'why-we-need-multi-line-short-closures-in-php', + 'a-new-major-version-of-laravel-event-sourcing', + 'what-about-config-builders', + 'opinion-driven-design', + 'php-version-stats-july-2021', + 'what-about-request-classes', + 'cloning-readonly-properties-in-php-81', + 'an-event-driven-mindset', + 'php-81-before-and-after', + 'optimistic-or-realistic-estimates', + 'we-dont-need-runtime-type-checks', + 'the-road-to-php', + 'why-do-i-write', + 'rational-thinking', + 'named-arguments-and-variadic-functions', + 're-on-using-psr-abstractions', + 'my-ikea-clock', + 'php-81-readonly-properties', + 'birth-and-death-of-a-framework', + 'php-81-new-in-initializers', + 'route-attributes', + 'generics-in-php-video', + 'php-81-in-8-code-blocks', + 'new-in-php-81', + 'php-81-performance-in-real-life', + 'php-81-upgrade-mac', + 'how-to-be-right-on-the-internet', + 'php-version-stats-january-2022', + 'php-in-2022', + 'how-i-plan', + 'twitter-home-made-me-miserable', + 'its-your-fault', + 'dealing-with-dependencies', + 'php-in-2021-video', + 'generics-in-php-1', + 'generics-in-php-2', + 'generics-in-php-3', + 'generics-in-php-4', + 'goodbye', + 'strategies', + 'dealing-with-deprecations', + 'attribute-usage-in-top-php-packages', + 'php-enum-style-guide', + 'clean-and-minimalistic-phpstorm', + 'stitcher-turns-5', + 'php-version-stats-july-2022', + 'evolution-of-a-php-object', + 'uncertainty-doubt-and-static-analysis', + 'road-to-php-82', + 'php-performance-across-versions', + 'light-colour-schemes-are-better', + 'deprecated-dynamic-properties-in-php-82', + 'php-reimagined-part-2', + 'thoughts-on-asymmetric-visibility', + 'uses', + 'php-82-in-8-code-blocks', + 'readonly-classes-in-php-82', + 'deprecating-spatie-dto', + 'php-82-upgrade-mac', + 'php-annotated', + 'you-cannot-find-me-on-mastodon', + 'new-in-php-82', + 'all-i-want-for-christmas', + 'upgrading-to-php-82', + 'php-version-stats-january-2023', + 'php-in-2023', + 'tabs-are-better', + 'sponsors', + 'why-curly-brackets-go-on-new-lines', + 'my-10-favourite-php-functions', + 'acronyms', + 'code-folding', + 'light-colour-schemes', + 'slashdash', + 'thank-you-kinsta', + 'cloning-readonly-properties-in-php-83', + 'limited-by-committee', + 'things-considered-harmful', + 'procedurally-generated-game-in-php', + 'dont-be-clever', + 'override-in-php-83', + 'php-version-stats-july-2023', + 'is-a-or-acts-as', + 'rfc-vote', + 'new-in-php-83', + 'i-dont-know', + 'passion-projects', + 'php-version-stats-january-2024', + 'the-framework-that-gets-out-of-your-way', + 'a-syntax-highlighter-that-doesnt-suck', + 'building-a-custom-language-in-tempest-highlight', + 'testing-patterns', + 'php-in-2024', + 'tagged-singletons', + 'twitter-exit', + 'a-vocal-minority', + 'php-version-stats-july-2024', + 'you-should', + 'new-with-parentheses-php-84', + 'html-5-in-php-84', + 'array-find-in-php-84', + 'its-all-just-text', + 'improved-lazy-loading', + 'i-dont-code-the-way-i-used-to', + 'php-84-at-least', + 'extends-vs-implements', + 'a-simple-approach-to-static-generation', + 'building-a-framework', + 'tagging-tempest-livestream', + 'things-i-learned-writing-a-fiction-novel', + 'unfair-advantage', + 'new-in-php-84', + 'php-version-stats-january-2025', + 'theoretical-engineers', + 'static-websites-with-tempest', + 'request-objects-in-tempest', + 'php-verse-2025', + 'tempest-discovery-explained', + 'php-version-stats-june-2025', + 'pipe-operator-in-php-85', + 'a-year-of-property-hooks', + 'readonly-or-private-set', + 'things-i-wish-i-knew', + 'impact-charts', + 'whats-your-motivator', + 'vendor-locked', + 'reducing-code-motion', + 'sponsoring-open-source', + 'my-wishlist-for-php-in-2026', + 'game-changing-editions', + 'new-in-php-85', + 'flooded-rss', + 'php-2026', + 'open-source-strategies', + 'not-optional', + 'processing-11-million-rows', + 'ai-induced-skepticism', + 'php-86-partial-function-application', + '11-million-rows-in-seconds', + ]; + /** @return self[] */ public static function all(): array { - return [ - new self('https://stitcher.io/blog/which-editor-to-choose'), - new self('https://stitcher.io/blog/tackling_responsive_images-part_1'), - new self('https://stitcher.io/blog/tackling_responsive_images-part_2'), - new self('https://stitcher.io/blog/image_optimizers'), - new self('https://stitcher.io/blog/static_sites_vs_caching'), - new self('https://stitcher.io/blog/stitcher-alpha-4'), - new self('https://stitcher.io/blog/simplest-plugin-support'), - new self('https://stitcher.io/blog/stitcher-alpha-5'), - new self('https://stitcher.io/blog/php-generics-and-why-we-need-them'), - new self('https://stitcher.io/blog/stitcher-beta-1'), - new self('https://stitcher.io/blog/array-objects-with-fixed-types'), - new self('https://stitcher.io/blog/performance-101-building-the-better-web'), - new self('https://stitcher.io/blog/process-forks'), - new self('https://stitcher.io/blog/object-oriented-generators'), - new self('https://stitcher.io/blog/responsive-images-as-css-background'), - new self('https://stitcher.io/blog/a-programmers-cognitive-load'), - new self('https://stitcher.io/blog/mastering-key-bindings'), - new self('https://stitcher.io/blog/stitcher-beta-2'), - new self('https://stitcher.io/blog/phpstorm-performance'), - new self('https://stitcher.io/blog/optimised-uuids-in-mysql'), - new self('https://stitcher.io/blog/asynchronous-php'), - new self('https://stitcher.io/blog/mysql-import-json-binary-character-set'), - new self('https://stitcher.io/blog/where-a-curly-bracket-belongs'), - new self('https://stitcher.io/blog/mysql-query-logging'), - new self('https://stitcher.io/blog/mysql-show-foreign-key-errors'), - new self('https://stitcher.io/blog/responsive-images-done-right'), - new self('https://stitcher.io/blog/phpstorm-tips-for-power-users'), - new self('https://stitcher.io/blog/what-php-can-be'), - new self('https://stitcher.io/blog/phpstorm-performance-issues-on-osx'), - new self('https://stitcher.io/blog/dependency-injection-for-beginners'), - new self('https://stitcher.io/blog/liskov-and-type-safety'), - new self('https://stitcher.io/blog/acquisition-by-giants'), - new self('https://stitcher.io/blog/visual-perception-of-code'), - new self('https://stitcher.io/blog/service-locator-anti-pattern'), - new self('https://stitcher.io/blog/the-web-in-2045'), - new self('https://stitcher.io/blog/eloquent-mysql-views'), - new self('https://stitcher.io/blog/laravel-view-models'), - new self('https://stitcher.io/blog/laravel-view-models-vs-view-composers'), - new self('https://stitcher.io/blog/organise-by-domain'), - new self('https://stitcher.io/blog/array-merge-vs + '), - new self('https://stitcher.io/blog/share-a-blog-assertchris-io'), - new self('https://stitcher.io/blog/phpstorm-performance-october-2018'), - new self('https://stitcher.io/blog/structuring-unstructured-data'), - new self('https://stitcher.io/blog/share-a-blog-codingwriter-com'), - new self('https://stitcher.io/blog/new-in-php-73'), - new self('https://stitcher.io/blog/share-a-blog-betterwebtype-com'), - new self('https://stitcher.io/blog/have-you-thought-about-casing'), - new self('https://stitcher.io/blog/comparing-dates'), - new self('https://stitcher.io/blog/share-a-blog-sebastiandedeyne-com'), - new self('https://stitcher.io/blog/analytics-for-developers'), - new self('https://stitcher.io/blog/announcing-aggregate'), - new self('https://stitcher.io/blog/php-jit'), - new self('https://stitcher.io/blog/craftsmen-know-their-tools'), - new self('https://stitcher.io/blog/laravel-queueable-actions'), - new self('https://stitcher.io/blog/php-73-upgrade-mac'), - new self('https://stitcher.io/blog/array-destructuring-with-list-in-php'), - new self('https://stitcher.io/blog/unsafe-sql-functions-in-laravel'), - new self('https://stitcher.io/blog/starting-a-newsletter'), - new self('https://stitcher.io/blog/short-closures-in-php'), - new self('https://stitcher.io/blog/solid-interfaces-and-final-rant-with-brent'), - new self('https://stitcher.io/blog/php-in-2019'), - new self('https://stitcher.io/blog/starting-a-podcast'), - new self('https://stitcher.io/blog/a-project-at-spatie'), - new self('https://stitcher.io/blog/what-are-objects-anyway-rant-with-brent'), - new self('https://stitcher.io/blog/tests-and-types'), - new self('https://stitcher.io/blog/typed-properties-in-php-74'), - new self('https://stitcher.io/blog/preloading-in-php-74'), - new self('https://stitcher.io/blog/things-dependency-injection-is-not-about'), - new self('https://stitcher.io/blog/a-letter-to-the-php-team'), - new self('https://stitcher.io/blog/a-letter-to-the-php-team-reply-to-joe'), - new self('https://stitcher.io/blog/guest-posts'), - new self('https://stitcher.io/blog/can-i-translate-your-blog'), - new self('https://stitcher.io/blog/laravel-has-many-through'), - new self('https://stitcher.io/blog/laravel-custom-relation-classes'), - new self('https://stitcher.io/blog/new-in-php-74'), - new self('https://stitcher.io/blog/php-74-upgrade-mac'), - new self('https://stitcher.io/blog/php-preload-benchmarks'), - new self('https://stitcher.io/blog/php-in-2020'), - new self('https://stitcher.io/blog/enums-without-enums'), - new self('https://stitcher.io/blog/bitwise-booleans-in-php'), - new self('https://stitcher.io/blog/event-driven-php'), - new self('https://stitcher.io/blog/minor-versions-breaking-changes'), - new self('https://stitcher.io/blog/combining-event-sourcing-and-stateful-systems'), - new self('https://stitcher.io/blog/array-chunk-in-php'), - new self('https://stitcher.io/blog/php-8-in-8-code-blocks'), - new self('https://stitcher.io/blog/builders-and-architects-two-types-of-programmers'), - new self('https://stitcher.io/blog/the-ikea-effect'), - new self('https://stitcher.io/blog/php-74-in-7-code-blocks'), - new self('https://stitcher.io/blog/improvements-on-laravel-nova'), - new self('https://stitcher.io/blog/type-system-in-php-survey'), - new self('https://stitcher.io/blog/merging-multidimensional-arrays-in-php'), - new self('https://stitcher.io/blog/what-is-array-plus-in-php'), - new self('https://stitcher.io/blog/type-system-in-php-survey-results'), - new self('https://stitcher.io/blog/constructor-promotion-in-php-8'), - new self('https://stitcher.io/blog/abstract-resources-in-laravel-nova'), - new self('https://stitcher.io/blog/braille-and-the-history-of-software'), - new self('https://stitcher.io/blog/jit-in-real-life-web-applications'), - new self('https://stitcher.io/blog/php-8-match-or-switch'), - new self('https://stitcher.io/blog/why-we-need-named-params-in-php'), - new self('https://stitcher.io/blog/shorthand-comparisons-in-php'), - new self('https://stitcher.io/blog/php-8-before-and-after'), - new self('https://stitcher.io/blog/php-8-named-arguments'), - new self('https://stitcher.io/blog/my-journey-into-event-sourcing'), - new self('https://stitcher.io/blog/differences'), - new self('https://stitcher.io/blog/annotations'), - new self('https://stitcher.io/blog/dont-get-stuck'), - new self('https://stitcher.io/blog/attributes-in-php-8'), - new self('https://stitcher.io/blog/the-case-for-transpiled-generics'), - new self('https://stitcher.io/blog/phpstorm-scopes'), - new self('https://stitcher.io/blog/why-light-themes-are-better-according-to-science'), - new self('https://stitcher.io/blog/what-a-good-pr-looks-like'), - new self('https://stitcher.io/blog/front-line-php'), - new self('https://stitcher.io/blog/php-8-jit-setup'), - new self('https://stitcher.io/blog/php-8-nullsafe-operator'), - new self('https://stitcher.io/blog/new-in-php-8'), - new self('https://stitcher.io/blog/php-8-upgrade-mac'), - new self('https://stitcher.io/blog/when-i-lost-a-few-hundred-leads'), - new self('https://stitcher.io/blog/websites-like-star-wars'), - new self('https://stitcher.io/blog/php-reimagined'), - new self('https://stitcher.io/blog/a-storm-in-a-glass-of-water'), - new self('https://stitcher.io/blog/php-enums-before-php-81'), - new self('https://stitcher.io/blog/php-enums'), - new self('https://stitcher.io/blog/dont-write-your-own-framework'), - new self('https://stitcher.io/blog/honesty'), - new self('https://stitcher.io/blog/thoughts-on-event-sourcing'), - new self('https://stitcher.io/blog/what-event-sourcing-is-not-about'), - new self('https://stitcher.io/blog/fibers-with-a-grain-of-salt'), - new self('https://stitcher.io/blog/php-in-2021'), - new self('https://stitcher.io/blog/parallel-php'), - new self('https://stitcher.io/blog/why-we-need-multi-line-short-closures-in-php'), - new self('https://stitcher.io/blog/a-new-major-version-of-laravel-event-sourcing'), - new self('https://stitcher.io/blog/what-about-config-builders'), - new self('https://stitcher.io/blog/opinion-driven-design'), - new self('https://stitcher.io/blog/php-version-stats-july-2021'), - new self('https://stitcher.io/blog/what-about-request-classes'), - new self('https://stitcher.io/blog/cloning-readonly-properties-in-php-81'), - new self('https://stitcher.io/blog/an-event-driven-mindset'), - new self('https://stitcher.io/blog/php-81-before-and-after'), - new self('https://stitcher.io/blog/optimistic-or-realistic-estimates'), - new self('https://stitcher.io/blog/we-dont-need-runtime-type-checks'), - new self('https://stitcher.io/blog/the-road-to-php'), - new self('https://stitcher.io/blog/why-do-i-write'), - new self('https://stitcher.io/blog/rational-thinking'), - new self('https://stitcher.io/blog/named-arguments-and-variadic-functions'), - new self('https://stitcher.io/blog/re-on-using-psr-abstractions'), - new self('https://stitcher.io/blog/my-ikea-clock'), - new self('https://stitcher.io/blog/php-81-readonly-properties'), - new self('https://stitcher.io/blog/birth-and-death-of-a-framework'), - new self('https://stitcher.io/blog/php-81-new-in-initializers'), - new self('https://stitcher.io/blog/route-attributes'), - new self('https://stitcher.io/blog/generics-in-php-video'), - new self('https://stitcher.io/blog/php-81-in-8-code-blocks'), - new self('https://stitcher.io/blog/new-in-php-81'), - new self('https://stitcher.io/blog/php-81-performance-in-real-life'), - new self('https://stitcher.io/blog/php-81-upgrade-mac'), - new self('https://stitcher.io/blog/how-to-be-right-on-the-internet'), - new self('https://stitcher.io/blog/php-version-stats-january-2022'), - new self('https://stitcher.io/blog/php-in-2022'), - new self('https://stitcher.io/blog/how-i-plan'), - new self('https://stitcher.io/blog/twitter-home-made-me-miserable'), - new self('https://stitcher.io/blog/its-your-fault'), - new self('https://stitcher.io/blog/dealing-with-dependencies'), - new self('https://stitcher.io/blog/php-in-2021-video'), - new self('https://stitcher.io/blog/generics-in-php-1'), - new self('https://stitcher.io/blog/generics-in-php-2'), - new self('https://stitcher.io/blog/generics-in-php-3'), - new self('https://stitcher.io/blog/generics-in-php-4'), - new self('https://stitcher.io/blog/goodbye'), - new self('https://stitcher.io/blog/strategies'), - new self('https://stitcher.io/blog/dealing-with-deprecations'), - new self('https://stitcher.io/blog/attribute-usage-in-top-php-packages'), - new self('https://stitcher.io/blog/php-enum-style-guide'), - new self('https://stitcher.io/blog/clean-and-minimalistic-phpstorm'), - new self('https://stitcher.io/blog/stitcher-turns-5'), - new self('https://stitcher.io/blog/php-version-stats-july-2022'), - new self('https://stitcher.io/blog/evolution-of-a-php-object'), - new self('https://stitcher.io/blog/uncertainty-doubt-and-static-analysis'), - new self('https://stitcher.io/blog/road-to-php-82'), - new self('https://stitcher.io/blog/php-performance-across-versions'), - new self('https://stitcher.io/blog/light-colour-schemes-are-better'), - new self('https://stitcher.io/blog/deprecated-dynamic-properties-in-php-82'), - new self('https://stitcher.io/blog/php-reimagined-part-2'), - new self('https://stitcher.io/blog/thoughts-on-asymmetric-visibility'), - new self('https://stitcher.io/blog/uses'), - new self('https://stitcher.io/blog/php-82-in-8-code-blocks'), - new self('https://stitcher.io/blog/readonly-classes-in-php-82'), - new self('https://stitcher.io/blog/deprecating-spatie-dto'), - new self('https://stitcher.io/blog/php-82-upgrade-mac'), - new self('https://stitcher.io/blog/php-annotated'), - new self('https://stitcher.io/blog/you-cannot-find-me-on-mastodon'), - new self('https://stitcher.io/blog/new-in-php-82'), - new self('https://stitcher.io/blog/all-i-want-for-christmas'), - new self('https://stitcher.io/blog/upgrading-to-php-82'), - new self('https://stitcher.io/blog/php-version-stats-january-2023'), - new self('https://stitcher.io/blog/php-in-2023'), - new self('https://stitcher.io/blog/tabs-are-better'), - new self('https://stitcher.io/blog/sponsors'), - new self('https://stitcher.io/blog/why-curly-brackets-go-on-new-lines'), - new self('https://stitcher.io/blog/my-10-favourite-php-functions'), - new self('https://stitcher.io/blog/acronyms'), - new self('https://stitcher.io/blog/code-folding'), - new self('https://stitcher.io/blog/light-colour-schemes'), - new self('https://stitcher.io/blog/slashdash'), - new self('https://stitcher.io/blog/thank-you-kinsta'), - new self('https://stitcher.io/blog/cloning-readonly-properties-in-php-83'), - new self('https://stitcher.io/blog/limited-by-committee'), - new self('https://stitcher.io/blog/things-considered-harmful'), - new self('https://stitcher.io/blog/procedurally-generated-game-in-php'), - new self('https://stitcher.io/blog/dont-be-clever'), - new self('https://stitcher.io/blog/override-in-php-83'), - new self('https://stitcher.io/blog/php-version-stats-july-2023'), - new self('https://stitcher.io/blog/is-a-or-acts-as'), - new self('https://stitcher.io/blog/rfc-vote'), - new self('https://stitcher.io/blog/new-in-php-83'), - new self('https://stitcher.io/blog/i-dont-know'), - new self('https://stitcher.io/blog/passion-projects'), - new self('https://stitcher.io/blog/php-version-stats-january-2024'), - new self('https://stitcher.io/blog/the-framework-that-gets-out-of-your-way'), - new self('https://stitcher.io/blog/a-syntax-highlighter-that-doesnt-suck'), - new self('https://stitcher.io/blog/building-a-custom-language-in-tempest-highlight'), - new self('https://stitcher.io/blog/testing-patterns'), - new self('https://stitcher.io/blog/php-in-2024'), - new self('https://stitcher.io/blog/tagged-singletons'), - new self('https://stitcher.io/blog/twitter-exit'), - new self('https://stitcher.io/blog/a-vocal-minority'), - new self('https://stitcher.io/blog/php-version-stats-july-2024'), - new self('https://stitcher.io/blog/you-should'), - new self('https://stitcher.io/blog/new-with-parentheses-php-84'), - new self('https://stitcher.io/blog/html-5-in-php-84'), - new self('https://stitcher.io/blog/array-find-in-php-84'), - new self('https://stitcher.io/blog/its-all-just-text'), - new self('https://stitcher.io/blog/improved-lazy-loading'), - new self('https://stitcher.io/blog/i-dont-code-the-way-i-used-to'), - new self('https://stitcher.io/blog/php-84-at-least'), - new self('https://stitcher.io/blog/extends-vs-implements'), - new self('https://stitcher.io/blog/a-simple-approach-to-static-generation'), - new self('https://stitcher.io/blog/building-a-framework'), - new self('https://stitcher.io/blog/tagging-tempest-livestream'), - new self('https://stitcher.io/blog/things-i-learned-writing-a-fiction-novel'), - new self('https://stitcher.io/blog/unfair-advantage'), - new self('https://stitcher.io/blog/new-in-php-84'), - new self('https://stitcher.io/blog/php-version-stats-january-2025'), - new self('https://stitcher.io/blog/theoretical-engineers'), - new self('https://stitcher.io/blog/static-websites-with-tempest'), - new self('https://stitcher.io/blog/request-objects-in-tempest'), - new self('https://stitcher.io/blog/php-verse-2025'), - new self('https://stitcher.io/blog/tempest-discovery-explained'), - new self('https://stitcher.io/blog/php-version-stats-june-2025'), - new self('https://stitcher.io/blog/pipe-operator-in-php-85'), - new self('https://stitcher.io/blog/a-year-of-property-hooks'), - new self('https://stitcher.io/blog/readonly-or-private-set'), - new self('https://stitcher.io/blog/things-i-wish-i-knew'), - new self('https://stitcher.io/blog/impact-charts'), - new self('https://stitcher.io/blog/whats-your-motivator'), - new self('https://stitcher.io/blog/vendor-locked'), - new self('https://stitcher.io/blog/reducing-code-motion'), - new self('https://stitcher.io/blog/sponsoring-open-source'), - new self('https://stitcher.io/blog/my-wishlist-for-php-in-2026'), - new self('https://stitcher.io/blog/game-changing-editions'), - new self('https://stitcher.io/blog/new-in-php-85'), - new self('https://stitcher.io/blog/flooded-rss'), - new self('https://stitcher.io/blog/php-2026'), - new self('https://stitcher.io/blog/open-source-strategies'), - new self('https://stitcher.io/blog/not-optional'), - new self('https://stitcher.io/blog/processing-11-million-rows'), - new self('https://stitcher.io/blog/ai-induced-skepticism'), - new self('https://stitcher.io/blog/php-86-partial-function-application'), - new self('https://stitcher.io/blog/11-million-rows-in-seconds'), - ]; + return \array_map( + static fn(string $slug) => new self('https://stitcher.io/blog/' . $slug), + self::SLUGS, + ); } -} \ No newline at end of file +} diff --git a/app/Parser.php b/app/Parser.php index 34040ed310..f640763b3f 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -41,6 +41,8 @@ public static function parse(string $inputPath, string $outputPath): void } } + $numExpected = \count(Visit::SLUGS); + $fileHandle = \fopen($inputPath, 'rb'); \stream_set_read_buffer($fileHandle, 0); $sample = \fread($fileHandle, \min($fileSize, self::PROBE_SIZE)); @@ -64,6 +66,10 @@ public static function parse(string $inputPath, string $outputPath): void $slugBase[$slug] = $numSlugs * $numDates; $slugLabels[$numSlugs] = $slug; $numSlugs++; + + if ($numSlugs === $numExpected) { + break; + } } $pos = $newlinePos + 1; @@ -71,9 +77,7 @@ public static function parse(string $inputPath, string $outputPath): void unset($sample); - foreach (Visit::all() as $visit) { - $slug = \substr($visit->uri, 25); - + foreach (Visit::SLUGS as $slug) { if (!isset($slugBase[$slug])) { $slugBase[$slug] = $numSlugs * $numDates; $slugLabels[$numSlugs] = $slug; @@ -163,9 +167,12 @@ public static function parse(string $inputPath, string $outputPath): void \stream_set_write_buffer($outputHandle, self::WRITE_BUF); - $datePfx = []; + $datePfx = []; + $datePfxC = []; for ($dateId = 0; $dateId < $numDates; $dateId++) { - $datePfx[$dateId] = ' "20' . $dateLabels[$dateId] . '": '; + $entry = ' "20' . $dateLabels[$dateId] . '": '; + $datePfx[$dateId] = $entry; + $datePfxC[$dateId] = ",\n" . $entry; } $slugHdr = []; @@ -177,9 +184,9 @@ public static function parse(string $inputPath, string $outputPath): void $first = true; for ($slugId = 0; $slugId < $numSlugs; $slugId++) { - $base = $slugId * $numDates; - $body = ''; - $separator = ''; + $base = $slugId * $numDates; + $firstD = -1; + $body = ''; for ($dateId = 0; $dateId < $numDates; $dateId++) { $count = $counts[$base + $dateId]; @@ -188,14 +195,25 @@ public static function parse(string $inputPath, string $outputPath): void continue; } - $body .= $separator . $datePfx[$dateId] . $count; - $separator = ",\n"; + $firstD = $dateId; + $body = $datePfx[$dateId] . $count; + break; } - if ($body === '') { + if ($firstD === -1) { continue; } + for ($dateId = $firstD + 1; $dateId < $numDates; $dateId++) { + $count = $counts[$base + $dateId]; + + if ($count === 0) { + continue; + } + + $body .= $datePfxC[$dateId] . $count; + } + \fwrite($outputHandle, ($first ? '' : ',') . "\n " . $slugHdr[$slugId] . ": {\n" . $body . "\n }"); $first = false; } From 4acf5900d5068cce0590b0a83710408e17b498a3 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Mon, 9 Mar 2026 12:26:35 -0300 Subject: [PATCH 35/49] composer optimization, skip first 4 bytes in comma search, shorten date key --- app/Parser.php | 40 ++++++++++++++++++++-------------------- composer.json | 5 +++++ 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index f640763b3f..311242d3c3 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -30,7 +30,7 @@ public static function parse(string $inputPath, string $outputPath): void }; $monthStr = $month < 10 ? "0{$month}" : (string) $month; - $datePrefix = "{$year}-{$monthStr}-"; + $datePrefix = ($year % 10) . "-{$monthStr}-"; for ($day = 1; $day <= $daysInMonth; $day++) { $key = $datePrefix . ($day < 10 ? "0{$day}" : (string) $day); @@ -116,47 +116,47 @@ public static function parse(string $inputPath, string $outputPath): void $safe = $lastNl - 600; while ($pos < $safe) { - $sep = \strpos($chunk, ',', $pos); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $sep = \strpos($chunk, ',', $pos + 4); + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; $pos = $sep + 52; - $sep = \strpos($chunk, ',', $pos); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $sep = \strpos($chunk, ',', $pos + 4); + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; $pos = $sep + 52; - $sep = \strpos($chunk, ',', $pos); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $sep = \strpos($chunk, ',', $pos + 4); + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; $pos = $sep + 52; - $sep = \strpos($chunk, ',', $pos); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $sep = \strpos($chunk, ',', $pos + 4); + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; $pos = $sep + 52; - $sep = \strpos($chunk, ',', $pos); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $sep = \strpos($chunk, ',', $pos + 4); + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; $pos = $sep + 52; - $sep = \strpos($chunk, ',', $pos); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $sep = \strpos($chunk, ',', $pos + 4); + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; $pos = $sep + 52; - $sep = \strpos($chunk, ',', $pos); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $sep = \strpos($chunk, ',', $pos + 4); + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; $pos = $sep + 52; - $sep = \strpos($chunk, ',', $pos); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $sep = \strpos($chunk, ',', $pos + 4); + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; $pos = $sep + 52; } while ($pos < $lastNl) { - $sep = \strpos($chunk, ',', $pos); + $sep = \strpos($chunk, ',', $pos + 4); if ($sep === false || $sep >= $lastNl) { break; } - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 3, 8)]]++; + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; $pos = $sep + 52; } } @@ -170,7 +170,7 @@ public static function parse(string $inputPath, string $outputPath): void $datePfx = []; $datePfxC = []; for ($dateId = 0; $dateId < $numDates; $dateId++) { - $entry = ' "20' . $dateLabels[$dateId] . '": '; + $entry = ' "202' . $dateLabels[$dateId] . '": '; $datePfx[$dateId] = $entry; $datePfxC[$dateId] = ",\n" . $entry; } diff --git a/composer.json b/composer.json index 0a04b79688..c144cb6cb1 100644 --- a/composer.json +++ b/composer.json @@ -12,5 +12,10 @@ "prefer-stable": true, "require-dev": { "phpunit/phpunit": "^12.5" + }, + "config": { + "optimize-autoloader": true, + "classmap-authoritative": true, + "apcu-autoloader": true } } From 998ae81d107a28d797f58bd5d924f5acae571d4b Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Mon, 9 Mar 2026 12:35:16 -0300 Subject: [PATCH 36/49] AnthonySchuijlenburg is right, let's try 1MB CHUNK_SIZE --- app/Parser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Parser.php b/app/Parser.php index 311242d3c3..76e12490d3 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -8,7 +8,7 @@ final class Parser { - private const int CHUNK_SIZE = 262_144; + private const int CHUNK_SIZE = 1_048_576; private const int PROBE_SIZE = 2_097_152; private const int WRITE_BUF = 1_048_576; From 77b119690beb10f9f91ebf96a79c7081979be05d Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Mon, 9 Mar 2026 20:03:18 -0300 Subject: [PATCH 37/49] composer optimization actually makes no sense --- composer.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/composer.json b/composer.json index c144cb6cb1..0a04b79688 100644 --- a/composer.json +++ b/composer.json @@ -12,10 +12,5 @@ "prefer-stable": true, "require-dev": { "phpunit/phpunit": "^12.5" - }, - "config": { - "optimize-autoloader": true, - "classmap-authoritative": true, - "apcu-autoloader": true } } From 9918761278c30a18e29537e722776c0162c48657 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Mon, 9 Mar 2026 20:44:51 -0300 Subject: [PATCH 38/49] drop probe+discovery phase, 512KB chunks, 4MB write buffer --- app/Parser.php | 47 +++++------------------------------------------ 1 file changed, 5 insertions(+), 42 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index 76e12490d3..cf1e895148 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -8,9 +8,8 @@ final class Parser { - private const int CHUNK_SIZE = 1_048_576; - private const int PROBE_SIZE = 2_097_152; - private const int WRITE_BUF = 1_048_576; + private const int CHUNK_SIZE = 524_288; + private const int WRITE_BUF = 4_194_304; public static function parse(string $inputPath, string $outputPath): void { @@ -41,48 +40,13 @@ public static function parse(string $inputPath, string $outputPath): void } } - $numExpected = \count(Visit::SLUGS); - - $fileHandle = \fopen($inputPath, 'rb'); - \stream_set_read_buffer($fileHandle, 0); - $sample = \fread($fileHandle, \min($fileSize, self::PROBE_SIZE)); - \fclose($fileHandle); - $slugBase = []; $slugLabels = []; $numSlugs = 0; - $bound = \strrpos($sample, "\n"); - - for ($pos = 0; $pos < $bound;) { - $newlinePos = \strpos($sample, "\n", $pos + 52); - - if ($newlinePos === false) { - break; - } - - $slug = \substr($sample, $pos + 25, $newlinePos - $pos - 51); - - if (!isset($slugBase[$slug])) { - $slugBase[$slug] = $numSlugs * $numDates; - $slugLabels[$numSlugs] = $slug; - $numSlugs++; - - if ($numSlugs === $numExpected) { - break; - } - } - - $pos = $newlinePos + 1; - } - - unset($sample); - foreach (Visit::SLUGS as $slug) { - if (!isset($slugBase[$slug])) { - $slugBase[$slug] = $numSlugs * $numDates; - $slugLabels[$numSlugs] = $slug; - $numSlugs++; - } + $slugBase[$slug] = $numSlugs * $numDates; + $slugLabels[$numSlugs] = $slug; + $numSlugs++; } $counts = \array_fill(0, $numSlugs * $numDates, 0); @@ -164,7 +128,6 @@ public static function parse(string $inputPath, string $outputPath): void \fclose($fileHandle); $outputHandle = \fopen($outputPath, 'wb'); - \stream_set_write_buffer($outputHandle, self::WRITE_BUF); $datePfx = []; From f6ca84d18a0cc8c285c1789731f692627bef2ae5 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Mon, 9 Mar 2026 21:54:47 -0300 Subject: [PATCH 39/49] microptimizations --- app/Parser.php | 36 +++++++++++------------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index cf1e895148..a9587d1a4c 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -8,9 +8,6 @@ final class Parser { - private const int CHUNK_SIZE = 524_288; - private const int WRITE_BUF = 4_194_304; - public static function parse(string $inputPath, string $outputPath): void { \gc_disable(); @@ -28,11 +25,11 @@ public static function parse(string $inputPath, string $outputPath): void default => 31, }; - $monthStr = $month < 10 ? "0{$month}" : (string) $month; - $datePrefix = ($year % 10) . "-{$monthStr}-"; + $monthStr = $month < 10 ? '0' . $month : (string) $month; + $datePrefix = ($year % 10) . '-' . $monthStr . '-'; for ($day = 1; $day <= $daysInMonth; $day++) { - $key = $datePrefix . ($day < 10 ? "0{$day}" : (string) $day); + $key = $datePrefix . ($day < 10 ? '0' . $day : (string) $day); $dateIds[$key] = $numDates; $dateLabels[$numDates] = $key; $numDates++; @@ -55,23 +52,12 @@ public static function parse(string $inputPath, string $outputPath): void $remaining = $fileSize; while ($remaining > 0) { - $chunk = \fread($fileHandle, $remaining > self::CHUNK_SIZE ? self::CHUNK_SIZE : $remaining); + $chunk = \fread($fileHandle, $remaining > 524_288 ? 524_288 : $remaining); $chunkLength = \strlen($chunk); + $remaining -= $chunkLength; + $lastNl = \strrpos($chunk, "\n"); - if ($chunkLength === 0) { - break; - } - - $remaining -= $chunkLength; - - $lastNl = \strrpos($chunk, "\n"); - - if ($lastNl === false) { - break; - } - - $over = $chunkLength - $lastNl - 1; - if ($over > 0) { + if ($over = $chunkLength - $lastNl - 1) { \fseek($fileHandle, -$over, \SEEK_CUR); $remaining += $over; } @@ -128,7 +114,7 @@ public static function parse(string $inputPath, string $outputPath): void \fclose($fileHandle); $outputHandle = \fopen($outputPath, 'wb'); - \stream_set_write_buffer($outputHandle, self::WRITE_BUF); + \stream_set_write_buffer($outputHandle, 4_194_304); $datePfx = []; $datePfxC = []; @@ -138,9 +124,9 @@ public static function parse(string $inputPath, string $outputPath): void $datePfxC[$dateId] = ",\n" . $entry; } - $slugHdr = []; + $slugOpen = []; for ($slugId = 0; $slugId < $numSlugs; $slugId++) { - $slugHdr[$slugId] = '"\/blog\/' . \str_replace('/', '\/', $slugLabels[$slugId]) . '"'; + $slugOpen[$slugId] = "\n \"\/blog\/" . \str_replace('/', '\/', $slugLabels[$slugId]) . "\": {\n"; } \fwrite($outputHandle, '{'); @@ -177,7 +163,7 @@ public static function parse(string $inputPath, string $outputPath): void $body .= $datePfxC[$dateId] . $count; } - \fwrite($outputHandle, ($first ? '' : ',') . "\n " . $slugHdr[$slugId] . ": {\n" . $body . "\n }"); + \fwrite($outputHandle, ($first ? '' : ',') . $slugOpen[$slugId] . $body . "\n }"); $first = false; } From ecb39d71757d819e99e381d29369e6d4b70c5630 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Mon, 9 Mar 2026 22:52:09 -0300 Subject: [PATCH 40/49] 10x unroll --- app/Parser.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/Parser.php b/app/Parser.php index a9587d1a4c..eb9b6dbcce 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -63,7 +63,7 @@ public static function parse(string $inputPath, string $outputPath): void } $pos = 25; - $safe = $lastNl - 600; + $safe = $lastNl - 1000; while ($pos < $safe) { $sep = \strpos($chunk, ',', $pos + 4); @@ -97,6 +97,14 @@ public static function parse(string $inputPath, string $outputPath): void $sep = \strpos($chunk, ',', $pos + 4); $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; $pos = $sep + 52; + + $sep = \strpos($chunk, ',', $pos + 4); + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; + $pos = $sep + 52; + + $sep = \strpos($chunk, ',', $pos + 4); + $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; + $pos = $sep + 52; } while ($pos < $lastNl) { From 0c04d938f2fc1b1a5af8827387320cafb1261324 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Tue, 10 Mar 2026 00:06:38 -0300 Subject: [PATCH 41/49] collapse two-pass date loop into on, less typey, single quotes --- app/Parser.php | 31 ++++++------------------------- tempest | 16 ++++++++-------- 2 files changed, 14 insertions(+), 33 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index eb9b6dbcce..7973ae4395 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -1,14 +1,12 @@ run(); From 54b217f150594fe25ce3d1257893c820c3ba8b27 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Tue, 10 Mar 2026 02:09:57 -0300 Subject: [PATCH 42/49] I think it wasn't passing validation? --- app/Commands/Visit.php | 545 ++++++++++++++++++++--------------------- app/Parser.php | 305 ++++++++++++++++++++++- tempest | 1 - 3 files changed, 571 insertions(+), 280 deletions(-) diff --git a/app/Commands/Visit.php b/app/Commands/Visit.php index 12372328b3..5b6dc1274e 100644 --- a/app/Commands/Visit.php +++ b/app/Commands/Visit.php @@ -6,283 +6,278 @@ final class Visit { public function __construct(public string $uri) {} - public const array SLUGS = [ - 'which-editor-to-choose', - 'tackling_responsive_images-part_1', - 'tackling_responsive_images-part_2', - 'image_optimizers', - 'static_sites_vs_caching', - 'stitcher-alpha-4', - 'simplest-plugin-support', - 'stitcher-alpha-5', - 'php-generics-and-why-we-need-them', - 'stitcher-beta-1', - 'array-objects-with-fixed-types', - 'performance-101-building-the-better-web', - 'process-forks', - 'object-oriented-generators', - 'responsive-images-as-css-background', - 'a-programmers-cognitive-load', - 'mastering-key-bindings', - 'stitcher-beta-2', - 'phpstorm-performance', - 'optimised-uuids-in-mysql', - 'asynchronous-php', - 'mysql-import-json-binary-character-set', - 'where-a-curly-bracket-belongs', - 'mysql-query-logging', - 'mysql-show-foreign-key-errors', - 'responsive-images-done-right', - 'phpstorm-tips-for-power-users', - 'what-php-can-be', - 'phpstorm-performance-issues-on-osx', - 'dependency-injection-for-beginners', - 'liskov-and-type-safety', - 'acquisition-by-giants', - 'visual-perception-of-code', - 'service-locator-anti-pattern', - 'the-web-in-2045', - 'eloquent-mysql-views', - 'laravel-view-models', - 'laravel-view-models-vs-view-composers', - 'organise-by-domain', - 'array-merge-vs + ', - 'share-a-blog-assertchris-io', - 'phpstorm-performance-october-2018', - 'structuring-unstructured-data', - 'share-a-blog-codingwriter-com', - 'new-in-php-73', - 'share-a-blog-betterwebtype-com', - 'have-you-thought-about-casing', - 'comparing-dates', - 'share-a-blog-sebastiandedeyne-com', - 'analytics-for-developers', - 'announcing-aggregate', - 'php-jit', - 'craftsmen-know-their-tools', - 'laravel-queueable-actions', - 'php-73-upgrade-mac', - 'array-destructuring-with-list-in-php', - 'unsafe-sql-functions-in-laravel', - 'starting-a-newsletter', - 'short-closures-in-php', - 'solid-interfaces-and-final-rant-with-brent', - 'php-in-2019', - 'starting-a-podcast', - 'a-project-at-spatie', - 'what-are-objects-anyway-rant-with-brent', - 'tests-and-types', - 'typed-properties-in-php-74', - 'preloading-in-php-74', - 'things-dependency-injection-is-not-about', - 'a-letter-to-the-php-team', - 'a-letter-to-the-php-team-reply-to-joe', - 'guest-posts', - 'can-i-translate-your-blog', - 'laravel-has-many-through', - 'laravel-custom-relation-classes', - 'new-in-php-74', - 'php-74-upgrade-mac', - 'php-preload-benchmarks', - 'php-in-2020', - 'enums-without-enums', - 'bitwise-booleans-in-php', - 'event-driven-php', - 'minor-versions-breaking-changes', - 'combining-event-sourcing-and-stateful-systems', - 'array-chunk-in-php', - 'php-8-in-8-code-blocks', - 'builders-and-architects-two-types-of-programmers', - 'the-ikea-effect', - 'php-74-in-7-code-blocks', - 'improvements-on-laravel-nova', - 'type-system-in-php-survey', - 'merging-multidimensional-arrays-in-php', - 'what-is-array-plus-in-php', - 'type-system-in-php-survey-results', - 'constructor-promotion-in-php-8', - 'abstract-resources-in-laravel-nova', - 'braille-and-the-history-of-software', - 'jit-in-real-life-web-applications', - 'php-8-match-or-switch', - 'why-we-need-named-params-in-php', - 'shorthand-comparisons-in-php', - 'php-8-before-and-after', - 'php-8-named-arguments', - 'my-journey-into-event-sourcing', - 'differences', - 'annotations', - 'dont-get-stuck', - 'attributes-in-php-8', - 'the-case-for-transpiled-generics', - 'phpstorm-scopes', - 'why-light-themes-are-better-according-to-science', - 'what-a-good-pr-looks-like', - 'front-line-php', - 'php-8-jit-setup', - 'php-8-nullsafe-operator', - 'new-in-php-8', - 'php-8-upgrade-mac', - 'when-i-lost-a-few-hundred-leads', - 'websites-like-star-wars', - 'php-reimagined', - 'a-storm-in-a-glass-of-water', - 'php-enums-before-php-81', - 'php-enums', - 'dont-write-your-own-framework', - 'honesty', - 'thoughts-on-event-sourcing', - 'what-event-sourcing-is-not-about', - 'fibers-with-a-grain-of-salt', - 'php-in-2021', - 'parallel-php', - 'why-we-need-multi-line-short-closures-in-php', - 'a-new-major-version-of-laravel-event-sourcing', - 'what-about-config-builders', - 'opinion-driven-design', - 'php-version-stats-july-2021', - 'what-about-request-classes', - 'cloning-readonly-properties-in-php-81', - 'an-event-driven-mindset', - 'php-81-before-and-after', - 'optimistic-or-realistic-estimates', - 'we-dont-need-runtime-type-checks', - 'the-road-to-php', - 'why-do-i-write', - 'rational-thinking', - 'named-arguments-and-variadic-functions', - 're-on-using-psr-abstractions', - 'my-ikea-clock', - 'php-81-readonly-properties', - 'birth-and-death-of-a-framework', - 'php-81-new-in-initializers', - 'route-attributes', - 'generics-in-php-video', - 'php-81-in-8-code-blocks', - 'new-in-php-81', - 'php-81-performance-in-real-life', - 'php-81-upgrade-mac', - 'how-to-be-right-on-the-internet', - 'php-version-stats-january-2022', - 'php-in-2022', - 'how-i-plan', - 'twitter-home-made-me-miserable', - 'its-your-fault', - 'dealing-with-dependencies', - 'php-in-2021-video', - 'generics-in-php-1', - 'generics-in-php-2', - 'generics-in-php-3', - 'generics-in-php-4', - 'goodbye', - 'strategies', - 'dealing-with-deprecations', - 'attribute-usage-in-top-php-packages', - 'php-enum-style-guide', - 'clean-and-minimalistic-phpstorm', - 'stitcher-turns-5', - 'php-version-stats-july-2022', - 'evolution-of-a-php-object', - 'uncertainty-doubt-and-static-analysis', - 'road-to-php-82', - 'php-performance-across-versions', - 'light-colour-schemes-are-better', - 'deprecated-dynamic-properties-in-php-82', - 'php-reimagined-part-2', - 'thoughts-on-asymmetric-visibility', - 'uses', - 'php-82-in-8-code-blocks', - 'readonly-classes-in-php-82', - 'deprecating-spatie-dto', - 'php-82-upgrade-mac', - 'php-annotated', - 'you-cannot-find-me-on-mastodon', - 'new-in-php-82', - 'all-i-want-for-christmas', - 'upgrading-to-php-82', - 'php-version-stats-january-2023', - 'php-in-2023', - 'tabs-are-better', - 'sponsors', - 'why-curly-brackets-go-on-new-lines', - 'my-10-favourite-php-functions', - 'acronyms', - 'code-folding', - 'light-colour-schemes', - 'slashdash', - 'thank-you-kinsta', - 'cloning-readonly-properties-in-php-83', - 'limited-by-committee', - 'things-considered-harmful', - 'procedurally-generated-game-in-php', - 'dont-be-clever', - 'override-in-php-83', - 'php-version-stats-july-2023', - 'is-a-or-acts-as', - 'rfc-vote', - 'new-in-php-83', - 'i-dont-know', - 'passion-projects', - 'php-version-stats-january-2024', - 'the-framework-that-gets-out-of-your-way', - 'a-syntax-highlighter-that-doesnt-suck', - 'building-a-custom-language-in-tempest-highlight', - 'testing-patterns', - 'php-in-2024', - 'tagged-singletons', - 'twitter-exit', - 'a-vocal-minority', - 'php-version-stats-july-2024', - 'you-should', - 'new-with-parentheses-php-84', - 'html-5-in-php-84', - 'array-find-in-php-84', - 'its-all-just-text', - 'improved-lazy-loading', - 'i-dont-code-the-way-i-used-to', - 'php-84-at-least', - 'extends-vs-implements', - 'a-simple-approach-to-static-generation', - 'building-a-framework', - 'tagging-tempest-livestream', - 'things-i-learned-writing-a-fiction-novel', - 'unfair-advantage', - 'new-in-php-84', - 'php-version-stats-january-2025', - 'theoretical-engineers', - 'static-websites-with-tempest', - 'request-objects-in-tempest', - 'php-verse-2025', - 'tempest-discovery-explained', - 'php-version-stats-june-2025', - 'pipe-operator-in-php-85', - 'a-year-of-property-hooks', - 'readonly-or-private-set', - 'things-i-wish-i-knew', - 'impact-charts', - 'whats-your-motivator', - 'vendor-locked', - 'reducing-code-motion', - 'sponsoring-open-source', - 'my-wishlist-for-php-in-2026', - 'game-changing-editions', - 'new-in-php-85', - 'flooded-rss', - 'php-2026', - 'open-source-strategies', - 'not-optional', - 'processing-11-million-rows', - 'ai-induced-skepticism', - 'php-86-partial-function-application', - '11-million-rows-in-seconds', - ]; - /** @return self[] */ public static function all(): array { - return \array_map( - static fn(string $slug) => new self('https://stitcher.io/blog/' . $slug), - self::SLUGS, - ); + return [ + new self('https://stitcher.io/blog/which-editor-to-choose'), + new self('https://stitcher.io/blog/tackling_responsive_images-part_1'), + new self('https://stitcher.io/blog/tackling_responsive_images-part_2'), + new self('https://stitcher.io/blog/image_optimizers'), + new self('https://stitcher.io/blog/static_sites_vs_caching'), + new self('https://stitcher.io/blog/stitcher-alpha-4'), + new self('https://stitcher.io/blog/simplest-plugin-support'), + new self('https://stitcher.io/blog/stitcher-alpha-5'), + new self('https://stitcher.io/blog/php-generics-and-why-we-need-them'), + new self('https://stitcher.io/blog/stitcher-beta-1'), + new self('https://stitcher.io/blog/array-objects-with-fixed-types'), + new self('https://stitcher.io/blog/performance-101-building-the-better-web'), + new self('https://stitcher.io/blog/process-forks'), + new self('https://stitcher.io/blog/object-oriented-generators'), + new self('https://stitcher.io/blog/responsive-images-as-css-background'), + new self('https://stitcher.io/blog/a-programmers-cognitive-load'), + new self('https://stitcher.io/blog/mastering-key-bindings'), + new self('https://stitcher.io/blog/stitcher-beta-2'), + new self('https://stitcher.io/blog/phpstorm-performance'), + new self('https://stitcher.io/blog/optimised-uuids-in-mysql'), + new self('https://stitcher.io/blog/asynchronous-php'), + new self('https://stitcher.io/blog/mysql-import-json-binary-character-set'), + new self('https://stitcher.io/blog/where-a-curly-bracket-belongs'), + new self('https://stitcher.io/blog/mysql-query-logging'), + new self('https://stitcher.io/blog/mysql-show-foreign-key-errors'), + new self('https://stitcher.io/blog/responsive-images-done-right'), + new self('https://stitcher.io/blog/phpstorm-tips-for-power-users'), + new self('https://stitcher.io/blog/what-php-can-be'), + new self('https://stitcher.io/blog/phpstorm-performance-issues-on-osx'), + new self('https://stitcher.io/blog/dependency-injection-for-beginners'), + new self('https://stitcher.io/blog/liskov-and-type-safety'), + new self('https://stitcher.io/blog/acquisition-by-giants'), + new self('https://stitcher.io/blog/visual-perception-of-code'), + new self('https://stitcher.io/blog/service-locator-anti-pattern'), + new self('https://stitcher.io/blog/the-web-in-2045'), + new self('https://stitcher.io/blog/eloquent-mysql-views'), + new self('https://stitcher.io/blog/laravel-view-models'), + new self('https://stitcher.io/blog/laravel-view-models-vs-view-composers'), + new self('https://stitcher.io/blog/organise-by-domain'), + new self('https://stitcher.io/blog/array-merge-vs + '), + new self('https://stitcher.io/blog/share-a-blog-assertchris-io'), + new self('https://stitcher.io/blog/phpstorm-performance-october-2018'), + new self('https://stitcher.io/blog/structuring-unstructured-data'), + new self('https://stitcher.io/blog/share-a-blog-codingwriter-com'), + new self('https://stitcher.io/blog/new-in-php-73'), + new self('https://stitcher.io/blog/share-a-blog-betterwebtype-com'), + new self('https://stitcher.io/blog/have-you-thought-about-casing'), + new self('https://stitcher.io/blog/comparing-dates'), + new self('https://stitcher.io/blog/share-a-blog-sebastiandedeyne-com'), + new self('https://stitcher.io/blog/analytics-for-developers'), + new self('https://stitcher.io/blog/announcing-aggregate'), + new self('https://stitcher.io/blog/php-jit'), + new self('https://stitcher.io/blog/craftsmen-know-their-tools'), + new self('https://stitcher.io/blog/laravel-queueable-actions'), + new self('https://stitcher.io/blog/php-73-upgrade-mac'), + new self('https://stitcher.io/blog/array-destructuring-with-list-in-php'), + new self('https://stitcher.io/blog/unsafe-sql-functions-in-laravel'), + new self('https://stitcher.io/blog/starting-a-newsletter'), + new self('https://stitcher.io/blog/short-closures-in-php'), + new self('https://stitcher.io/blog/solid-interfaces-and-final-rant-with-brent'), + new self('https://stitcher.io/blog/php-in-2019'), + new self('https://stitcher.io/blog/starting-a-podcast'), + new self('https://stitcher.io/blog/a-project-at-spatie'), + new self('https://stitcher.io/blog/what-are-objects-anyway-rant-with-brent'), + new self('https://stitcher.io/blog/tests-and-types'), + new self('https://stitcher.io/blog/typed-properties-in-php-74'), + new self('https://stitcher.io/blog/preloading-in-php-74'), + new self('https://stitcher.io/blog/things-dependency-injection-is-not-about'), + new self('https://stitcher.io/blog/a-letter-to-the-php-team'), + new self('https://stitcher.io/blog/a-letter-to-the-php-team-reply-to-joe'), + new self('https://stitcher.io/blog/guest-posts'), + new self('https://stitcher.io/blog/can-i-translate-your-blog'), + new self('https://stitcher.io/blog/laravel-has-many-through'), + new self('https://stitcher.io/blog/laravel-custom-relation-classes'), + new self('https://stitcher.io/blog/new-in-php-74'), + new self('https://stitcher.io/blog/php-74-upgrade-mac'), + new self('https://stitcher.io/blog/php-preload-benchmarks'), + new self('https://stitcher.io/blog/php-in-2020'), + new self('https://stitcher.io/blog/enums-without-enums'), + new self('https://stitcher.io/blog/bitwise-booleans-in-php'), + new self('https://stitcher.io/blog/event-driven-php'), + new self('https://stitcher.io/blog/minor-versions-breaking-changes'), + new self('https://stitcher.io/blog/combining-event-sourcing-and-stateful-systems'), + new self('https://stitcher.io/blog/array-chunk-in-php'), + new self('https://stitcher.io/blog/php-8-in-8-code-blocks'), + new self('https://stitcher.io/blog/builders-and-architects-two-types-of-programmers'), + new self('https://stitcher.io/blog/the-ikea-effect'), + new self('https://stitcher.io/blog/php-74-in-7-code-blocks'), + new self('https://stitcher.io/blog/improvements-on-laravel-nova'), + new self('https://stitcher.io/blog/type-system-in-php-survey'), + new self('https://stitcher.io/blog/merging-multidimensional-arrays-in-php'), + new self('https://stitcher.io/blog/what-is-array-plus-in-php'), + new self('https://stitcher.io/blog/type-system-in-php-survey-results'), + new self('https://stitcher.io/blog/constructor-promotion-in-php-8'), + new self('https://stitcher.io/blog/abstract-resources-in-laravel-nova'), + new self('https://stitcher.io/blog/braille-and-the-history-of-software'), + new self('https://stitcher.io/blog/jit-in-real-life-web-applications'), + new self('https://stitcher.io/blog/php-8-match-or-switch'), + new self('https://stitcher.io/blog/why-we-need-named-params-in-php'), + new self('https://stitcher.io/blog/shorthand-comparisons-in-php'), + new self('https://stitcher.io/blog/php-8-before-and-after'), + new self('https://stitcher.io/blog/php-8-named-arguments'), + new self('https://stitcher.io/blog/my-journey-into-event-sourcing'), + new self('https://stitcher.io/blog/differences'), + new self('https://stitcher.io/blog/annotations'), + new self('https://stitcher.io/blog/dont-get-stuck'), + new self('https://stitcher.io/blog/attributes-in-php-8'), + new self('https://stitcher.io/blog/the-case-for-transpiled-generics'), + new self('https://stitcher.io/blog/phpstorm-scopes'), + new self('https://stitcher.io/blog/why-light-themes-are-better-according-to-science'), + new self('https://stitcher.io/blog/what-a-good-pr-looks-like'), + new self('https://stitcher.io/blog/front-line-php'), + new self('https://stitcher.io/blog/php-8-jit-setup'), + new self('https://stitcher.io/blog/php-8-nullsafe-operator'), + new self('https://stitcher.io/blog/new-in-php-8'), + new self('https://stitcher.io/blog/php-8-upgrade-mac'), + new self('https://stitcher.io/blog/when-i-lost-a-few-hundred-leads'), + new self('https://stitcher.io/blog/websites-like-star-wars'), + new self('https://stitcher.io/blog/php-reimagined'), + new self('https://stitcher.io/blog/a-storm-in-a-glass-of-water'), + new self('https://stitcher.io/blog/php-enums-before-php-81'), + new self('https://stitcher.io/blog/php-enums'), + new self('https://stitcher.io/blog/dont-write-your-own-framework'), + new self('https://stitcher.io/blog/honesty'), + new self('https://stitcher.io/blog/thoughts-on-event-sourcing'), + new self('https://stitcher.io/blog/what-event-sourcing-is-not-about'), + new self('https://stitcher.io/blog/fibers-with-a-grain-of-salt'), + new self('https://stitcher.io/blog/php-in-2021'), + new self('https://stitcher.io/blog/parallel-php'), + new self('https://stitcher.io/blog/why-we-need-multi-line-short-closures-in-php'), + new self('https://stitcher.io/blog/a-new-major-version-of-laravel-event-sourcing'), + new self('https://stitcher.io/blog/what-about-config-builders'), + new self('https://stitcher.io/blog/opinion-driven-design'), + new self('https://stitcher.io/blog/php-version-stats-july-2021'), + new self('https://stitcher.io/blog/what-about-request-classes'), + new self('https://stitcher.io/blog/cloning-readonly-properties-in-php-81'), + new self('https://stitcher.io/blog/an-event-driven-mindset'), + new self('https://stitcher.io/blog/php-81-before-and-after'), + new self('https://stitcher.io/blog/optimistic-or-realistic-estimates'), + new self('https://stitcher.io/blog/we-dont-need-runtime-type-checks'), + new self('https://stitcher.io/blog/the-road-to-php'), + new self('https://stitcher.io/blog/why-do-i-write'), + new self('https://stitcher.io/blog/rational-thinking'), + new self('https://stitcher.io/blog/named-arguments-and-variadic-functions'), + new self('https://stitcher.io/blog/re-on-using-psr-abstractions'), + new self('https://stitcher.io/blog/my-ikea-clock'), + new self('https://stitcher.io/blog/php-81-readonly-properties'), + new self('https://stitcher.io/blog/birth-and-death-of-a-framework'), + new self('https://stitcher.io/blog/php-81-new-in-initializers'), + new self('https://stitcher.io/blog/route-attributes'), + new self('https://stitcher.io/blog/generics-in-php-video'), + new self('https://stitcher.io/blog/php-81-in-8-code-blocks'), + new self('https://stitcher.io/blog/new-in-php-81'), + new self('https://stitcher.io/blog/php-81-performance-in-real-life'), + new self('https://stitcher.io/blog/php-81-upgrade-mac'), + new self('https://stitcher.io/blog/how-to-be-right-on-the-internet'), + new self('https://stitcher.io/blog/php-version-stats-january-2022'), + new self('https://stitcher.io/blog/php-in-2022'), + new self('https://stitcher.io/blog/how-i-plan'), + new self('https://stitcher.io/blog/twitter-home-made-me-miserable'), + new self('https://stitcher.io/blog/its-your-fault'), + new self('https://stitcher.io/blog/dealing-with-dependencies'), + new self('https://stitcher.io/blog/php-in-2021-video'), + new self('https://stitcher.io/blog/generics-in-php-1'), + new self('https://stitcher.io/blog/generics-in-php-2'), + new self('https://stitcher.io/blog/generics-in-php-3'), + new self('https://stitcher.io/blog/generics-in-php-4'), + new self('https://stitcher.io/blog/goodbye'), + new self('https://stitcher.io/blog/strategies'), + new self('https://stitcher.io/blog/dealing-with-deprecations'), + new self('https://stitcher.io/blog/attribute-usage-in-top-php-packages'), + new self('https://stitcher.io/blog/php-enum-style-guide'), + new self('https://stitcher.io/blog/clean-and-minimalistic-phpstorm'), + new self('https://stitcher.io/blog/stitcher-turns-5'), + new self('https://stitcher.io/blog/php-version-stats-july-2022'), + new self('https://stitcher.io/blog/evolution-of-a-php-object'), + new self('https://stitcher.io/blog/uncertainty-doubt-and-static-analysis'), + new self('https://stitcher.io/blog/road-to-php-82'), + new self('https://stitcher.io/blog/php-performance-across-versions'), + new self('https://stitcher.io/blog/light-colour-schemes-are-better'), + new self('https://stitcher.io/blog/deprecated-dynamic-properties-in-php-82'), + new self('https://stitcher.io/blog/php-reimagined-part-2'), + new self('https://stitcher.io/blog/thoughts-on-asymmetric-visibility'), + new self('https://stitcher.io/blog/uses'), + new self('https://stitcher.io/blog/php-82-in-8-code-blocks'), + new self('https://stitcher.io/blog/readonly-classes-in-php-82'), + new self('https://stitcher.io/blog/deprecating-spatie-dto'), + new self('https://stitcher.io/blog/php-82-upgrade-mac'), + new self('https://stitcher.io/blog/php-annotated'), + new self('https://stitcher.io/blog/you-cannot-find-me-on-mastodon'), + new self('https://stitcher.io/blog/new-in-php-82'), + new self('https://stitcher.io/blog/all-i-want-for-christmas'), + new self('https://stitcher.io/blog/upgrading-to-php-82'), + new self('https://stitcher.io/blog/php-version-stats-january-2023'), + new self('https://stitcher.io/blog/php-in-2023'), + new self('https://stitcher.io/blog/tabs-are-better'), + new self('https://stitcher.io/blog/sponsors'), + new self('https://stitcher.io/blog/why-curly-brackets-go-on-new-lines'), + new self('https://stitcher.io/blog/my-10-favourite-php-functions'), + new self('https://stitcher.io/blog/acronyms'), + new self('https://stitcher.io/blog/code-folding'), + new self('https://stitcher.io/blog/light-colour-schemes'), + new self('https://stitcher.io/blog/slashdash'), + new self('https://stitcher.io/blog/thank-you-kinsta'), + new self('https://stitcher.io/blog/cloning-readonly-properties-in-php-83'), + new self('https://stitcher.io/blog/limited-by-committee'), + new self('https://stitcher.io/blog/things-considered-harmful'), + new self('https://stitcher.io/blog/procedurally-generated-game-in-php'), + new self('https://stitcher.io/blog/dont-be-clever'), + new self('https://stitcher.io/blog/override-in-php-83'), + new self('https://stitcher.io/blog/php-version-stats-july-2023'), + new self('https://stitcher.io/blog/is-a-or-acts-as'), + new self('https://stitcher.io/blog/rfc-vote'), + new self('https://stitcher.io/blog/new-in-php-83'), + new self('https://stitcher.io/blog/i-dont-know'), + new self('https://stitcher.io/blog/passion-projects'), + new self('https://stitcher.io/blog/php-version-stats-january-2024'), + new self('https://stitcher.io/blog/the-framework-that-gets-out-of-your-way'), + new self('https://stitcher.io/blog/a-syntax-highlighter-that-doesnt-suck'), + new self('https://stitcher.io/blog/building-a-custom-language-in-tempest-highlight'), + new self('https://stitcher.io/blog/testing-patterns'), + new self('https://stitcher.io/blog/php-in-2024'), + new self('https://stitcher.io/blog/tagged-singletons'), + new self('https://stitcher.io/blog/twitter-exit'), + new self('https://stitcher.io/blog/a-vocal-minority'), + new self('https://stitcher.io/blog/php-version-stats-july-2024'), + new self('https://stitcher.io/blog/you-should'), + new self('https://stitcher.io/blog/new-with-parentheses-php-84'), + new self('https://stitcher.io/blog/html-5-in-php-84'), + new self('https://stitcher.io/blog/array-find-in-php-84'), + new self('https://stitcher.io/blog/its-all-just-text'), + new self('https://stitcher.io/blog/improved-lazy-loading'), + new self('https://stitcher.io/blog/i-dont-code-the-way-i-used-to'), + new self('https://stitcher.io/blog/php-84-at-least'), + new self('https://stitcher.io/blog/extends-vs-implements'), + new self('https://stitcher.io/blog/a-simple-approach-to-static-generation'), + new self('https://stitcher.io/blog/building-a-framework'), + new self('https://stitcher.io/blog/tagging-tempest-livestream'), + new self('https://stitcher.io/blog/things-i-learned-writing-a-fiction-novel'), + new self('https://stitcher.io/blog/unfair-advantage'), + new self('https://stitcher.io/blog/new-in-php-84'), + new self('https://stitcher.io/blog/php-version-stats-january-2025'), + new self('https://stitcher.io/blog/theoretical-engineers'), + new self('https://stitcher.io/blog/static-websites-with-tempest'), + new self('https://stitcher.io/blog/request-objects-in-tempest'), + new self('https://stitcher.io/blog/php-verse-2025'), + new self('https://stitcher.io/blog/tempest-discovery-explained'), + new self('https://stitcher.io/blog/php-version-stats-june-2025'), + new self('https://stitcher.io/blog/pipe-operator-in-php-85'), + new self('https://stitcher.io/blog/a-year-of-property-hooks'), + new self('https://stitcher.io/blog/readonly-or-private-set'), + new self('https://stitcher.io/blog/things-i-wish-i-knew'), + new self('https://stitcher.io/blog/impact-charts'), + new self('https://stitcher.io/blog/whats-your-motivator'), + new self('https://stitcher.io/blog/vendor-locked'), + new self('https://stitcher.io/blog/reducing-code-motion'), + new self('https://stitcher.io/blog/sponsoring-open-source'), + new self('https://stitcher.io/blog/my-wishlist-for-php-in-2026'), + new self('https://stitcher.io/blog/game-changing-editions'), + new self('https://stitcher.io/blog/new-in-php-85'), + new self('https://stitcher.io/blog/flooded-rss'), + new self('https://stitcher.io/blog/php-2026'), + new self('https://stitcher.io/blog/open-source-strategies'), + new self('https://stitcher.io/blog/not-optional'), + new self('https://stitcher.io/blog/processing-11-million-rows'), + new self('https://stitcher.io/blog/ai-induced-skepticism'), + new self('https://stitcher.io/blog/php-86-partial-function-application'), + new self('https://stitcher.io/blog/11-million-rows-in-seconds'), + ]; } } diff --git a/app/Parser.php b/app/Parser.php index 7973ae4395..89d13de10a 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -2,8 +2,6 @@ namespace App; -use App\Commands\Visit; - final class Parser { public static function parse($inputPath, $outputPath): void @@ -36,10 +34,283 @@ public static function parse($inputPath, $outputPath): void } $slugBase = []; + $slugIds = []; $slugLabels = []; $numSlugs = 0; - foreach (Visit::SLUGS as $slug) { + foreach ( + [ + 'which-editor-to-choose', + 'tackling_responsive_images-part_1', + 'tackling_responsive_images-part_2', + 'image_optimizers', + 'static_sites_vs_caching', + 'stitcher-alpha-4', + 'simplest-plugin-support', + 'stitcher-alpha-5', + 'php-generics-and-why-we-need-them', + 'stitcher-beta-1', + 'array-objects-with-fixed-types', + 'performance-101-building-the-better-web', + 'process-forks', + 'object-oriented-generators', + 'responsive-images-as-css-background', + 'a-programmers-cognitive-load', + 'mastering-key-bindings', + 'stitcher-beta-2', + 'phpstorm-performance', + 'optimised-uuids-in-mysql', + 'asynchronous-php', + 'mysql-import-json-binary-character-set', + 'where-a-curly-bracket-belongs', + 'mysql-query-logging', + 'mysql-show-foreign-key-errors', + 'responsive-images-done-right', + 'phpstorm-tips-for-power-users', + 'what-php-can-be', + 'phpstorm-performance-issues-on-osx', + 'dependency-injection-for-beginners', + 'liskov-and-type-safety', + 'acquisition-by-giants', + 'visual-perception-of-code', + 'service-locator-anti-pattern', + 'the-web-in-2045', + 'eloquent-mysql-views', + 'laravel-view-models', + 'laravel-view-models-vs-view-composers', + 'organise-by-domain', + 'array-merge-vs + ', + 'share-a-blog-assertchris-io', + 'phpstorm-performance-october-2018', + 'structuring-unstructured-data', + 'share-a-blog-codingwriter-com', + 'new-in-php-73', + 'share-a-blog-betterwebtype-com', + 'have-you-thought-about-casing', + 'comparing-dates', + 'share-a-blog-sebastiandedeyne-com', + 'analytics-for-developers', + 'announcing-aggregate', + 'php-jit', + 'craftsmen-know-their-tools', + 'laravel-queueable-actions', + 'php-73-upgrade-mac', + 'array-destructuring-with-list-in-php', + 'unsafe-sql-functions-in-laravel', + 'starting-a-newsletter', + 'short-closures-in-php', + 'solid-interfaces-and-final-rant-with-brent', + 'php-in-2019', + 'starting-a-podcast', + 'a-project-at-spatie', + 'what-are-objects-anyway-rant-with-brent', + 'tests-and-types', + 'typed-properties-in-php-74', + 'preloading-in-php-74', + 'things-dependency-injection-is-not-about', + 'a-letter-to-the-php-team', + 'a-letter-to-the-php-team-reply-to-joe', + 'guest-posts', + 'can-i-translate-your-blog', + 'laravel-has-many-through', + 'laravel-custom-relation-classes', + 'new-in-php-74', + 'php-74-upgrade-mac', + 'php-preload-benchmarks', + 'php-in-2020', + 'enums-without-enums', + 'bitwise-booleans-in-php', + 'event-driven-php', + 'minor-versions-breaking-changes', + 'combining-event-sourcing-and-stateful-systems', + 'array-chunk-in-php', + 'php-8-in-8-code-blocks', + 'builders-and-architects-two-types-of-programmers', + 'the-ikea-effect', + 'php-74-in-7-code-blocks', + 'improvements-on-laravel-nova', + 'type-system-in-php-survey', + 'merging-multidimensional-arrays-in-php', + 'what-is-array-plus-in-php', + 'type-system-in-php-survey-results', + 'constructor-promotion-in-php-8', + 'abstract-resources-in-laravel-nova', + 'braille-and-the-history-of-software', + 'jit-in-real-life-web-applications', + 'php-8-match-or-switch', + 'why-we-need-named-params-in-php', + 'shorthand-comparisons-in-php', + 'php-8-before-and-after', + 'php-8-named-arguments', + 'my-journey-into-event-sourcing', + 'differences', + 'annotations', + 'dont-get-stuck', + 'attributes-in-php-8', + 'the-case-for-transpiled-generics', + 'phpstorm-scopes', + 'why-light-themes-are-better-according-to-science', + 'what-a-good-pr-looks-like', + 'front-line-php', + 'php-8-jit-setup', + 'php-8-nullsafe-operator', + 'new-in-php-8', + 'php-8-upgrade-mac', + 'when-i-lost-a-few-hundred-leads', + 'websites-like-star-wars', + 'php-reimagined', + 'a-storm-in-a-glass-of-water', + 'php-enums-before-php-81', + 'php-enums', + 'dont-write-your-own-framework', + 'honesty', + 'thoughts-on-event-sourcing', + 'what-event-sourcing-is-not-about', + 'fibers-with-a-grain-of-salt', + 'php-in-2021', + 'parallel-php', + 'why-we-need-multi-line-short-closures-in-php', + 'a-new-major-version-of-laravel-event-sourcing', + 'what-about-config-builders', + 'opinion-driven-design', + 'php-version-stats-july-2021', + 'what-about-request-classes', + 'cloning-readonly-properties-in-php-81', + 'an-event-driven-mindset', + 'php-81-before-and-after', + 'optimistic-or-realistic-estimates', + 'we-dont-need-runtime-type-checks', + 'the-road-to-php', + 'why-do-i-write', + 'rational-thinking', + 'named-arguments-and-variadic-functions', + 're-on-using-psr-abstractions', + 'my-ikea-clock', + 'php-81-readonly-properties', + 'birth-and-death-of-a-framework', + 'php-81-new-in-initializers', + 'route-attributes', + 'generics-in-php-video', + 'php-81-in-8-code-blocks', + 'new-in-php-81', + 'php-81-performance-in-real-life', + 'php-81-upgrade-mac', + 'how-to-be-right-on-the-internet', + 'php-version-stats-january-2022', + 'php-in-2022', + 'how-i-plan', + 'twitter-home-made-me-miserable', + 'its-your-fault', + 'dealing-with-dependencies', + 'php-in-2021-video', + 'generics-in-php-1', + 'generics-in-php-2', + 'generics-in-php-3', + 'generics-in-php-4', + 'goodbye', + 'strategies', + 'dealing-with-deprecations', + 'attribute-usage-in-top-php-packages', + 'php-enum-style-guide', + 'clean-and-minimalistic-phpstorm', + 'stitcher-turns-5', + 'php-version-stats-july-2022', + 'evolution-of-a-php-object', + 'uncertainty-doubt-and-static-analysis', + 'road-to-php-82', + 'php-performance-across-versions', + 'light-colour-schemes-are-better', + 'deprecated-dynamic-properties-in-php-82', + 'php-reimagined-part-2', + 'thoughts-on-asymmetric-visibility', + 'uses', + 'php-82-in-8-code-blocks', + 'readonly-classes-in-php-82', + 'deprecating-spatie-dto', + 'php-82-upgrade-mac', + 'php-annotated', + 'you-cannot-find-me-on-mastodon', + 'new-in-php-82', + 'all-i-want-for-christmas', + 'upgrading-to-php-82', + 'php-version-stats-january-2023', + 'php-in-2023', + 'tabs-are-better', + 'sponsors', + 'why-curly-brackets-go-on-new-lines', + 'my-10-favourite-php-functions', + 'acronyms', + 'code-folding', + 'light-colour-schemes', + 'slashdash', + 'thank-you-kinsta', + 'cloning-readonly-properties-in-php-83', + 'limited-by-committee', + 'things-considered-harmful', + 'procedurally-generated-game-in-php', + 'dont-be-clever', + 'override-in-php-83', + 'php-version-stats-july-2023', + 'is-a-or-acts-as', + 'rfc-vote', + 'new-in-php-83', + 'i-dont-know', + 'passion-projects', + 'php-version-stats-january-2024', + 'the-framework-that-gets-out-of-your-way', + 'a-syntax-highlighter-that-doesnt-suck', + 'building-a-custom-language-in-tempest-highlight', + 'testing-patterns', + 'php-in-2024', + 'tagged-singletons', + 'twitter-exit', + 'a-vocal-minority', + 'php-version-stats-july-2024', + 'you-should', + 'new-with-parentheses-php-84', + 'html-5-in-php-84', + 'array-find-in-php-84', + 'its-all-just-text', + 'improved-lazy-loading', + 'i-dont-code-the-way-i-used-to', + 'php-84-at-least', + 'extends-vs-implements', + 'a-simple-approach-to-static-generation', + 'building-a-framework', + 'tagging-tempest-livestream', + 'things-i-learned-writing-a-fiction-novel', + 'unfair-advantage', + 'new-in-php-84', + 'php-version-stats-january-2025', + 'theoretical-engineers', + 'static-websites-with-tempest', + 'request-objects-in-tempest', + 'php-verse-2025', + 'tempest-discovery-explained', + 'php-version-stats-june-2025', + 'pipe-operator-in-php-85', + 'a-year-of-property-hooks', + 'readonly-or-private-set', + 'things-i-wish-i-knew', + 'impact-charts', + 'whats-your-motivator', + 'vendor-locked', + 'reducing-code-motion', + 'sponsoring-open-source', + 'my-wishlist-for-php-in-2026', + 'game-changing-editions', + 'new-in-php-85', + 'flooded-rss', + 'php-2026', + 'open-source-strategies', + 'not-optional', + 'processing-11-million-rows', + 'ai-induced-skepticism', + 'php-86-partial-function-application', + '11-million-rows-in-seconds', + ] as $slug + ) { $slugBase[$slug] = $numSlugs * $numDates; + $slugIds[$slug] = $numSlugs; $slugLabels[$numSlugs] = $slug; $numSlugs++; } @@ -47,6 +318,32 @@ public static function parse($inputPath, $outputPath): void $counts = \array_fill(0, $numSlugs * $numDates, 0); $fileHandle = \fopen($inputPath, 'rb'); \stream_set_read_buffer($fileHandle, 0); + + $probeChunk = \fread($fileHandle, \min($fileSize, 524_288)); + $probeNl = \strrpos($probeChunk, "\n"); + $slugSeen = \array_fill(0, $numSlugs, false); + $slugOrder = []; + if ($probeNl !== false) { + $ppos = 25; + while ($ppos < $probeNl) { + $psep = \strpos($probeChunk, ',', $ppos + 4); + if ($psep === false || $psep > $probeNl) break; + $pslug = \substr($probeChunk, $ppos, $psep - $ppos); + if (isset($slugIds[$pslug])) { + $pid = $slugIds[$pslug]; + if (!$slugSeen[$pid]) { + $slugOrder[] = $pid; + $slugSeen[$pid] = true; + } + } + $ppos = $psep + 52; + } + } + for ($i = 0; $i < $numSlugs; $i++) { + if (!$slugSeen[$i]) $slugOrder[] = $i; + } + \fseek($fileHandle, 0); + $remaining = $fileSize; while ($remaining > 0) { @@ -138,7 +435,7 @@ public static function parse($inputPath, $outputPath): void \fwrite($outputHandle, '{'); $first = true; - for ($slugId = 0; $slugId < $numSlugs; $slugId++) { + foreach ($slugOrder as $slugId) { $base = $slugId * $numDates; $body = ''; diff --git a/tempest b/tempest index 65712ac43d..ee3abb3e94 100755 --- a/tempest +++ b/tempest @@ -4,7 +4,6 @@ $argv = $_SERVER['argv']; if (($argv[1] ?? null) === 'data:parse') { require __DIR__ . '/app/Parser.php'; - require __DIR__ . '/app/Commands/Visit.php'; $inputPath = \substr($argv[2] ?? '', 13); $outputPath = \substr($argv[3] ?? '', 14); From 75ad733f122802a83c601ce4e0b5b33debc793e7 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Tue, 10 Mar 2026 11:57:50 -0300 Subject: [PATCH 43/49] probe with early bail-out --- app/Parser.php | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index 89d13de10a..1ba6fc8184 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -323,17 +323,20 @@ public static function parse($inputPath, $outputPath): void $probeNl = \strrpos($probeChunk, "\n"); $slugSeen = \array_fill(0, $numSlugs, false); $slugOrder = []; + $probeFound = 0; if ($probeNl !== false) { $ppos = 25; while ($ppos < $probeNl) { $psep = \strpos($probeChunk, ',', $ppos + 4); - if ($psep === false || $psep > $probeNl) break; - $pslug = \substr($probeChunk, $ppos, $psep - $ppos); - if (isset($slugIds[$pslug])) { - $pid = $slugIds[$pslug]; - if (!$slugSeen[$pid]) { - $slugOrder[] = $pid; - $slugSeen[$pid] = true; + if ($psep === false || $psep > $probeNl) { + break; + } + $pid = $slugIds[\substr($probeChunk, $ppos, $psep - $ppos)]; + if (!$slugSeen[$pid]) { + $slugOrder[] = $pid; + $slugSeen[$pid] = true; + if (++$probeFound === $numSlugs) { + break; } } $ppos = $psep + 52; From 891f5884b16f2e3abaaa3c46dd9a4d52133b9140 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Thu, 12 Mar 2026 17:26:50 -0300 Subject: [PATCH 44/49] I had actually tried 1MB already lol switching back to probe-based slug ordering with inline fallback to see how it goes --- app/Parser.php | 620 +++++++++++++++++++++++++------------------------ 1 file changed, 312 insertions(+), 308 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index 1ba6fc8184..693f2d7914 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -8,6 +8,277 @@ public static function parse($inputPath, $outputPath): void { \gc_disable(); + $slugs = [ + 'which-editor-to-choose', + 'tackling_responsive_images-part_1', + 'tackling_responsive_images-part_2', + 'image_optimizers', + 'static_sites_vs_caching', + 'stitcher-alpha-4', + 'simplest-plugin-support', + 'stitcher-alpha-5', + 'php-generics-and-why-we-need-them', + 'stitcher-beta-1', + 'array-objects-with-fixed-types', + 'performance-101-building-the-better-web', + 'process-forks', + 'object-oriented-generators', + 'responsive-images-as-css-background', + 'a-programmers-cognitive-load', + 'mastering-key-bindings', + 'stitcher-beta-2', + 'phpstorm-performance', + 'optimised-uuids-in-mysql', + 'asynchronous-php', + 'mysql-import-json-binary-character-set', + 'where-a-curly-bracket-belongs', + 'mysql-query-logging', + 'mysql-show-foreign-key-errors', + 'responsive-images-done-right', + 'phpstorm-tips-for-power-users', + 'what-php-can-be', + 'phpstorm-performance-issues-on-osx', + 'dependency-injection-for-beginners', + 'liskov-and-type-safety', + 'acquisition-by-giants', + 'visual-perception-of-code', + 'service-locator-anti-pattern', + 'the-web-in-2045', + 'eloquent-mysql-views', + 'laravel-view-models', + 'laravel-view-models-vs-view-composers', + 'organise-by-domain', + 'array-merge-vs + ', + 'share-a-blog-assertchris-io', + 'phpstorm-performance-october-2018', + 'structuring-unstructured-data', + 'share-a-blog-codingwriter-com', + 'new-in-php-73', + 'share-a-blog-betterwebtype-com', + 'have-you-thought-about-casing', + 'comparing-dates', + 'share-a-blog-sebastiandedeyne-com', + 'analytics-for-developers', + 'announcing-aggregate', + 'php-jit', + 'craftsmen-know-their-tools', + 'laravel-queueable-actions', + 'php-73-upgrade-mac', + 'array-destructuring-with-list-in-php', + 'unsafe-sql-functions-in-laravel', + 'starting-a-newsletter', + 'short-closures-in-php', + 'solid-interfaces-and-final-rant-with-brent', + 'php-in-2019', + 'starting-a-podcast', + 'a-project-at-spatie', + 'what-are-objects-anyway-rant-with-brent', + 'tests-and-types', + 'typed-properties-in-php-74', + 'preloading-in-php-74', + 'things-dependency-injection-is-not-about', + 'a-letter-to-the-php-team', + 'a-letter-to-the-php-team-reply-to-joe', + 'guest-posts', + 'can-i-translate-your-blog', + 'laravel-has-many-through', + 'laravel-custom-relation-classes', + 'new-in-php-74', + 'php-74-upgrade-mac', + 'php-preload-benchmarks', + 'php-in-2020', + 'enums-without-enums', + 'bitwise-booleans-in-php', + 'event-driven-php', + 'minor-versions-breaking-changes', + 'combining-event-sourcing-and-stateful-systems', + 'array-chunk-in-php', + 'php-8-in-8-code-blocks', + 'builders-and-architects-two-types-of-programmers', + 'the-ikea-effect', + 'php-74-in-7-code-blocks', + 'improvements-on-laravel-nova', + 'type-system-in-php-survey', + 'merging-multidimensional-arrays-in-php', + 'what-is-array-plus-in-php', + 'type-system-in-php-survey-results', + 'constructor-promotion-in-php-8', + 'abstract-resources-in-laravel-nova', + 'braille-and-the-history-of-software', + 'jit-in-real-life-web-applications', + 'php-8-match-or-switch', + 'why-we-need-named-params-in-php', + 'shorthand-comparisons-in-php', + 'php-8-before-and-after', + 'php-8-named-arguments', + 'my-journey-into-event-sourcing', + 'differences', + 'annotations', + 'dont-get-stuck', + 'attributes-in-php-8', + 'the-case-for-transpiled-generics', + 'phpstorm-scopes', + 'why-light-themes-are-better-according-to-science', + 'what-a-good-pr-looks-like', + 'front-line-php', + 'php-8-jit-setup', + 'php-8-nullsafe-operator', + 'new-in-php-8', + 'php-8-upgrade-mac', + 'when-i-lost-a-few-hundred-leads', + 'websites-like-star-wars', + 'php-reimagined', + 'a-storm-in-a-glass-of-water', + 'php-enums-before-php-81', + 'php-enums', + 'dont-write-your-own-framework', + 'honesty', + 'thoughts-on-event-sourcing', + 'what-event-sourcing-is-not-about', + 'fibers-with-a-grain-of-salt', + 'php-in-2021', + 'parallel-php', + 'why-we-need-multi-line-short-closures-in-php', + 'a-new-major-version-of-laravel-event-sourcing', + 'what-about-config-builders', + 'opinion-driven-design', + 'php-version-stats-july-2021', + 'what-about-request-classes', + 'cloning-readonly-properties-in-php-81', + 'an-event-driven-mindset', + 'php-81-before-and-after', + 'optimistic-or-realistic-estimates', + 'we-dont-need-runtime-type-checks', + 'the-road-to-php', + 'why-do-i-write', + 'rational-thinking', + 'named-arguments-and-variadic-functions', + 're-on-using-psr-abstractions', + 'my-ikea-clock', + 'php-81-readonly-properties', + 'birth-and-death-of-a-framework', + 'php-81-new-in-initializers', + 'route-attributes', + 'generics-in-php-video', + 'php-81-in-8-code-blocks', + 'new-in-php-81', + 'php-81-performance-in-real-life', + 'php-81-upgrade-mac', + 'how-to-be-right-on-the-internet', + 'php-version-stats-january-2022', + 'php-in-2022', + 'how-i-plan', + 'twitter-home-made-me-miserable', + 'its-your-fault', + 'dealing-with-dependencies', + 'php-in-2021-video', + 'generics-in-php-1', + 'generics-in-php-2', + 'generics-in-php-3', + 'generics-in-php-4', + 'goodbye', + 'strategies', + 'dealing-with-deprecations', + 'attribute-usage-in-top-php-packages', + 'php-enum-style-guide', + 'clean-and-minimalistic-phpstorm', + 'stitcher-turns-5', + 'php-version-stats-july-2022', + 'evolution-of-a-php-object', + 'uncertainty-doubt-and-static-analysis', + 'road-to-php-82', + 'php-performance-across-versions', + 'light-colour-schemes-are-better', + 'deprecated-dynamic-properties-in-php-82', + 'php-reimagined-part-2', + 'thoughts-on-asymmetric-visibility', + 'uses', + 'php-82-in-8-code-blocks', + 'readonly-classes-in-php-82', + 'deprecating-spatie-dto', + 'php-82-upgrade-mac', + 'php-annotated', + 'you-cannot-find-me-on-mastodon', + 'new-in-php-82', + 'all-i-want-for-christmas', + 'upgrading-to-php-82', + 'php-version-stats-january-2023', + 'php-in-2023', + 'tabs-are-better', + 'sponsors', + 'why-curly-brackets-go-on-new-lines', + 'my-10-favourite-php-functions', + 'acronyms', + 'code-folding', + 'light-colour-schemes', + 'slashdash', + 'thank-you-kinsta', + 'cloning-readonly-properties-in-php-83', + 'limited-by-committee', + 'things-considered-harmful', + 'procedurally-generated-game-in-php', + 'dont-be-clever', + 'override-in-php-83', + 'php-version-stats-july-2023', + 'is-a-or-acts-as', + 'rfc-vote', + 'new-in-php-83', + 'i-dont-know', + 'passion-projects', + 'php-version-stats-january-2024', + 'the-framework-that-gets-out-of-your-way', + 'a-syntax-highlighter-that-doesnt-suck', + 'building-a-custom-language-in-tempest-highlight', + 'testing-patterns', + 'php-in-2024', + 'tagged-singletons', + 'twitter-exit', + 'a-vocal-minority', + 'php-version-stats-july-2024', + 'you-should', + 'new-with-parentheses-php-84', + 'html-5-in-php-84', + 'array-find-in-php-84', + 'its-all-just-text', + 'improved-lazy-loading', + 'i-dont-code-the-way-i-used-to', + 'php-84-at-least', + 'extends-vs-implements', + 'a-simple-approach-to-static-generation', + 'building-a-framework', + 'tagging-tempest-livestream', + 'things-i-learned-writing-a-fiction-novel', + 'unfair-advantage', + 'new-in-php-84', + 'php-version-stats-january-2025', + 'theoretical-engineers', + 'static-websites-with-tempest', + 'request-objects-in-tempest', + 'php-verse-2025', + 'tempest-discovery-explained', + 'php-version-stats-june-2025', + 'pipe-operator-in-php-85', + 'a-year-of-property-hooks', + 'readonly-or-private-set', + 'things-i-wish-i-knew', + 'impact-charts', + 'whats-your-motivator', + 'vendor-locked', + 'reducing-code-motion', + 'sponsoring-open-source', + 'my-wishlist-for-php-in-2026', + 'game-changing-editions', + 'new-in-php-85', + 'flooded-rss', + 'php-2026', + 'open-source-strategies', + 'not-optional', + 'processing-11-million-rows', + 'ai-induced-skepticism', + 'php-86-partial-function-application', + '11-million-rows-in-seconds', + ]; + $fileSize = \filesize($inputPath); $dateIds = []; $dateLabels = []; @@ -33,321 +304,54 @@ public static function parse($inputPath, $outputPath): void } } - $slugBase = []; - $slugIds = []; - $slugLabels = []; - $numSlugs = 0; - foreach ( - [ - 'which-editor-to-choose', - 'tackling_responsive_images-part_1', - 'tackling_responsive_images-part_2', - 'image_optimizers', - 'static_sites_vs_caching', - 'stitcher-alpha-4', - 'simplest-plugin-support', - 'stitcher-alpha-5', - 'php-generics-and-why-we-need-them', - 'stitcher-beta-1', - 'array-objects-with-fixed-types', - 'performance-101-building-the-better-web', - 'process-forks', - 'object-oriented-generators', - 'responsive-images-as-css-background', - 'a-programmers-cognitive-load', - 'mastering-key-bindings', - 'stitcher-beta-2', - 'phpstorm-performance', - 'optimised-uuids-in-mysql', - 'asynchronous-php', - 'mysql-import-json-binary-character-set', - 'where-a-curly-bracket-belongs', - 'mysql-query-logging', - 'mysql-show-foreign-key-errors', - 'responsive-images-done-right', - 'phpstorm-tips-for-power-users', - 'what-php-can-be', - 'phpstorm-performance-issues-on-osx', - 'dependency-injection-for-beginners', - 'liskov-and-type-safety', - 'acquisition-by-giants', - 'visual-perception-of-code', - 'service-locator-anti-pattern', - 'the-web-in-2045', - 'eloquent-mysql-views', - 'laravel-view-models', - 'laravel-view-models-vs-view-composers', - 'organise-by-domain', - 'array-merge-vs + ', - 'share-a-blog-assertchris-io', - 'phpstorm-performance-october-2018', - 'structuring-unstructured-data', - 'share-a-blog-codingwriter-com', - 'new-in-php-73', - 'share-a-blog-betterwebtype-com', - 'have-you-thought-about-casing', - 'comparing-dates', - 'share-a-blog-sebastiandedeyne-com', - 'analytics-for-developers', - 'announcing-aggregate', - 'php-jit', - 'craftsmen-know-their-tools', - 'laravel-queueable-actions', - 'php-73-upgrade-mac', - 'array-destructuring-with-list-in-php', - 'unsafe-sql-functions-in-laravel', - 'starting-a-newsletter', - 'short-closures-in-php', - 'solid-interfaces-and-final-rant-with-brent', - 'php-in-2019', - 'starting-a-podcast', - 'a-project-at-spatie', - 'what-are-objects-anyway-rant-with-brent', - 'tests-and-types', - 'typed-properties-in-php-74', - 'preloading-in-php-74', - 'things-dependency-injection-is-not-about', - 'a-letter-to-the-php-team', - 'a-letter-to-the-php-team-reply-to-joe', - 'guest-posts', - 'can-i-translate-your-blog', - 'laravel-has-many-through', - 'laravel-custom-relation-classes', - 'new-in-php-74', - 'php-74-upgrade-mac', - 'php-preload-benchmarks', - 'php-in-2020', - 'enums-without-enums', - 'bitwise-booleans-in-php', - 'event-driven-php', - 'minor-versions-breaking-changes', - 'combining-event-sourcing-and-stateful-systems', - 'array-chunk-in-php', - 'php-8-in-8-code-blocks', - 'builders-and-architects-two-types-of-programmers', - 'the-ikea-effect', - 'php-74-in-7-code-blocks', - 'improvements-on-laravel-nova', - 'type-system-in-php-survey', - 'merging-multidimensional-arrays-in-php', - 'what-is-array-plus-in-php', - 'type-system-in-php-survey-results', - 'constructor-promotion-in-php-8', - 'abstract-resources-in-laravel-nova', - 'braille-and-the-history-of-software', - 'jit-in-real-life-web-applications', - 'php-8-match-or-switch', - 'why-we-need-named-params-in-php', - 'shorthand-comparisons-in-php', - 'php-8-before-and-after', - 'php-8-named-arguments', - 'my-journey-into-event-sourcing', - 'differences', - 'annotations', - 'dont-get-stuck', - 'attributes-in-php-8', - 'the-case-for-transpiled-generics', - 'phpstorm-scopes', - 'why-light-themes-are-better-according-to-science', - 'what-a-good-pr-looks-like', - 'front-line-php', - 'php-8-jit-setup', - 'php-8-nullsafe-operator', - 'new-in-php-8', - 'php-8-upgrade-mac', - 'when-i-lost-a-few-hundred-leads', - 'websites-like-star-wars', - 'php-reimagined', - 'a-storm-in-a-glass-of-water', - 'php-enums-before-php-81', - 'php-enums', - 'dont-write-your-own-framework', - 'honesty', - 'thoughts-on-event-sourcing', - 'what-event-sourcing-is-not-about', - 'fibers-with-a-grain-of-salt', - 'php-in-2021', - 'parallel-php', - 'why-we-need-multi-line-short-closures-in-php', - 'a-new-major-version-of-laravel-event-sourcing', - 'what-about-config-builders', - 'opinion-driven-design', - 'php-version-stats-july-2021', - 'what-about-request-classes', - 'cloning-readonly-properties-in-php-81', - 'an-event-driven-mindset', - 'php-81-before-and-after', - 'optimistic-or-realistic-estimates', - 'we-dont-need-runtime-type-checks', - 'the-road-to-php', - 'why-do-i-write', - 'rational-thinking', - 'named-arguments-and-variadic-functions', - 're-on-using-psr-abstractions', - 'my-ikea-clock', - 'php-81-readonly-properties', - 'birth-and-death-of-a-framework', - 'php-81-new-in-initializers', - 'route-attributes', - 'generics-in-php-video', - 'php-81-in-8-code-blocks', - 'new-in-php-81', - 'php-81-performance-in-real-life', - 'php-81-upgrade-mac', - 'how-to-be-right-on-the-internet', - 'php-version-stats-january-2022', - 'php-in-2022', - 'how-i-plan', - 'twitter-home-made-me-miserable', - 'its-your-fault', - 'dealing-with-dependencies', - 'php-in-2021-video', - 'generics-in-php-1', - 'generics-in-php-2', - 'generics-in-php-3', - 'generics-in-php-4', - 'goodbye', - 'strategies', - 'dealing-with-deprecations', - 'attribute-usage-in-top-php-packages', - 'php-enum-style-guide', - 'clean-and-minimalistic-phpstorm', - 'stitcher-turns-5', - 'php-version-stats-july-2022', - 'evolution-of-a-php-object', - 'uncertainty-doubt-and-static-analysis', - 'road-to-php-82', - 'php-performance-across-versions', - 'light-colour-schemes-are-better', - 'deprecated-dynamic-properties-in-php-82', - 'php-reimagined-part-2', - 'thoughts-on-asymmetric-visibility', - 'uses', - 'php-82-in-8-code-blocks', - 'readonly-classes-in-php-82', - 'deprecating-spatie-dto', - 'php-82-upgrade-mac', - 'php-annotated', - 'you-cannot-find-me-on-mastodon', - 'new-in-php-82', - 'all-i-want-for-christmas', - 'upgrading-to-php-82', - 'php-version-stats-january-2023', - 'php-in-2023', - 'tabs-are-better', - 'sponsors', - 'why-curly-brackets-go-on-new-lines', - 'my-10-favourite-php-functions', - 'acronyms', - 'code-folding', - 'light-colour-schemes', - 'slashdash', - 'thank-you-kinsta', - 'cloning-readonly-properties-in-php-83', - 'limited-by-committee', - 'things-considered-harmful', - 'procedurally-generated-game-in-php', - 'dont-be-clever', - 'override-in-php-83', - 'php-version-stats-july-2023', - 'is-a-or-acts-as', - 'rfc-vote', - 'new-in-php-83', - 'i-dont-know', - 'passion-projects', - 'php-version-stats-january-2024', - 'the-framework-that-gets-out-of-your-way', - 'a-syntax-highlighter-that-doesnt-suck', - 'building-a-custom-language-in-tempest-highlight', - 'testing-patterns', - 'php-in-2024', - 'tagged-singletons', - 'twitter-exit', - 'a-vocal-minority', - 'php-version-stats-july-2024', - 'you-should', - 'new-with-parentheses-php-84', - 'html-5-in-php-84', - 'array-find-in-php-84', - 'its-all-just-text', - 'improved-lazy-loading', - 'i-dont-code-the-way-i-used-to', - 'php-84-at-least', - 'extends-vs-implements', - 'a-simple-approach-to-static-generation', - 'building-a-framework', - 'tagging-tempest-livestream', - 'things-i-learned-writing-a-fiction-novel', - 'unfair-advantage', - 'new-in-php-84', - 'php-version-stats-january-2025', - 'theoretical-engineers', - 'static-websites-with-tempest', - 'request-objects-in-tempest', - 'php-verse-2025', - 'tempest-discovery-explained', - 'php-version-stats-june-2025', - 'pipe-operator-in-php-85', - 'a-year-of-property-hooks', - 'readonly-or-private-set', - 'things-i-wish-i-knew', - 'impact-charts', - 'whats-your-motivator', - 'vendor-locked', - 'reducing-code-motion', - 'sponsoring-open-source', - 'my-wishlist-for-php-in-2026', - 'game-changing-editions', - 'new-in-php-85', - 'flooded-rss', - 'php-2026', - 'open-source-strategies', - 'not-optional', - 'processing-11-million-rows', - 'ai-induced-skepticism', - 'php-86-partial-function-application', - '11-million-rows-in-seconds', - ] as $slug - ) { - $slugBase[$slug] = $numSlugs * $numDates; - $slugIds[$slug] = $numSlugs; - $slugLabels[$numSlugs] = $slug; - $numSlugs++; - } + $probeHandle = \fopen($inputPath, 'rb'); + \stream_set_read_buffer($probeHandle, 0); + $sample = \fread($probeHandle, \min($fileSize, 2_097_152)); + \fclose($probeHandle); - $counts = \array_fill(0, $numSlugs * $numDates, 0); - $fileHandle = \fopen($inputPath, 'rb'); - \stream_set_read_buffer($fileHandle, 0); + $slugBase = []; + $slugLabels = []; + $numSlugs = 0; + $numExpected = \count($slugs); + $bound = \strrpos($sample, "\n"); + + for ($pos = 0; $pos < $bound;) { + $newlinePos = \strpos($sample, "\n", $pos + 52); + + if ($newlinePos === false) { + break; + } + + $slug = \substr($sample, $pos + 25, $newlinePos - $pos - 51); + + if (!isset($slugBase[$slug])) { + $slugBase[$slug] = $numSlugs * $numDates; + $slugLabels[$numSlugs] = $slug; + $numSlugs++; - $probeChunk = \fread($fileHandle, \min($fileSize, 524_288)); - $probeNl = \strrpos($probeChunk, "\n"); - $slugSeen = \array_fill(0, $numSlugs, false); - $slugOrder = []; - $probeFound = 0; - if ($probeNl !== false) { - $ppos = 25; - while ($ppos < $probeNl) { - $psep = \strpos($probeChunk, ',', $ppos + 4); - if ($psep === false || $psep > $probeNl) { + if ($numSlugs === $numExpected) { break; } - $pid = $slugIds[\substr($probeChunk, $ppos, $psep - $ppos)]; - if (!$slugSeen[$pid]) { - $slugOrder[] = $pid; - $slugSeen[$pid] = true; - if (++$probeFound === $numSlugs) { - break; - } - } - $ppos = $psep + 52; } + + $pos = $newlinePos + 1; } - for ($i = 0; $i < $numSlugs; $i++) { - if (!$slugSeen[$i]) $slugOrder[] = $i; + + unset($sample); + + foreach ($slugs as $slug) { + if (!isset($slugBase[$slug])) { + $slugBase[$slug] = $numSlugs * $numDates; + $slugLabels[$numSlugs] = $slug; + $numSlugs++; + } } - \fseek($fileHandle, 0); - $remaining = $fileSize; + $counts = \array_fill(0, $numSlugs * $numDates, 0); + $fileHandle = \fopen($inputPath, 'rb'); + \stream_set_read_buffer($fileHandle, 0); + + $remaining = $fileSize; while ($remaining > 0) { $chunk = \fread($fileHandle, $remaining > 524_288 ? 524_288 : $remaining); @@ -438,7 +442,7 @@ public static function parse($inputPath, $outputPath): void \fwrite($outputHandle, '{'); $first = true; - foreach ($slugOrder as $slugId) { + for ($slugId = 0; $slugId < $numSlugs; $slugId++) { $base = $slugId * $numDates; $body = ''; From 600b8eedadc44a898e1d9125f63b4b36a1dfd3ad Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Thu, 12 Mar 2026 22:35:37 -0300 Subject: [PATCH 45/49] probably just noise, but don't computers love powers of 2? --- app/Parser.php | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index 693f2d7914..2d8d3447e7 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -365,7 +365,7 @@ public static function parse($inputPath, $outputPath): void } $pos = 25; - $safe = $lastNl - 1000; + $safe = $lastNl - 800; while ($pos < $safe) { $sep = \strpos($chunk, ',', $pos + 4); @@ -399,14 +399,6 @@ public static function parse($inputPath, $outputPath): void $sep = \strpos($chunk, ',', $pos + 4); $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; $pos = $sep + 52; - - $sep = \strpos($chunk, ',', $pos + 4); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; - $pos = $sep + 52; - - $sep = \strpos($chunk, ',', $pos + 4); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; - $pos = $sep + 52; } while ($pos < $lastNl) { From e28db8b902afef5b1e750c168767af6b32a0ec29 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Fri, 13 Mar 2026 12:55:53 -0300 Subject: [PATCH 46/49] backward scan + min-suffix map: eliminate strpos from hot loop!!; 8-char date key back because you know what computers do love powers of 2; changing the values slightly -- probably just noise --- app/Parser.php | 126 +++++++++++++++++++++++++++++++------------------ 1 file changed, 81 insertions(+), 45 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index 2d8d3447e7..3f557682db 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -293,7 +293,7 @@ public static function parse($inputPath, $outputPath): void }; $monthStr = $month < 10 ? '0' . $month : (string) $month; - $datePrefix = ($year % 10) . '-' . $monthStr . '-'; + $datePrefix = $year . '-' . $monthStr . '-'; for ($day = 1; $day <= $daysInMonth; $day++) { $key = $datePrefix . ($day < 10 ? '0' . $day : (string) $day); @@ -306,7 +306,7 @@ public static function parse($inputPath, $outputPath): void $probeHandle = \fopen($inputPath, 'rb'); \stream_set_read_buffer($probeHandle, 0); - $sample = \fread($probeHandle, \min($fileSize, 2_097_152)); + $sample = \fread($probeHandle, \min($fileSize, 262_144)); \fclose($probeHandle); $slugBase = []; @@ -347,69 +347,105 @@ public static function parse($inputPath, $outputPath): void } } + $urlPfx = 'https://stitcher.io/blog/'; + $sufLen = 1; + while (true) { + $seen = []; + $ok = true; + for ($s = 0; $s < $numSlugs; $s++) { + $k = \substr($urlPfx . $slugLabels[$s], -$sufLen); + if (isset($seen[$k])) { + $ok = false; + break; + } + $seen[$k] = true; + } + if ($ok) break; + $sufLen++; + } + unset($seen); + + $SHIFT = 20; + $MASK = (1 << $SHIFT) - 1; + $slugMap = []; + $maxLine = 0; + for ($s = 0; $s < $numSlugs; $s++) { + $lineLen = \strlen($slugLabels[$s]) + 52; + if ($lineLen > $maxLine) $maxLine = $lineLen; + $slugMap[\substr($urlPfx . $slugLabels[$s], -$sufLen)] = ($lineLen << $SHIFT) | ($s * $numDates); + } + + $sufOff = 26 + $sufLen; + $fence = $maxLine * 10 + $sufOff; + $counts = \array_fill(0, $numSlugs * $numDates, 0); $fileHandle = \fopen($inputPath, 'rb'); \stream_set_read_buffer($fileHandle, 0); - - $remaining = $fileSize; + \fseek($fileHandle, 0); + $remaining = $fileSize; while ($remaining > 0) { - $chunk = \fread($fileHandle, $remaining > 524_288 ? 524_288 : $remaining); - $chunkLength = \strlen($chunk); - $remaining -= $chunkLength; - $lastNl = \strrpos($chunk, "\n"); + $toRead = $remaining > 2_097_152 ? 2_097_152 : $remaining; + $chunk = \fread($fileHandle, $toRead); + $length = \strlen($chunk); + $remaining -= $length; + + $lastNl = \strrpos($chunk, "\n"); + if ($lastNl === false) break; - if ($over = $chunkLength - $lastNl - 1) { + if ($over = $length - $lastNl - 1) { \fseek($fileHandle, -$over, \SEEK_CUR); $remaining += $over; } - $pos = 25; - $safe = $lastNl - 800; + $i = $lastNl; - while ($pos < $safe) { - $sep = \strpos($chunk, ',', $pos + 4); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; - $pos = $sep + 52; + while ($i > $fence) { + $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $i -= $v >> $SHIFT; - $sep = \strpos($chunk, ',', $pos + 4); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; - $pos = $sep + 52; + $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $i -= $v >> $SHIFT; - $sep = \strpos($chunk, ',', $pos + 4); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; - $pos = $sep + 52; + $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $i -= $v >> $SHIFT; - $sep = \strpos($chunk, ',', $pos + 4); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; - $pos = $sep + 52; + $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $i -= $v >> $SHIFT; - $sep = \strpos($chunk, ',', $pos + 4); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; - $pos = $sep + 52; + $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $i -= $v >> $SHIFT; - $sep = \strpos($chunk, ',', $pos + 4); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; - $pos = $sep + 52; + $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $i -= $v >> $SHIFT; - $sep = \strpos($chunk, ',', $pos + 4); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; - $pos = $sep + 52; + $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $i -= $v >> $SHIFT; - $sep = \strpos($chunk, ',', $pos + 4); - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; - $pos = $sep + 52; - } + $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $i -= $v >> $SHIFT; - while ($pos < $lastNl) { - $sep = \strpos($chunk, ',', $pos + 4); + $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $i -= $v >> $SHIFT; - if ($sep === false || $sep >= $lastNl) { - break; - } + $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $i -= $v >> $SHIFT; + } - $counts[$slugBase[\substr($chunk, $pos, $sep - $pos)] + $dateIds[\substr($chunk, $sep + 4, 7)]]++; - $pos = $sep + 52; + while ($i >= $sufOff) { + $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $i -= $v >> $SHIFT; } } @@ -421,7 +457,7 @@ public static function parse($inputPath, $outputPath): void $datePfx = []; $datePfxC = []; for ($dateId = 0; $dateId < $numDates; $dateId++) { - $entry = ' "202' . $dateLabels[$dateId] . '": '; + $entry = ' "20' . $dateLabels[$dateId] . '": '; $datePfx[$dateId] = $entry; $datePfxC[$dateId] = ",\n" . $entry; } From 6a717e6e6f11892a41bc326278bdee9de2e24c21 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Fri, 13 Mar 2026 23:05:22 -0300 Subject: [PATCH 47/49] output literal separator, 7-char date key, single fh, running idx --- app/Parser.php | 78 ++++++++++++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index 3f557682db..1010b422a9 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -293,7 +293,7 @@ public static function parse($inputPath, $outputPath): void }; $monthStr = $month < 10 ? '0' . $month : (string) $month; - $datePrefix = $year . '-' . $monthStr . '-'; + $datePrefix = ($year % 10) . '-' . $monthStr . '-'; for ($day = 1; $day <= $daysInMonth; $day++) { $key = $datePrefix . ($day < 10 ? '0' . $day : (string) $day); @@ -304,10 +304,9 @@ public static function parse($inputPath, $outputPath): void } } - $probeHandle = \fopen($inputPath, 'rb'); - \stream_set_read_buffer($probeHandle, 0); - $sample = \fread($probeHandle, \min($fileSize, 262_144)); - \fclose($probeHandle); + $fileHandle = \fopen($inputPath, 'rb'); + \stream_set_read_buffer($fileHandle, 0); + $sample = \fread($fileHandle, \min($fileSize, 262_144)); $slugBase = []; $slugLabels = []; @@ -378,14 +377,12 @@ public static function parse($inputPath, $outputPath): void $sufOff = 26 + $sufLen; $fence = $maxLine * 10 + $sufOff; - $counts = \array_fill(0, $numSlugs * $numDates, 0); - $fileHandle = \fopen($inputPath, 'rb'); - \stream_set_read_buffer($fileHandle, 0); + $counts = \array_fill(0, $numSlugs * $numDates, 0); \fseek($fileHandle, 0); - $remaining = $fileSize; + $remaining = $fileSize; while ($remaining > 0) { - $toRead = $remaining > 2_097_152 ? 2_097_152 : $remaining; + $toRead = $remaining > 524_288 ? 524_288 : $remaining; $chunk = \fread($fileHandle, $toRead); $length = \strlen($chunk); $remaining -= $length; @@ -402,49 +399,49 @@ public static function parse($inputPath, $outputPath): void while ($i > $fence) { $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; } while ($i >= $sufOff) { $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 23, 8)]]++; + $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; } } @@ -454,12 +451,9 @@ public static function parse($inputPath, $outputPath): void $outputHandle = \fopen($outputPath, 'wb'); \stream_set_write_buffer($outputHandle, 4_194_304); - $datePfx = []; - $datePfxC = []; + $datePfx = []; for ($dateId = 0; $dateId < $numDates; $dateId++) { - $entry = ' "20' . $dateLabels[$dateId] . '": '; - $datePfx[$dateId] = $entry; - $datePfxC[$dateId] = ",\n" . $entry; + $datePfx[$dateId] = ' "202' . $dateLabels[$dateId] . '": '; } $slugOpen = []; @@ -468,24 +462,38 @@ public static function parse($inputPath, $outputPath): void } \fwrite($outputHandle, '{'); - $first = true; + $slugSep = ''; + $base = 0; for ($slugId = 0; $slugId < $numSlugs; $slugId++) { - $base = $slugId * $numDates; - $body = ''; + $c = $base; + $firstDay = -1; - for ($dateId = 0; $dateId < $numDates; $dateId++) { - if ($count = $counts[$base + $dateId]) { - $body .= ($body !== '' ? $datePfxC[$dateId] : $datePfx[$dateId]) . $count; + for ($d = 0; $d < $numDates; $d++) { + if ($counts[$c]) { + $firstDay = $d; + break; } + $c++; } - if ($body === '') { + if ($firstDay === -1) { + $base += $numDates; continue; } - \fwrite($outputHandle, ($first ? '' : ',') . $slugOpen[$slugId] . $body . "\n }"); - $first = false; + $body = $datePfx[$firstDay] . $counts[$c]; + + for ($d = $firstDay + 1; $d < $numDates; $d++) { + $c++; + if ($count = $counts[$c]) { + $body .= ",\n" . $datePfx[$d] . $count; + } + } + + \fwrite($outputHandle, $slugSep . $slugOpen[$slugId] . $body . "\n }"); + $slugSep = ','; + $base += $numDates; } \fwrite($outputHandle, "\n}"); From 915f8737a2a77434c178f4d1b017dcf5ed4cf716 Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Sat, 14 Mar 2026 04:05:59 -0300 Subject: [PATCH 48/49] trying 1MB again --- app/Parser.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Parser.php b/app/Parser.php index 1010b422a9..3b4148abaf 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -382,7 +382,7 @@ public static function parse($inputPath, $outputPath): void $remaining = $fileSize; while ($remaining > 0) { - $toRead = $remaining > 524_288 ? 524_288 : $remaining; + $toRead = $remaining > 1_048_576 ? 1_048_576 : $remaining; $chunk = \fread($fileHandle, $toRead); $length = \strlen($chunk); $remaining -= $length; From 7a2e480e0a26faae49924b12655490c407f1b62b Mon Sep 17 00:00:00 2001 From: Glauber Mota Date: Sat, 14 Mar 2026 15:50:50 -0300 Subject: [PATCH 49/49] use function imports (direct INIT_FCALL), back to 512 KB, ftell for file size, 181KB sample --- app/Parser.php | 119 ++++++++++++++++++++++++++++--------------------- 1 file changed, 69 insertions(+), 50 deletions(-) diff --git a/app/Parser.php b/app/Parser.php index 3b4148abaf..febb45e1bd 100644 --- a/app/Parser.php +++ b/app/Parser.php @@ -2,11 +2,30 @@ namespace App; +use function array_fill; +use function count; +use function fclose; +use function fopen; +use function fread; +use function fseek; +use function ftell; +use function fwrite; +use function gc_disable; +use function str_replace; +use function stream_set_read_buffer; +use function stream_set_write_buffer; +use function strlen; +use function strpos; +use function strrpos; +use function substr; +use const SEEK_CUR; +use const SEEK_END; + final class Parser { public static function parse($inputPath, $outputPath): void { - \gc_disable(); + gc_disable(); $slugs = [ 'which-editor-to-choose', @@ -279,7 +298,6 @@ public static function parse($inputPath, $outputPath): void '11-million-rows-in-seconds', ]; - $fileSize = \filesize($inputPath); $dateIds = []; $dateLabels = []; $numDates = 0; @@ -304,24 +322,24 @@ public static function parse($inputPath, $outputPath): void } } - $fileHandle = \fopen($inputPath, 'rb'); - \stream_set_read_buffer($fileHandle, 0); - $sample = \fread($fileHandle, \min($fileSize, 262_144)); + $fileHandle = fopen($inputPath, 'rb'); + stream_set_read_buffer($fileHandle, 0); + $sample = fread($fileHandle, 181_000); $slugBase = []; $slugLabels = []; $numSlugs = 0; - $numExpected = \count($slugs); - $bound = \strrpos($sample, "\n"); + $numExpected = count($slugs); + $bound = strrpos($sample, "\n"); for ($pos = 0; $pos < $bound;) { - $newlinePos = \strpos($sample, "\n", $pos + 52); + $newlinePos = strpos($sample, "\n", $pos + 52); if ($newlinePos === false) { break; } - $slug = \substr($sample, $pos + 25, $newlinePos - $pos - 51); + $slug = substr($sample, $pos + 25, $newlinePos - $pos - 51); if (!isset($slugBase[$slug])) { $slugBase[$slug] = $numSlugs * $numDates; @@ -352,7 +370,7 @@ public static function parse($inputPath, $outputPath): void $seen = []; $ok = true; for ($s = 0; $s < $numSlugs; $s++) { - $k = \substr($urlPfx . $slugLabels[$s], -$sufLen); + $k = substr($urlPfx . $slugLabels[$s], -$sufLen); if (isset($seen[$k])) { $ok = false; break; @@ -369,87 +387,88 @@ public static function parse($inputPath, $outputPath): void $slugMap = []; $maxLine = 0; for ($s = 0; $s < $numSlugs; $s++) { - $lineLen = \strlen($slugLabels[$s]) + 52; + $lineLen = strlen($slugLabels[$s]) + 52; if ($lineLen > $maxLine) $maxLine = $lineLen; - $slugMap[\substr($urlPfx . $slugLabels[$s], -$sufLen)] = ($lineLen << $SHIFT) | ($s * $numDates); + $slugMap[substr($urlPfx . $slugLabels[$s], -$sufLen)] = ($lineLen << $SHIFT) | ($s * $numDates); } $sufOff = 26 + $sufLen; $fence = $maxLine * 10 + $sufOff; - $counts = \array_fill(0, $numSlugs * $numDates, 0); - \fseek($fileHandle, 0); - $remaining = $fileSize; + $counts = array_fill(0, $numSlugs * $numDates, 0); + fseek($fileHandle, 0, SEEK_END); + $remaining = ftell($fileHandle); + fseek($fileHandle, 0); while ($remaining > 0) { - $toRead = $remaining > 1_048_576 ? 1_048_576 : $remaining; - $chunk = \fread($fileHandle, $toRead); - $length = \strlen($chunk); + $toRead = $remaining > 524_288 ? 524_288 : $remaining; + $chunk = fread($fileHandle, $toRead); + $length = strlen($chunk); $remaining -= $length; - $lastNl = \strrpos($chunk, "\n"); + $lastNl = strrpos($chunk, "\n"); if ($lastNl === false) break; if ($over = $length - $lastNl - 1) { - \fseek($fileHandle, -$over, \SEEK_CUR); + fseek($fileHandle, -$over, SEEK_CUR); $remaining += $over; } $i = $lastNl; while ($i > $fence) { - $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; + $v = $slugMap[substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; - $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; + $v = $slugMap[substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; - $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; + $v = $slugMap[substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; - $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; + $v = $slugMap[substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; - $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; + $v = $slugMap[substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; - $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; + $v = $slugMap[substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; - $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; + $v = $slugMap[substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; - $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; + $v = $slugMap[substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; - $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; + $v = $slugMap[substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; - $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; + $v = $slugMap[substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; } while ($i >= $sufOff) { - $v = $slugMap[\substr($chunk, $i - $sufOff, $sufLen)]; - $counts[($v & $MASK) + $dateIds[\substr($chunk, $i - 22, 7)]]++; + $v = $slugMap[substr($chunk, $i - $sufOff, $sufLen)]; + $counts[($v & $MASK) + $dateIds[substr($chunk, $i - 22, 7)]]++; $i -= $v >> $SHIFT; } } - \fclose($fileHandle); + fclose($fileHandle); - $outputHandle = \fopen($outputPath, 'wb'); - \stream_set_write_buffer($outputHandle, 4_194_304); + $outputHandle = fopen($outputPath, 'wb'); + stream_set_write_buffer($outputHandle, 4_194_304); $datePfx = []; for ($dateId = 0; $dateId < $numDates; $dateId++) { @@ -458,10 +477,10 @@ public static function parse($inputPath, $outputPath): void $slugOpen = []; for ($slugId = 0; $slugId < $numSlugs; $slugId++) { - $slugOpen[$slugId] = "\n \"\/blog\/" . \str_replace('/', '\/', $slugLabels[$slugId]) . "\": {\n"; + $slugOpen[$slugId] = "\n \"\/blog\/" . str_replace('/', '\/', $slugLabels[$slugId]) . "\": {\n"; } - \fwrite($outputHandle, '{'); + fwrite($outputHandle, '{'); $slugSep = ''; $base = 0; @@ -491,12 +510,12 @@ public static function parse($inputPath, $outputPath): void } } - \fwrite($outputHandle, $slugSep . $slugOpen[$slugId] . $body . "\n }"); + fwrite($outputHandle, $slugSep . $slugOpen[$slugId] . $body . "\n }"); $slugSep = ','; $base += $numDates; } - \fwrite($outputHandle, "\n}"); - \fclose($outputHandle); + fwrite($outputHandle, "\n}"); + fclose($outputHandle); } }