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

Reply via email to