Hi Dik,

On 11/3/19 12:44 AM, Dik Takken wrote:
> On 25-10-19 12:22, Nikita Popov wrote:
>> For reference, here are the results I get with/without JIT:
>> https://gist.github.com/nikic/2a2d363fffaa3aeb251da976f0edbc33
> 
> I toyed a bit with the benchmark script (union_bench.php) as well and
> wanted to share some observations. First of all I noticed the benchmark
> script has a typo on line 90 where it is calling the wrong function. It
> should read:
> 
>    func6(1, 2, 3, 4, 5);
> 
> When running the corrected script I see that adding 5 argument type
> checks and a return type check cause almost 4x slowdown. My results
> (with opcache / jit):
> 
> func($a,$b,$c,$d,$e)               0.680    0.583
> func(int $a,$b,$c,$d,$e): int      2.106    2.009

Thanks for catching this. At least, now I see 2 times slowdown without 
JIT, that I expected, but didn't see.

func($a,$b,$c,$d,$e)               1.746    1.555
func(int $a,$b,$c,$d,$e): int      3.647    3.455

JIT will able to eliminate type checks only if it exactly knows the 
called function at caller site. Unfortunately, this is quite rare case, 
because the functions may be declared in different files, OOP, type 
variance, etc.

> 
> However, this appears to be entirely due to the return type check
> lacking a JIT implementation, as pointed out by Nikita. Adding one more
> test to the benchmark shows this nicely:
> 
> func($a,$b,$c,$d,$e)               0.675    0.575
> func(int $a,$b,$c,$d,$e)           0.574    0.475
> func(int $a,$b,$c,$d,$e): int      2.106    2.009
> 
> Now we can see that the argument type hint actually improves
> performance, I guess due to it narrowing down the number of possible
> types that need to be considered for the function arguments.
> 
> Union types allow for more accurate type hinting as well as type hinting
> in places where this is currently not possible. As a result union types
> can be used to obtain performance gains. As an example, consider the
> case where the return type hint matches the type information that
> opcache has inferred about the variable that is returned. In that case,
> the return type check is optimized away. Let us try and leverage union
> types to make this happen. From the benchmark script we take func6:
> 
>    function func6(int $a, int $b, int $c, int $d, int $e) : int {
>        return $a + $b + $c + $d + $e;
>    }
> 
> and adjust it to read:
> 
>    function func6(int $a, int $b, int $c, int $d, int $e) : int|float {
>        return $a + $b + $c + $d + $e;
>    }
> 
> Now the return type hint matches what opcache infers the result of the
> expression will be and the cost of return type checking disappears
> completely:
> 
> func($a,$b,$c,$d,$e)                 0.663    0.568
> func(int $a,$b,$c,$d,$e)             0.574    0.475
> func(int $a,$b,$c,$d,$e): int|float  0.561    0.466
> 
> Then, on to another observation. The SSA forms currently produced by
> opcache show union types like string|int. This suggests that opcache
> supports union types for type inference already. It explains why opcache
> can nicely optimize type checks away even when union types are used.
> 
> This is not true for unions of classes though. A union type like int|Foo
> copies into the SSA form just fine while Foo|Bar becomes 'object'. Code
> like this:
> 
>    class Foo {}
>    class Bar {}
> 
>    function func(): Foo|Bar {
>        return new Foo();
>    }
> 
>    func();
> 
> produces the following SSA form:
> 
>    func: ; (lines=4, args=0, vars=0, tmps=1, ssa_vars=2, no_loops)
>        ; (before dfa pass)
>        ; /php-src/sapi/cli/test.php:6-8
>        ; return  [object]
>    BB0: start exit lines=[0-3]
>        ; level=0
>                #0.V0 [object (Foo)] = NEW 0 string("Foo")
>                DO_FCALL
>                VERIFY_RETURN_TYPE #0.V0 [object (Foo)] -> #1.V0 [object]
>                RETURN #1.V0 [object]
> 
> which will still perform a return type check even though the return type
> hint matches the actual type of the variable. Apparently the union type
> support in opcache is present but incomplete.
> So, while union types can incur higher type checking cost they also
> provide more powerful means to help type inference and improve
> performance. As opcache improves over time I think we can expect the
> cost to decrease while the gain increases. Or am I too optimistic here?

In my experience, static optimizations are not able to eliminate most 
type checks in PHP. Probably, if we developed more complete type-system 
and used type declaration everywhere we could achieve better results.
Introducing more type checks and more complex rules will increase 
run-time overhead.

I'm currently working on attempt of speculative optimizations based on 
run-time feedback, and the results might change the whole picture a bit.

Anyway, I'm especially against of mixing multiple classes in unions, not 
because of performance, but because of complex rules of method 
inheritance compatibility checks in conjunction with type variance.

Thanks. Dmitry.

> 
> Regards,
> Dik Takken
> 
> 
> CAUTION: This email originated from outside of the organization. Do not click 
> on links or open attachments unless you recognize the sender and know the 
> content is safe.
> 

Reply via email to