Browse Source

some performance tweaks

Johannes Schmitt 14 years ago
parent
commit
1908fc0c27
3 changed files with 170 additions and 12 deletions
  1. 47 11
      Serializer/Normalizer/PropertyBasedNormalizer.php
  2. 114 0
      Tests/PerformanceTest.php
  3. 9 1
      phpunit.xml.dist

+ 47 - 11
Serializer/Normalizer/PropertyBasedNormalizer.php

@@ -18,6 +18,8 @@
 
 namespace JMS\SerializerBundle\Serializer\Normalizer;
 
+use JMS\SerializerBundle\Serializer\Exclusion\ExclusionStrategyInterface;
+
 use Symfony\Component\Serializer\Normalizer\SerializerAwareNormalizer;
 use JMS\SerializerBundle\Annotation\Type;
 use JMS\SerializerBundle\Exception\UnsupportedException;
@@ -34,6 +36,10 @@ class PropertyBasedNormalizer extends SerializerAwareNormalizer
     private $reader;
     private $propertyNamingStrategy;
     private $exclusionStrategyFactory;
+    private $exclusionStrategies = array();
+    private $reflectionData = array();
+    private $translatedNames = array();
+    private $excludedProperties = array();
 
     public function __construct(ReaderInterface $reader, PropertyNamingStrategyInterface $propertyNamingStrategy, ExclusionStrategyFactoryInterface $exclusionStrategyFactory)
     {
@@ -49,8 +55,7 @@ class PropertyBasedNormalizer extends SerializerAwareNormalizer
         }
 
         // collect class hierarchy
-        $class = new \ReflectionClass($object);
-        $classes = $this->getClassHierarchy($class);
+        list($class, $classes) = $this->getReflectionData(get_class($object));
 
         // go through properties and collect values
         $normalized = $processed = array();
@@ -63,11 +68,11 @@ class PropertyBasedNormalizer extends SerializerAwareNormalizer
                 }
                 $processed[$name] = true;
 
-                if ($exclusionStrategy->shouldSkipProperty($property)) {
+                if ($this->shouldSkipProperty($exclusionStrategy, $property)) {
                     continue;
                 }
 
-                $serializedName = $this->propertyNamingStrategy->translateName($property);
+                $serializedName = $this->translateName($property);
                 $property->setAccessible(true);
                 $value = $this->serializer->normalize($property->getValue($object), $format);
 
@@ -77,6 +82,7 @@ class PropertyBasedNormalizer extends SerializerAwareNormalizer
 
                 $normalized[$serializedName] = $value;
             }
+
         }
 
         return $normalized;
@@ -98,8 +104,7 @@ class PropertyBasedNormalizer extends SerializerAwareNormalizer
             throw new UnsupportedException(sprintf('Unsupported type; "%s" is not a valid class.', $type));
         }
 
-        $class = new \ReflectionClass($type);
-        $classes = $this->getClassHierarchy($class);
+        list($class, $classes) = $this->getReflectionData($type);
 
         $object = unserialize(sprintf('O:%d:"%s":0:{}', strlen($type), $type));
         $processed = array();
@@ -112,11 +117,12 @@ class PropertyBasedNormalizer extends SerializerAwareNormalizer
                 }
                 $processed[$name] = true;
 
-                if ($exclusionStrategy->shouldSkipProperty($property)) {
+
+                if ($this->shouldSkipProperty($exclusionStrategy, $property)) {
                     continue;
                 }
 
-                $serializedName = $this->propertyNamingStrategy->translateName($property);
+                $serializedName = $this->translateName($property);
                 if (!array_key_exists($serializedName, $data)) {
                     continue;
                 }
@@ -141,8 +147,32 @@ class PropertyBasedNormalizer extends SerializerAwareNormalizer
         return $object;
     }
 
+    private function translateName(\ReflectionProperty $property)
+    {
+        $key = $property->getDeclaringClass()->getName().'$'.$property->getName();
+        if (isset($this->translatedNames[$key])) {
+            return $this->translatednames[$key];
+        }
+
+        return $this->translatedNames[$key] = $this->propertyNamingStrategy->translateName($property);
+    }
+
+    private function shouldSkipProperty(ExclusionStrategyInterface $exclusionStrategy, \ReflectionProperty $property)
+    {
+        $key = $property->getDeclaringClass()->getName().'$'.$property->getName();
+        if (isset($this->excludedProperties[$key])) {
+            return $this->excludedProperties[$key];
+        }
+
+        $this->excludedProperties[$key] = $exclusionStrategy->shouldSkipProperty($property);
+    }
+
     private function getExclusionStrategy(\ReflectionClass $class)
     {
+        if (isset($this->exclusionStrategies[$name = $class->getName()])) {
+            return $this->exclusionStrategies[$name];
+        }
+
         $annotations = $this->reader->getClassAnnotations($class);
         foreach ($annotations as $annotation) {
             if ($annotation instanceof ExclusionPolicy) {
@@ -150,11 +180,16 @@ class PropertyBasedNormalizer extends SerializerAwareNormalizer
             }
         }
 
-        return $this->exclusionStrategyFactory->getStrategy('NONE');
+        return $this->exclusionStrategies[$name] = $this->exclusionStrategyFactory->getStrategy('NONE');
     }
 
