-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Serializer] twice as slow as the JMS serializer #16179
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Can you share the code you used for the benchmark to profile it? |
The cannot share the models for the object graph currently. But the test is pretty straight forward: <?php
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;
use JMS\Serializer\SerializationContext;
use JMS\Serializer\SerializerBuilder;
use JMS\Serializer\SerializerInterface as JmsSerializerInterface;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\SerializerInterface as SymfonySerializerInterface;
class SerializerComparisonTest extends \PHPUnit_Framework_TestCase
{
/**
* @var SymfonySerializerInterface
*/
private $symfonySerializer;
/**
* @var JmsSerializerInterface
*/
private $jmsSerializer;
protected function setUp()
{
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$callback = function ($dateTime) {
return $dateTime instanceof \DateTime
? $dateTime->format(\DateTime::ISO8601)
: '';
};
$encoders = array(new JsonEncoder());
$normalizer = new ObjectNormalizer($classMetadataFactory, new CamelCaseToSnakeCaseNameConverter());
$normalizer->setIgnoredAttributes(array('regions'));
$normalizer->setCallbacks(
array('startDate' => $callback, 'endDate' => $callback, 'validFrom' => $callback, 'validTo' => $callback, 'publicationDate' => $callback, 'updatedAt' => $callback)
);
$this->symfonySerializer = new Serializer([$normalizer], $encoders);
AnnotationRegistry::registerLoader('class_exists');
$this->jmsSerializer = SerializerBuilder::create()
->build();
}
public function testSymfonySerializer()
{
$productsExample = file_get_contents(__DIR__.'/products-example.json');
$productCollection = $this->jmsSerializer->deserialize($productsExample, ProductCollection::class, 'json');
for ($i = 0; $i < 100; $i++) {
$this->symfonySerializer->serialize($productCollection, 'json', ['groups' => ['api']]);
}
file_put_contents(__DIR__ . '/products-example-symfony-serialized.json', $this->symfonySerializer->serialize($productCollection, 'json', ['groups' => ['api']]));
}
public function testJmsSerializer()
{
$productsExample = file_get_contents(__DIR__.'/products-example.json');
$productCollection = $this->jmsSerializer->deserialize($productsExample, ProductCollection::class, 'json');
for ($i = 0; $i < 100; $i++) {
$this->jmsSerializer->serialize($productCollection, 'json', SerializationContext::create()->setGroups(['api']));
}
file_put_contents(__DIR__ . '/products-example-jms-serialized.json', $this->jmsSerializer->serialize($productCollection, 'json', SerializationContext::create()->setGroups(['api'])));
}
} Maybe you find an obvious error in the above test that explains the performance gap.
|
Ok I've not used a profiler right now but there is obviously two things that slow down the Symfony Serializer here:
|
Your |
@iltar Seems intentional for testing serialize method |
@dunglas the metadata extraction caches it in memory anyway from what I've seen. So a cache wouldn't help in a single process test. The But in general I've really hard time to replicate the behavior of the JMS serializer. So currently the serialization is broken and missing properties. Some findings:
|
|
This PR was squashed before being merged into the 2.3 branch (closes #16294). Discussion ---------- [PropertyAccess] Major performance improvement | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #16179 | License | MIT | Doc PR | n/a This PR improves performance of the PropertyAccess component of ~70%. The two main changes are: * caching the `PropertyPath` initialization * caching the guessed access strategy This is especially important for the `ObjectNormalizer` (Symfony Serializer) and the JSON-LD normalizer ([API Platform](https://api-platform.com)) because they use the `PropertyAccessor` class in large loops (ex: normalization of a list of entities). Here is the Blackfire comparison: https://blackfire.io/profiles/compare/c42fd275-2b0c-4ce5-8bf3-84762054d31e/graph The code of the benchmark I've used (with Symfony 2.3 as dependency): ```php <?php require 'vendor/autoload.php'; class Foo { private $baz; public $bar; public function getBaz() { return $this->baz; } public function setBaz($baz) { $this->baz = $baz; } } use Symfony\Component\PropertyAccess\PropertyAccess; $accessor = PropertyAccess::createPropertyAccessor(); $start = microtime(true); for ($i = 0; $i < 10000; ++$i) { $foo = new Foo(); $accessor->setValue($foo, 'bar', 'Lorem'); $accessor->setValue($foo, 'baz', 'Ipsum'); $accessor->getValue($foo, 'bar'); $accessor->getValue($foo, 'baz'); } echo 'Time: '.(microtime(true) - $start).PHP_EOL; ``` This PR also adds an optional support for Doctrine cache to keep access information across requests and improve the overall application performance (even outside of loops). Commits ------- 284dc75 [PropertyAccess] Major performance improvement
… 2.3 (dunglas) This PR was squashed before being merged into the 2.7 branch (closes #16463). Discussion ---------- [PropertyAccess] Port of the performance optimization from 2.3 | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #16179 | License | MIT | Doc PR | n/a Portage of #16294 in the 2.7 branch. Commits ------- aa4cc90 [PropertyAccess] Port of the performance optimization from 2.3
@dunglas The optimization you added reduced the above test case with ObjectNormalizer to half. From 14 seconds to 7 seconds. On PHP 7, the PropertyNormalizer took the same time as the JMS serializer (2.1 seconds). So even the simplest of all normalizers is not faster than the JMS serializer which offers alot more built-in features. |
We found the inheritence problem I was talking about above. Our model implements IteratorAggregate and ArrayAccess which causes all other properties on the model to be ignored in the serialization. This is kinda unexpected. |
@Tobion providing a profile of your script would help finding what should be optimized next. We cannot profile it ourselves, as you haven't provided a full reproducing case above |
This PR was squashed before being merged into the 2.8 branch (closes #16547). Discussion ---------- [Serializer] Improve ObjectNormalizer performance | Q | A | ------------- | --- | Bug fix? | no | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #16179 | License | MIT | Doc PR | n/a Cache attributes detection in a similar way than in #16294 for PropertyAccess. As for the PropertyAccess Component, I'll open another PR (in 2.8 or 3.1) allowing to cache these attributes between requests using Doctrine Cache. @Tobion, can you try this PR with your benchmark to estimate the gain? Commits ------- 683f0f7 [Serializer] Improve ObjectNormalizer performance
IMO, this issue should be reopened. I have profiled Symfony, JMS & Ivory serializer and here you can find a full reproductive case that you can easily profile: https://github.com/egeloen/ivory-serializer-benchmark And the last benchmark result using caching and dev-master for all libraries: https://travis-ci.org/egeloen/ivory-serializer-benchmark/jobs/162590026 It results that Symfony is the slowest even if the cache is used. |
Running your benchmark with last versions of Symfony components (
The Symfony Serializer is faster than JMSSerializer and slower than yours, but nothing significative (IMO) in a real production app. Btw the cache of the PropertyAccess component isn't enabled (not sure if it will change something for a case like this one) and to be fair, you should use the |
Using
|
Thanks for the fast answer, I just update the benchmark according to your feedback |
In our project, alot of time is spent serializing our object graph to JSON using the JMS serializer. So we were looking at alternatives to speed things up. Naturally we now tried the symfony serializer with the recent new features that we would need like groups, property naming strategies etc.
So we did some performance benchmarks to compare JMS serializer and symfony serializer with real-world data. Serializing our rather big object graph (which results in ~130 kB of json) 100 times takes
So the symfony serializer is roughly TWICE as SLOW. No caching was involved. But that shouldn't have influence as the test was run in a single PHP process. That the symfony serializer is even slower is not what we expected since it's code base looks slimmer.
@dunglas any idea what is causing this huge difference?
The text was updated successfully, but these errors were encountered: