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

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?

Regards,
Dik Takken

-- 
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php

Reply via email to