Search code examples
phpperformancereflectionattributesphp-8.2

Does PHP8 Reflection more bad performance versus PHP7 without reflection


I have re write my code from old php 7.4 scripts. And i see my code be more slower than old version My old models export data from method toArray And his representation was like that

public function toArray()
{
   return [
      "id" => $this->id;
      "name" => $this->name;
      //And many other values
   ]
}

But now, with PHP 8, i have use reflection object to serialize my object. That take something like :

class MyModel extends Model
{
   #[AttributeExport]
   private int $id;

   #[AttributeExport]
   private string $name;
}

//In Model

abstract Model implement JsonSerialize
{
   public function jsonSerialize(): array
    {
        $data = [];

        $reflection = new \ReflectionClass(get_called_class());
        $elements = array_merge($reflection->getProperties(), $reflection->getMethods());

        foreach ($elements as $element) {
            $exportableAttributes = $element->getAttributes(
                AttributeExport::class,
                \ReflectionAttribute::IS_INSTANCEOF
            );
            foreach ($exportableAttributes as $exportableAttribute) {
                /** @var AttributeExport $exportableInstance */
                $exportableInstance = $exportableAttribute->newInstance();

                $exportName = $exportableInstance->hasName() ? $exportableInstance->getName() : $element->getName();
                $method = $element->getName();
                if ($element instanceof \ReflectionProperty) {
                    $method = "get" . ucfirst($method);
                }

                $data[$exportName] = $this->{$method}();
            }
        }
        return $data;
    }
}

I may be mistakenly thinking that the problem may come from there. But do you think that on a large volume of data, this strategy can have an impact?

I used more abstract class compared to the old version to avoid code duplication. Dont know if that can impact too


Solution

  • There are two things to consider, you changed from direct property access to a reflection based getter method approach:

    • Calling methods is usually slower then direct access
    • Reflection operations are expensive but can usually be cached quite well

    Have a look at this modification, i added a simple cache, now the runtime of the reflection approach is much faster, i think the performace hit should be acceptable in the most settings:

    string(29) "reflection: 0.036892890930176"
    string(33) "direct acccess: 0.015763998031616"
    string(33) "direct methods: 0.018218040466309"
    
    <?php
    #[Attribute]
    class AttributeExport
    {
    
        public function __construct()
        {
        }
    
        public function hasName()
        {
            return false;
        }
    }
    
    abstract class Model implements JsonSerializable
    {
        private static $cache = [];
    
        public function jsonSerialize():array
        {
            $data = [];
    
            if (!isset(self::$cache[self::class]))
            {
                self::$cache[self::class ] = [];
                $reflection = new \ReflectionClass(get_called_class());
                $elements = array_merge($reflection->getProperties() , $reflection->getMethods());
    
                foreach ($elements as $element)
                {
                    $exportableAttributes = $element->getAttributes(AttributeExport::class , \ReflectionAttribute::IS_INSTANCEOF);
                    foreach ($exportableAttributes as $exportableAttribute)
                    {
                        /** @var AttributeExport $exportableInstance */
                        $exportableInstance = $exportableAttribute->newInstance();
    
                        $exportName = $exportableInstance->hasName() ? $exportableInstance->getName() : $element->getName();
                        $method = $element->getName();
                        if ($element instanceof \ReflectionProperty)
                        {
                            $method = "get" . ucfirst($method);
                        }
    
                        self::$cache[self::class][$exportName] = $method;
                    }
                }
            }
    
            foreach (self::$cache[self::class] as $exportName => $method)
            {
                $data[$exportName] = $this->{$method}();
            }
            return $data;
        }
    
    }
    
    class MyModel extends Model
    {
    
        public function __construct($id, $name)
        {
            $this->id = $id;
            $this->name = $name;
        }
    
        #[AttributeExport]
        private int $id;
    
        #[AttributeExport]
        private string $name;
    
        public function getId()
        {
            return $this->id;
        }
        public function getName()
        {
            return $this->name;
        }
    
        public function toArray()
        {
            return [
                "id" => $this->id,
                "name" => $this->name
                //And many other values
            ];
        }
    
        public function toArrayMethods()
        {
            return [
                "id" => $this->getId(),
                "name" => $this->getName()
                //And many other values
            ];
        }
    
    }
    
    $z = new MyModel(1, "a");
    $zr = $z->jsonSerialize();
    $zr2 = $z->toArray();
    
    $time1 = microtime(true);
    for ($a = 0;$a < 100000;$a++)
    {
        $x = new MyModel(1, "a");
        $r = $x->jsonSerialize();
    }
    $timea = microtime(true) - $time1;
    
    $time2 = microtime(true);
    for ($b = 1;$b < 100000;$b++)
    {
        $x2 = new MyModel(1, "a");
        $r2 = $x2->toArray();
    }
    $timeb = microtime(true) - $time2;
    
    $time3 = microtime(true);
    for ($b = 1;$b < 100000;$b++)
    {
        $x2 = new MyModel(1, "a");
        $r2 = $x2->toArrayMethods();
    }
    $timec = microtime(true) - $time3;
    
    var_dump("reflection: " . $timea);
    var_dump("direct acccess: " . $timeb);
    var_dump("direct methods: " . $timec);
    

    Using reflection to access the properties without getter could also be measured.