Description
The problem
Recently some people have raised concerns about Translator component performance (see #13676 for example). I was inspecting the dumped container for production environment to get more information about this component and I saw the following problems:
1. All translation loaders and dumpers are instantiated and loaded, no matter which ones you use:
protected function getTranslation_LoaderService()
{
$a = $this->get('translation.loader.xliff');
$this->services['translation.loader'] = $instance = new \Symfony\Bundle\FrameworkBundle\Translation\TranslationLoader();
$instance->addLoader('php', $this->get('translation.loader.php'));
$instance->addLoader('yml', $this->get('translation.loader.yml'));
$instance->addLoader('xlf', $a);
$instance->addLoader('xliff', $a);
$instance->addLoader('po', $this->get('translation.loader.po'));
$instance->addLoader('mo', $this->get('translation.loader.mo'));
$instance->addLoader('ts', $this->get('translation.loader.qt'));
$instance->addLoader('csv', $this->get('translation.loader.csv'));
$instance->addLoader('res', $this->get('translation.loader.res'));
$instance->addLoader('dat', $this->get('translation.loader.dat'));
$instance->addLoader('ini', $this->get('translation.loader.ini'));
$instance->addLoader('json', $this->get('translation.loader.json'));
return $instance;
}
protected function getTranslation_Dumper_QtService()
{
return $this->services['translation.dumper.qt'] = new \Symfony\Component\Translation\Dumper\QtFileDumper();
}
protected function getTranslation_Dumper_ResService()
{
return $this->services['translation.dumper.res'] = new \Symfony\Component\Translation\Dumper\IcuResFileDumper();
}
// ...
Same thing happens with the translation dumpers:
protected function getTranslation_WriterService()
{
$this->services['translation.writer'] = $instance = new \Symfony\Component\Translation\Writer\TranslationWriter();
$instance->addDumper('php', $this->get('translation.dumper.php'));
$instance->addDumper('xlf', $this->get('translation.dumper.xliff'));
$instance->addDumper('po', $this->get('translation.dumper.po'));
$instance->addDumper('mo', $this->get('translation.dumper.mo'));
$instance->addDumper('yml', $this->get('translation.dumper.yml'));
$instance->addDumper('ts', $this->get('translation.dumper.qt'));
$instance->addDumper('csv', $this->get('translation.dumper.csv'));
$instance->addDumper('ini', $this->get('translation.dumper.ini'));
$instance->addDumper('json', $this->get('translation.dumper.json'));
$instance->addDumper('res', $this->get('translation.dumper.res'));
return $instance;
}
2. All resources for all locales are loaded, no matter which ones you use:
protected function getTranslator_DefaultService()
{
// ...
$instance->setFallbackLocales(array(0 => 'es'));
$instance->addResource('xlf', ($this->targetDirs[3].'/vendor/symfony/symfony/src/Symfony/Component/Validator/Resources/translations/validators.af.xlf'), 'af', 'validators');
$instance->addResource('xlf', ($this->targetDirs[3].'/vendor/symfony/symfony/src/Symfony/Component/Validator/Resources/translations/validators.ar.xlf'), 'ar', 'validators');
$instance->addResource('xlf', ($this->targetDirs[3].'/vendor/symfony/symfony/src/Symfony/Component/Validator/Resources/translations/validators.az.xlf'), 'az', 'validators');
$instance->addResource('xlf', ($this->targetDirs[3].'/vendor/symfony/symfony/src/Symfony/Component/Validator/Resources/translations/validators.bg.xlf'), 'bg', 'validators');
$instance->addResource('xlf', ($this->targetDirs[3].'/vendor/symfony/symfony/src/Symfony/Component/Validator/Resources/translations/validators.ca.xlf'), 'ca', 'validators');
$instance->addResource('xlf', ($this->targetDirs[3].'/vendor/symfony/symfony/src/Symfony/Component/Validator/Resources/translations/validators.cs.xlf'), 'cs', 'validators');
$instance->addResource('xlf', ($this->targetDirs[3].'/vendor/symfony/symfony/src/Symfony/Component/Validator/Resources/translations/validators.cy.xlf'), 'cy', 'validators');
// ... hundreds of additional lines
}
The solution
1. We could solve this problem with a technique similar to the templating engines selector:
framework:
# ...
templating:
engines: ['twig', 'php']
In the case of translator
:
framework:
# ...
translator:
loaders: ['yml', 'xliff']
dumpers: ['php']
To maintain backwards compatibility, these options could be null
by default, meaning that all loaders/dumpers should be loaded.
2. We could solve this problem defining a new active_locales
option to explicitly define the list of locales which will be used in the application:
framework:
# ...
default_locale: 'en'
active_locales: ['es', 'en', 'fr', 'de']
Again, to maintain backwards compatibility, this new option would default to null
to load all resources for all locales.
The benchmark
In my tests, I modified the generated container commenting/removing the lines that instantiate all the unused loaders/dumpers and I also removed all the translation resources files except the ones for the active locale. A quick before/after comparison made with Blackfire gave me around 10% performance increase in CPU time, 2.64% memory consumption reduction and about 600 less PHP function calls.