Skip to content

perf: use trunc(dval(&u)) instead of (Long)(dval(&u)) #22676

Open
henderkes wants to merge 1 commit into
php:masterfrom
henderkes:xperf/dtoa-fp-unit-digit-loop
Open

perf: use trunc(dval(&u)) instead of (Long)(dval(&u)) #22676
henderkes wants to merge 1 commit into
php:masterfrom
henderkes:xperf/dtoa-fp-unit-digit-loop

Conversation

@henderkes

@henderkes henderkes commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

… to keep per-digit dependency chain in float register

<?php
// Micro-benchmark of the float->string paths
const N = 4000;
const R = 8;

$vals = [];
for ($i = 1; $i <= N; $i++) {
    $vals[] = M_PI * $i / 7.0 + 1.0 / $i;
}

function bench(string $name, callable $fn, array $vals): void {
    $fn($vals); $fn($vals);
    $best = PHP_INT_MAX;
    for ($r = 0; $r < R; $r++) {
        $t = hrtime(true);
        $sink = $fn($vals);
        $dt = hrtime(true) - $t;
        if ($dt < $best) $best = $dt;
    }
    $ops = count($vals);
    printf("%-26s %8.1f ns/op   (min block %6.2f ms)\n", $name, $best / $ops, $best / 1e6);
}

bench('(string) cast', function($vals){ $s=0; foreach($vals as $v){ $x=(string)$v; $s+=strlen($x); } return $s; }, $vals);
bench('string interpolation', function($vals){ $s=0; foreach($vals as $v){ $x="$v"; $s+=strlen($x); } return $s; }, $vals);
bench('concatenation', function($vals){ $s=''; foreach($vals as $v){ $s = $v . '|'; } return strlen($s); }, $vals);
bench('number_format', function($vals){ $s=0; foreach($vals as $v){ $s+=strlen(number_format($v, 6, '.', ',')); } return $s; }, $vals);
bench("sprintf('%g')", function($vals){ $s=0; foreach($vals as $v){ $s+=strlen(sprintf('%g',$v)); } return $s; }, $vals);
bench("sprintf('%.10f')", function($vals){ $s=0; foreach($vals as $v){ $s+=strlen(sprintf('%.10f',$v)); } return $s; }, $vals);
bench('json_encode (array)', function($vals){ return strlen(json_encode($vals)); }, $vals);
bench('serialize (array)', function($vals){ return strlen(serialize($vals)); }, $vals);
bench('var_export (array)', function($vals){ return strlen(var_export($vals, true)); }, $vals);
bench('implode floats', function($vals){ return strlen(implode(',', $vals)); }, $vals);

aarch64:

Path (ns/op)basedtoachange
(string) cast182.6146.4−19.8%
string interpolation182.8146.3−20.0%
concatenation196.2159.5−18.7%
number_format309.8286.4−7.6%
sprintf('%g')153.0137.6−10.1%
sprintf('%.10f')258.8226.4−12.5%
json_encode (array)568.1567.8−0.1%
serialize (array)580.9579.7−0.2%
var_export (array)637.5635.9−0.3%
implode floats164.9129.3−21.6%

x86_64 (sse4.1):

Path (ns/op)basedtoachange
(string) cast105.9100.1−5.5%
string interpolation105.6100.3−5.0%
concatenation111.5106.1−4.8%
number_format162.4158.8−2.2%
sprintf('%g')82.279.8−2.9%
sprintf('%.10f')136.9130.7−4.5%
json_encode (array)261.1259.7−0.5%
serialize (array)268.2266.8−0.5%
var_export (array)287.5290.1+0.9%
implode floats103.197.6−5.3%

LLM disclosure: optimisation found entirely by Fable, I verified, wrote the comment and benchmarked.

…per-digit dependency chain in float register
@Girgias

Girgias commented Jul 10, 2026

Copy link
Copy Markdown
Member

This file is old and complicated and previous changees were rejected.

IMHO it would make more sense to replace this with a known correct and fast alternative like https://github.com/kolemannix/ffc.h

@henderkes

Copy link
Copy Markdown
Contributor Author

I'm sure replacing the whole logic would be a good idea, but I don't see why the file being old should lead to changes being rejected. It's a rather minimal change with decent performance gains (especially on aarch64) for a common operation and doesn't make replacing the logic entirely in the future any harder.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants