Skip to content
This repository has been archived by the owner on Feb 6, 2020. It is now read-only.

Performance optimize invokables (bug fix also) #232

Closed

Conversation

fhein
Copy link

@fhein fhein commented Jan 19, 2018

Current master and develop branches provide a special treatment for invokables within sm configuration phase. This treatment requires to loop through the invokables array and perform actions and create an InvokableFactory entry for each Invokable and an alias if the name of the invokable is different from the invokable class name. This is a major bottleneck for configuration speed. This treatment alone takes about 30% of overall configuration time.

Here is the benchmark comparing master to this PR.

benchmark: FetchNewServiceManagerBench
+----------------------------------+-------------------+------------------+--------+
| subject                          | suite:master:mean | suite:PR232:mean | factor |
+----------------------------------+-------------------+------------------+--------+
| benchFetchServiceManagerCreation | 872.000µs         | 218.000µs        | 4x     |
+----------------------------------+-------------------+------------------+--------+

The replacement of an invokable by a factory and an alias can also be considered as a bug. Current service resolution precedence is:

  1. Services
  2. Aliases
  3. Delegators
  4. Factories
  5. Abstract Factories

As @Ocramius said, there are use cases, where users want to provide a factory for an invokable class, because the particular application wants to apply configuration to the invokable object.

By defining an alias for the invokable class, this factory gets disabled efffectively.

This PR changes the treatment of invokables. They are stored in an $invokables array like the other items.
Now invokables get resolved to an InvokableFactory in getFactory now. The new resolution precedence is

  1. Services
  2. Aliases
  3. Delegators
  4. Factories
  5. Invokables
  6. Abstract Factories

I tried to discuss the problems of alias resolution precedence in #222. I do not know if there are discussions around that topic actually. At least nobody wanted to share his/her thoughts since I published that article weeks ago. Trouble with aliases could get eliminated by reducing them to aliases. That would mean to change the resolution precedence to

  1. Services
  2. Delegators
  3. Factories
  4. Invokables
  5. Abstract Factories
  6. Aliases

@fhein fhein changed the title Performance optmize invokables Performance optimize invokables Jan 19, 2018
@fhein fhein changed the title Performance optimize invokables Performance optimize invokables (bug fix also) Jan 19, 2018
Copy link
Member

@weierophinney weierophinney left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This patch appears to have a ton of changes from other PRs that have already been merged; additionally, it reverts changes that were already made for internal consistency. Please rebase and revert changes as marked so we can review the specific changes you're proposing.

/**
* @link http://github.com/zendframework/zend-servicemanager for the canonical source repository
* @copyright Copyright (c) 2016 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On this and other file-level docblocks for new files introduced by this patch, please do the following:

  • Use https scheme for all URLs
  • Date is 2018

public function benchSetInvokableClass()
{

// @todo @link https://github.com/phpbench/phpbench/issues/304
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On this and the following new methods:

  • Move the @todo annotation to a method-level docblock
  • Remove the blank line at the top of the method

}

$this->mapAliasToTarget($alias, $target);
throw ContainerModificationsNotAllowedException::fromExistingService($alias);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert this change; exceptions should be thrown within conditionals for internal consistency and with other components.

Copy link
Author

@fhein fhein Jan 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My idea behind that is, that by default a configuration and proposed (called) operations on it should get assumed to be error free (they finally are after all errors got fixed). This assumed, the || clause (with appropriate ordering, which I am not quite sure yet what the best is (my assumption here is that allowOverride is (should be) false in most production environments)) can pass successfully (true) after evaluation of just the first expression of the || clause at a high probability. If the second part of the clause needs evalution at all, we are half-way down the road to throw.

Inverting the condition would mean, that one maybe two evalutions get necessary to determine not to throw. The assumption here is more pessimistic. Because in most cases we are not gonna throw because people don't request things which are not allowed (my assumption). it's more likely that we have to evaluate both parts of the && condition to find out that everything is still ok.

So I believe, there is a (probable) slight performance advantage for error free configs and proper requests. That's what I am heading for. But it's all about assuming things and and estimating probability here.

What do you think? Did I forget to consider something important?

I hope my English is good enough to express my thoughts.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two points I was making:

  • Consistency. Throughout the framework, we generally (as in greater than 99% of cases) throw from a conditional. The method body is where the work of the method is done. In these cases, you're setting an alias, setting a factory, etc. That's the work of the methods. The conditional should be used to flag exceptional cases that prevent that work from happening.

  • Common use case. The primary use case for zend-servicemanager is to configure once. These setters are not the primary path. As such, having them readable and consistent with the code base is more important than micro-optimizations from conditionals.

If you are worried about the conditional performance, due to needing to evaluate two conditions, put the one most likely to be false first — which is how these were done (i.e., checking to see if the service/alias/factory/etc. has already been set). If the service has not been previously set, only the first part of the condition will be evaluated, and we'll then move on to the next statement.

On top of that, boolean checks (&& ! $this->allowOverride) are quite fast, and inverting the method workflow for this is truly a micro-optimization at the expense of consistency and readability.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok.

}

$this->createAliasesAndFactoriesForInvokables([$name => $class ?? $name]);
throw ContainerModificationsNotAllowedException::fromExistingService($name);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invert the conditional and throw from the conditional, please.

}

$this->factories[$name] = $factory;
throw ContainerModificationsNotAllowedException::fromExistingService($name);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert this change; exceptions should be thrown within conditionals for internal consistency and with other components.

}
$this->services[$name] = $service;
throw ContainerModificationsNotAllowedException::fromExistingService($name);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert this change; exceptions should be thrown within conditionals for internal consistency and with other components.

}

$this->shared[$name] = (bool) $flag;
throw ContainerModificationsNotAllowedException::fromExistingService($name);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert this change; exceptions should be thrown within conditionals for internal consistency and with other components.

* connected components (i.e. cycles in our case), we throw.
* If nodes are not strongly connected (i.e. resolvable in
* our case), they get resolved.
*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes in this method appear to be from #230; please rebase, so that this patch only contains the changes you propose.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest I rebase to #231, if it be accepted. With #231 I did much you requested.

*/
private function resolveAbstractFactoryInstance($abstractFactory)
private function resolveAbstractFactories($abstractFactories)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes in this method appear to be from a previously merged commit; please rebase, so that this patch only contains the changes you propose.

]);
$this->assertSame($sm->get('alias1'), $sm->get('alias2'));
$this->assertSame($sm->get(stdClass::class), $sm->get('alias1'));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two changes are from #230; please rebase.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest I rebase to #231, if that can get accepted. With #231 I did much you requested.

@fhein fhein force-pushed the PERFORMANCE-Optmize-Invokables branch from 5f84975 to 0f6b631 Compare January 24, 2018 22:28
@fhein fhein force-pushed the PERFORMANCE-Optmize-Invokables branch from 0f6b631 to 0b51895 Compare January 24, 2018 22:36
@fhein
Copy link
Author

fhein commented Jan 29, 2018

Closed because this primarily a bug fix for invokable handling. See #242.

@fhein fhein closed this Jan 29, 2018
@fhein fhein deleted the PERFORMANCE-Optmize-Invokables branch January 29, 2018 17:56
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants