[PHP-DEV] [RFC Idea] Proposal: Loop Unrolling in Userland via #[Unroll(N)] Attribute

On this page
  1. 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.
  2. What if PHP allowed users to annotate loops like this?
  3. In a patched version of the PHP compiler, we implemented a special-case optimization for exactly this loop pattern:
  4. This is not a full RFC yet, just an idea seeking feedback. Current limitations:

Hello,

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

Comments

    Comments are reviewed before they appear.