Hello internals, I’d like to share an early proposal idea that introduces the ability to express *loop unrolling* directly from userland using a simple attribute syntax: *#[Unroll(N)]*. Motivation
Loop unrolling is a well-known optimization strategy used by compilers to reduce the overhead of conditional jumps and improve CPU pipelining by expanding the loop body multiple times. In PHP, we often hand-unroll loops for performance gains in hot paths, like so: for ($i = 0; $i < $n; $i += 4) { $sum += $array[$i] + $array[$i + 1] + $array[$i + 2] + $array[$i + 3]; } This is tedious and error-prone, especially when the logic is more complex. Idea: #[Unroll(N)] Syntax What if PHP allowed users to annotate loops like this? #[Unroll(4)]for ($i = 0; $i < $n; $i++) { $sum += $array[$i]; } And the compiler could then *transform* this into an equivalent unrolled version at compile time, removing the need for manual duplication and keeping the source readable. Proof-of-Concept In a patched version of the PHP compiler, we implemented a special-case optimization for exactly this loop pattern: https://3v4l.org/T23MH <?php // Proposal idea of enabling the unrolling in the userland. function measure(string $label, callable $fn): void { $start = hrtime(true); $result = $fn(); $end = hrtime(true); $elapsed = ($end - $start) / 1e9; echo "Sum: {$result}, {$label}: " . number_format($elapsed, 6) . " seconds\n"; } $array = range(1, 1e6); $n = count($array); measure("Normal for loop", function () use ($array, $n) { $sum = 0; for ($i = 0; $i < $n; $i++) { $sum += $array[$i]; } return $sum; }); // Sum: 500000500000, Normal for loop: 0.047172 seconds measure("Manual Unrolled x4", function () use ($array, $n) { $sum = 0; for ($i = 0; $i < $n; $i += 4) { $sum += $array[$i] + $array[$i + 1] + $array[$i + 2] + $array[$i + 3]; } return $sum; }); // Sum: 500000500000, Manual Unrolled x4: 0.042200 seconds measure("Manual Unrolled x8", function () use ($array, $n) { $sum = 0; for ($i = 0; $i < $n; $i += 8) { $sum += $array[$i] + $array[$i + 1] + $array[$i + 2] + $array[$i + 3] + $array[$i + 4] + $array[$i + 5] + $array[$i + 6] + $array[$i + 7]; } return $sum; }); // Sum: 500000500000, Manual Unrolled x8: 0.036472 seconds // Attributed Unroll - this is handled by your modified compiler measure("Attributed Unrolled #[Unroll(4)]", function () use ($array, $n) { $sum = 0; // @TODO: KhaledAlam: Unrolling logic of zend_compile_for( ) // #[Unroll(4)] for ($i = 0; $i < $n; $i++) { $sum += $array[$i]; } return $sum; }); // Sum: 0, Attributed Unrolled #[Unroll(4)] expected to be less than normal loop um: 500000500000, Normal for loop: 0.006646 seconds Sum: 500000500000, Manual Unrolled x4: 0.005646 seconds Sum: 500000500000, Manual Unrolled x8: 0.004885 seconds Sum: 500000500000, Attributed Unrolled #[Unroll(4)]: expected to be less than normal loop Limitations & Scope This is not a full RFC yet, just an idea seeking feedback. Current limitations: - No generic AST replacement yet, just a special-case demo. - Doesn't handle complex control flow, non-trivial statements, or multi-variable iteration. - Attribute is ignored in production PHP, requires patched compiler. Open Questions - Is there interest in allowing *user-directed optimizations* like this? - Would PHP be willing to expand attribute-driven compiler behavior in this direction? - What are the risks in exposing low-level optimizations to users? - Could this be generalized in the JIT instead? Source & Demo Happy to share an initial POC underdevelopment patch[0] if there's interest. Looking forward to your thoughts. Best regards, Khaled Alam [0] https://gist.github.com/khaledalam/2f2237733e1cc3ec74d597b1b20d94df