-    private function getClassHierarchy(\ReflectionClass $class)
+    private function getReflectionData($fqcn)
     {
+        if (isset($this->reflectionData[$fqcn])) {
+            return $this->reflectionData[$fqcn];
+        }
+
+        $class = new \ReflectionClass($fqcn);
         $classes = array();
         do {
             if (!$class->isUserDefined()) {
@@ -163,7 +198,8 @@ class PropertyBasedNormalizer extends SerializerAwareNormalizer
 
             $classes[] = $class;
         } while (false !== $class = $class->getParentClass());
+        $classes = array_reverse($classes, false);
 
-        return array_reverse($classes, false);
+        return $this->reflectionData[$fqcn] = array($class, $classes);
     }
 }

+ 114 - 0
Tests/PerformanceTest.php

@@ -0,0 +1,114 @@
+<?php
+
+namespace JMS\SerializerBundle\Tests;
+
+use JMS\SerializerBundle\Tests\Fixtures\Comment;
+
+use JMS\SerializerBundle\Tests\Fixtures\Author;
+
+use JMS\SerializerBundle\Tests\Fixtures\BlogPost;
+use Annotations\Reader;
+use JMS\SerializerBundle\Serializer\Exclusion\NoneExclusionStrategy;
+use JMS\SerializerBundle\Serializer\Exclusion\AllExclusionStrategy;
+use JMS\SerializerBundle\Serializer\Exclusion\ExclusionStrategyFactory;
+use JMS\SerializerBundle\Serializer\Naming\CamelCaseNamingStrategy;
+use JMS\SerializerBundle\Serializer\Naming\SerializedNameAnnotationStrategy;
+use JMS\SerializerBundle\Serializer\Normalizer\ArrayCollectionNormalizer;
+use JMS\SerializerBundle\Serializer\Normalizer\NativePhpTypeNormalizer;
+use JMS\SerializerBundle\Serializer\Normalizer\PropertyBasedNormalizer;
+use JMS\SerializerBundle\Serializer\Serializer;
+use Symfony\Component\Serializer\Encoder\JsonEncoder;
+use Symfony\Component\Serializer\Encoder\XmlEncoder;
+
+class PerformanceTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @group performance
+     */
+    public function testNormalizationPerformance()
+    {
+        $serializer = $this->getSerializer();
+        $testData   = $this->getTestCollection();
+
+        $time = microtime(true);
+        for ($i=0,$c=10; $i<$c; $i++) {
+            $serializer->normalize($testData);
+        }
+        $time = microtime(true) - $time;
+
+        $this->printResults('normalization', $time, $c);
+    }
+
+    private function getTestCollection()
+    {
+        $collection = array();
+        for ($i=0; $i<50; $i++) {
+            $collection[] = $this->getTestObject();
+        }
+
+        return $collection;
+    }
+
+    private function getTestObject()
+    {
+        $post = new BlogPost('FooooooooooooooooooooooBAR', new Author('Foo'));
+        for ($i=0; $i<10; $i++) {
+            $post->addComment(new Comment(new Author('foo'), 'foobar'));
+        }
+
+        return $post;
+    }
+
+    private function getSerializer()
+    {
+        $reader = new Reader();
+
+        $propertyNamingStrategy = new SerializedNameAnnotationStrategy(
+            $reader,
+            new CamelCaseNamingStrategy()
+        );
+
+        $encoders = array(
+            'xml'  => new XmlEncoder(),
+            'json' => new JsonEncoder(),
+        );
+
+        $customNormalizers = array(
+            new ArrayCollectionNormalizer(),
+        );
+
+        $exclusionStrategyFactory = new ExclusionStrategyFactory(array(
+            'ALL'  => new AllExclusionStrategy($reader),
+            'NONE' => new NoneExclusionStrategy($reader),
+        ));
+
+        return new Serializer(
+            new NativePhpTypeNormalizer(),
+            new PropertyBasedNormalizer($reader, $propertyNamingStrategy, $exclusionStrategyFactory),
+            $customNormalizers,
+            $encoders
+        );
+    }
+
+    private function printResults($test, $time, $iterations)
+    {
+        if (0 == $iterations) {
+            throw new \InvalidArgumentException('$iterations cannot be zero.');
+        }
+
+        $title = $test." results:\n";
+        $iterationsText = sprintf("Iterations:         %d\n", $iterations);
+        $totalTime      = sprintf("Total Time:         %.3f s\n", $time);
+        $iterationTime  = sprintf("Time per iteration: %.3f ms\n", $time/$iterations * 1000);
+
+        $max = max(strlen($title), strlen($iterationTime)) - 1;
+
+        echo "\n".str_repeat('-', $max)."\n";
+        echo $title;
+        echo str_repeat('=', $max)."\n";
+        echo $iterationsText;
+        echo $totalTime;
+        echo $iterationTime;
+        echo str_repeat('-', $max)."\n";
+    }
+}

+ 9 - 1
phpunit.xml.dist

@@ -9,10 +9,18 @@
          processIsolation="false"
          stopOnFailure="false"
          syntaxCheck="false"
-         bootstrap="./../../../../app/bootstrap.php.cache">
+         bootstrap="./../../../../app/bootstrap.php.cache"
+>
+         
     <testsuites>
         <testsuite name="SerializerExtraBbundle Test Suite">
             <directory>./Tests</directory>
         </testsuite>
     </testsuites>
+    
+    <groups>
+        <exclude>
+            <group>performance</group>
+        </exclude>
+    </groups>
 </phpunit>