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

Performance enhancements for has(), setService(), setFactory(), setShared(), setAlias(), setInvokableClass() and configure() #221

Conversation

fhein
Copy link

@fhein fhein commented Jan 5, 2018

This PR is about performance of the setter APIs (or injector APIs if you like) as well as has(). Here's the current comparison report. Thanks @dantleech for faster than light fix.

benchmark: SetNewServicesBench
+------------------------+--------------------+------------------+--------+
| subject                | suite:develop:mean | suite:PR221:mean | gain   |
+------------------------+--------------------+------------------+--------+
| benchSetService        | 1.924µs            | 0.655µs          |  2,94x |
| benchSetFactory        | 4.183µs            | 1.226µs          |  3,41x |
| benchSetAlias          | 11.222µs           | 1.872µs          |  5,99x |
| benchSetAliasOverrided | 34.617µs           | 1.876µs          | 18,45x |
+------------------------+--------------------+------------------+--------+

This is the develop branch:

Dumped result to SetNewServices.develop.xml
suite: 133ec8900bdebd2cfb961e09ca103052bd993f16, date: 2018-01-05, stime: 22:12:29
+---------------------+------------------------+--------+--------+--------+-----+------------+-------------+-------------+-------------+-------------+------------+--------+--------+
| benchmark           | subject                | groups | params | revs   | its | mem_peak   | best        | mean        | mode        | worst       | stdev      | rstdev | diff   |
+---------------------+------------------------+--------+--------+--------+-----+------------+-------------+-------------+-------------+-------------+------------+--------+--------+
| SetNewServicesBench | benchSetService        |        | []     | 100000 | 100 | 1,375,992b | 1.910110µs  | 1.957413µs  | 1.946003µs  | 2.050120µs  | 0.028245µs | 1.44%  | 1.00x  |
| SetNewServicesBench | benchSetFactory        |        | []     | 100000 | 100 | 1,375,992b | 4.180240µs  | 4.240743µs  | 4.229557µs  | 4.420250µs  | 0.030047µs | 0.71%  | 2.17x  |
| SetNewServicesBench | benchSetAlias          |        | []     | 100000 | 100 | 1,375,992b | 11.440650µs | 11.625965µs | 11.601287µs | 12.160700µs | 0.099540µs | 0.86%  | 5.94x  |
| SetNewServicesBench | benchSetAliasOverrided |        | []     | 100000 | 100 | 1,376,000b | 35.572030µs | 35.950056µs | 35.759907µs | 37.172120µs | 0.312706µs | 0.87%  | 18.37x |
+---------------------+------------------------+--------+--------+--------+-----+------------+-------------+-------------+-------------+-------------+------------+--------+--------+

Here comes this PR:

Dumped result to SetNewServices.setAlias.xml
suite: 133ec89fee8638dd6d892c639ca0556b8090f9b8, date: 2018-01-05, stime: 21:42:44
+---------------------+------------------------+--------+--------+--------+-----+------------+------------+------------+------------+------------+------------+--------+-------+
| benchmark           | subject                | groups | params | revs   | its | mem_peak   | best       | mean       | mode       | worst      | stdev      | rstdev | diff  |
+---------------------+------------------------+--------+--------+--------+-----+------------+------------+------------+------------+------------+------------+--------+-------+
| SetNewServicesBench | benchSetService        |        | []     | 100000 | 100 | 1,347,528b | 0.640030µs | 0.654438µs | 0.650130µs | 0.680040µs | 0.010230µs | 1.56%  | 1.00x |
| SetNewServicesBench | benchSetFactory        |        | []     | 100000 | 100 | 1,347,528b | 1.200070µs | 1.222270µs | 1.217291µs | 1.280070µs | 0.013681µs | 1.12%  | 1.87x |
| SetNewServicesBench | benchSetAlias          |        | []     | 100000 | 100 | 1,347,528b | 1.810100µs | 1.845307µs | 1.848852µs | 1.900110µs | 0.015841µs | 0.86%  | 2.82x |
| SetNewServicesBench | benchSetAliasOverrided |        | []     | 100000 | 100 | 1,347,536b | 1.780100µs | 1.803603µs | 1.794896µs | 1.870110µs | 0.019256µs | 1.07%  | 2.76x |
+---------------------+------------------------+--------+--------+--------+-----+------------+------------+------------+------------+------------+------------+--------+-------+

@fhein fhein changed the title Performance enhancements for setService, setFactory and setShared Performance enhancements for setService, setFactory, setShared and setAlias(!) Jan 5, 2018
@fhein
Copy link
Author

fhein commented Jan 6, 2018

Here is an overall comparison of develop against this PR. The benchs of develop which are faster are for your convenience marked with x. The benchs where this PR outoperforms develop by much are marked with !.

@dantleech: This is a showcase where it would help that the comparison report would pull suite::context (as documented) instead of suite::uuid. I had to manually reformat. Not much fun. And, if you want me to beg, I'll do: For you it's just a finger snip to hardcode a comparison column (like 1.24x as in reports=aggregate). Assume that the first --file is the base, the second --file is the - hopefully - faster implementation. Even if you would do that just for me, I bet, tons of users would use that. Please! Please! Please! Pleeeeaaaaseee? :)

	$ vendor\bin\phpbench report --file=all.develop.xml --file=all.setAlias.xml --report=hein
	benchmark: FetchCachedServicesBench
	+----------------------------------+------------------- +------------------+
	| subject                          | suite:develop:mean | suite:PR221:mean |
	+----------------------------------+--------------------+------------------+
	| benchFetchFactory1               | 0.393µs            | 0.382µs          |
x!      | benchFetchInvokable1             | 0.394µs            | 0.937µs          |
	| benchFetchService1               | 0.391µs            | 0.382µs          |
	| benchFetchAlias1                 | 0.392µs            | 0.385µs          |
	| benchFetchRecursiveAlias1        | 0.392µs            | 0.384µs          |
	| benchFetchRecursiveAlias2        | 0.390µs            | 0.383µs          |
	| benchFetchAbstractFactoryService | 2.396µs            | 2.366µs          |
	+----------------------------------+--------------------+------------------+

	benchmark: FetchNewServiceUsingConfigAbstractFactoryAsFactoryBench
	+-------------------------------------+--------------------+------------------+
	| subject                             | suite:develop:mean | suite:PR221:mean |
	+-------------------------------------+--------------------+------------------+
	| benchFetchServiceWithNoDependencies | 4.615µs            | 4.387µs          |
	| benchBuildServiceWithNoDependencies | 4.138µs            | 4.141µs          |
	| benchFetchServiceDependingOnConfig  | 5.256µs            | 5.114µs          |
	| benchBuildServiceDependingOnConfig  | 4.851µs            | 4.720µs          |
	| benchFetchServiceWithDependency     | 5.249µs            | 4.976µs          |
	| benchBuildServiceWithDependency     | 4.711µs            | 4.659µs          |
	+-------------------------------------+--------------------+------------------+

	benchmark: FetchNewServiceUsingReflectionAbstractFactoryAsFactoryBench
	+-------------------------------------+--------------------+------------------+
	| subject                             | suite:develop:mean | suite:PR221:mean |
	+-------------------------------------+--------------------+------------------+
	| benchFetchServiceWithNoDependencies | 3.684µs            | 3.359µs          |
	| benchBuildServiceWithNoDependencies | 3.141µs            | 3.100µs          |
	| benchFetchServiceDependingOnConfig  | 7.070µs            | 6.767µs          |
x	| benchBuildServiceDependingOnConfig  | 6.503µs            | 6.871µs          |
	| benchFetchServiceWithDependency     | 8.360µs            | 8.090µs          |
	| benchBuildServiceWithDependency     | 7.861µs            | 7.828µs          |
	+-------------------------------------+--------------------+------------------+

	benchmark: FetchNewServiceViaConfigAbstractFactoryBench
	+-------------------------------------+--------------------+------------------+
	| subject                             | suite:develop:mean | suite:PR221:mean |
	+-------------------------------------+--------------------+------------------+
	| benchFetchServiceWithNoDependencies | 5.297µs            | 4.990µs          |
	| benchBuildServiceWithNoDependencies | 4.660µs            | 4.567µs          |
	| benchFetchServiceDependingOnConfig  | 5.867µs            | 5.655µs          |
	| benchBuildServiceDependingOnConfig  | 5.324µs            | 5.181µs          |
	| benchFetchServiceWithDependency     | 5.850µs            | 5.600µs          |
	| benchBuildServiceWithDependency     | 5.238µs            | 5.139µs          |
	+-------------------------------------+--------------------+------------------+

	benchmark: FetchNewServiceViaReflectionAbstractFactoryBench
	+-------------------------------------+--------------------+------------------+
	| subject                             | suite:develop:mean | suite:PR221:mean |
	+-------------------------------------+--------------------+------------------+
	| benchFetchServiceWithNoDependencies | 3.333µs            | 3.100µs          |
	| benchBuildServiceWithNoDependencies | 2.741µs            | 2.731µs          |
	| benchFetchServiceDependingOnConfig  | 6.833µs            | 6.566µs          |
x	| benchBuildServiceDependingOnConfig  | 6.168µs            | 6.452µs          |
	| benchFetchServiceWithDependency     | 8.211µs            | 7.988µs          |
	| benchBuildServiceWithDependency     | 7.543µs            | 7.519µs          |
	+-------------------------------------+--------------------+------------------+

	benchmark: FetchNewServicesBench
	+----------------------------------+--------------------+------------------+
	| subject                          | suite:develop:mean | suite:PR221:mean |
	+----------------------------------+--------------------+------------------+
x	| benchFetchFactory1               | 2.620µs            | 2.627µs          |
	| benchBuildFactory1               | 2.165µs            | 2.129µs          |
	| benchFetchInvokable1             | 3.025µs            | 2.878µs          |
	| benchBuildInvokable1             | 2.331µs            | 2.327µs          |
	| benchFetchService1               | 0.392µs           	| 0.383µs          |
	| benchFetchFactoryAlias1          | 2.152µs            | 2.141µs          |
x	| benchBuildFactoryAlias1          | 2.151µs            | 2.162µs          |
	| benchFetchRecursiveFactoryAlias1 | 2.158µs            | 2.145µs          |
x	| benchBuildRecursiveFactoryAlias1 | 2.145µs            | 2.147µs          |
x	| benchFetchRecursiveFactoryAlias2 | 2.148µs            | 2.158µs          |
	| benchBuildRecursiveFactoryAlias2 | 2.156µs            | 2.147µs          |
x	| benchFetchAbstractFactoryFoo     | 2.300µs            | 2.314µs          |
	| benchBuildAbstractFactoryFoo     | 1.860µs            | 1.836µs          |
	+----------------------------------+--------------------+------------------+

	benchmark: HasBench
	+-------------------------+--------------------+------------------+
	| subject                 | suite:develop:mean | suite:PR221:mean |
	+-------------------------+--------------------+------------------+
	| benchHasFactory1        | 0.444µs            | 0.442µs          |
	| benchHasInvokable1      | 0.448µs            | 0.443µs          |
	| benchHasService1        | 0.445µs            | 0.444µs          |
x	| benchHasAlias1          | 0.453µs            | 0.456µs          |
x	| benchHasRecursiveAlias1 | 0.456µs            | 0.457µs          |
	| benchHasRecursiveAlias2 | 0.459µs            | 0.455µs          |
	| benchHasAbstractFactory | 0.792µs            | 0.780µs          |
x	| benchHasNot             | 0.825µs            | 0.831µs          |
	+-------------------------+--------------------+------------------+

	benchmark: SetNewServicesBench
	+------------------------+--------------------+------------------+
	| subject                | suite:develop:mean | suite:PR221:mean |
	+------------------------+--------------------+------------------+
!	| benchSetFactory        | 4.238µs            | 1.224µs          |
!	| benchSetAlias          | 11.501µs           | 3.011µs          |
!	| benchSetAliasOverrided | 36.141µs           | 3.001µs          |
!	| benchSetService        | 1.924µs            | 0.655µs          |
	+------------------------+--------------------+------------------+

@dantleech
Copy link

dantleech commented Jan 6, 2018

This is a showcase where it would help that the comparison report would pull suite::context (as documented) instead of suite::uuid. I had to manually reformat. Not much fun

It uses UUID because the context might not be (and usually isn't) set. But it is relatively easy to change the behavior --report='extends: compare, compare: [ context ]' or similar.

Could you create an issue on PHPBench with the exact format of the report that you're after?

@muglug
Copy link

muglug commented Jan 6, 2018

A 970-word essay imploring the maintainers to accept your PR (before they've added a single comment) seems like the wrong approach to open source contributing.

@fhein
Copy link
Author

fhein commented Jan 6, 2018 via email

@fhein
Copy link
Author

fhein commented Jan 6, 2018

Essays removed. I felt somewhat treated like McStupid in the first time (other PRs). Did not understand why, And with a bottle of wine input I tend to get verbous.

@weierophinney
Copy link
Member

@fhein We perform code review for a number of reasons:

  • To review correctness of the patch: does it do what it states?
  • To point out inconsistencies in coding style. The components become harder to maintain when they are a patchwork of different coding practices. As such, you, as the patch author, might disagree with a change requested, but the reviewer is often requesting a change to make the patch consistent with other code in the component or the other components we maintain.

If we are not pointing out the why behind a request, ask. But do so in a collegial way. For instance, "I disagree with that request; why should I do that?" is combative and defensive, while "Could you explain the rationale behind that request? Is it part of a coding style? If so, can you provide some examples for me?" indicates a collaborative approach indicating you're willing to learn. If the reviewer cannot provide a rationale, then you can point out your own, so that we can learn.

Additionally, in some cases, one reviewer will ask another to jump in. In a previous patch of yours, @Ocramius was asking for my opinion on a potential future change regarding aliases. This may not affect the patch you're making, but if a future change might make the need for your patch obsolete, there's no point in us making a change we do not plan to maintain. Having a discussion around what we choose to maintain is important.

Finally, I read your original essay, and I want to point out something: your aspersions at "how could such code even make it into v3" are totally out of line. Why? Because in a year or two, somebody else is going to come along with a huge performance patch, and ask the same question, and now you are the target of that. Asking such questions is in no way helpful. Software evolves. The language evolves. Our understanding of the capabilities of the language evolves. As such, treat everyone as you would ask us to treat you. When in doubt, refer to our Code of Conduct.

@fhein
Copy link
Author

fhein commented Jan 8, 2018

I understand, that there can be rationales behind requests to change a PR, which may not be obvious for the contributor. And that the contributor should ask then.

But please allow a review of that particular request, which literally was 'Variable is redundant'.

My code was like

public function textXYZ() {
    $config = [ ... ];
    $sm =new ServiceManager($config);
    // following code did not use $config
}    

I did it the same way the other test functions do to be compliant with the coding style.
In ServiceManagerTest.php please review

 testSetAliasShouldWorkWithRecursiveAlias()
 testAliasToAnExplicitServiceShouldWork()
 testSharedServicesReferencingAliasShouldBeHonored()
 testSharedServicesReferencingInvokableAliasShouldBeHonored()
 testMapsNonSymmetricInvokablesAsAliasPlusInvokableFactory()
 testMapsOneToOneInvokablesAsInvokableFactoriesInternally()
 testShareability($sharedByDefault, $serviceShared, $serviceDefined, $shouldBeSameInstance)
 testCanWrapCreationInDelegators()

It actually did not even come to my mind to ask "Could you explain the rationale behind that request? Is it part of a coding style? If so, can you provide some examples for me?" What came to my mind, was "What variable do you mean? $config?" and that's what I asked.

I did as requested. Now my test looks different from the others. I still do not understand the rationale behind that. Could you please explain?

@Ocramius
Copy link
Member

Ocramius commented Jan 8, 2018 via email

@fhein
Copy link
Author

fhein commented Jan 8, 2018

Am I supposed to know that?

@Ocramius
Copy link
Member

Ocramius commented Jan 8, 2018 via email

@fhein
Copy link
Author

fhein commented Jan 8, 2018

Thanks for the rationale. It's very personal.

Regarding the optimizations of the 'setters'. I consider it as bad practice to discard caller supplied information and regather it again. This was done in all setters. What I mean is

function setService($name, $service) 
{
    // here we know already that the user wants to set a service
    $this->configure( ['services' => [$name, $service]]); // here we drop that information
    // configure then has to find out again, what (in cases routed from setXYZ) the
    // the user wanted via isset($config['services'])
}

Is that really a matter of software evolution and PHP language evolution? I don't think so. But maybe I do not have the whole picture. Please explain.

@Ocramius
Copy link
Member

Ocramius commented Jan 8, 2018 via email

@fhein
Copy link
Author

fhein commented Jan 8, 2018

Nicer to read than the performance optimized versions? That's how they look now.

public function setService($name, $service)
{
    $this->validate($name);
    $this->services[$name] = $service;
}

Performance and readibility don't mutually exclude each other.

@weierophinney
Copy link
Member

I consider it as bad practice to discard caller supplied information and regather it again.

configure() was introduced in v3 as we were striving to make the container immutable once configured. Additionally, we do a few bits of work in there that affect configuration:

  • invokables become factory entries, pointing a service name at the InvokableFactory, and, in some cases, alias entries.
  • alias resolution (to ensure validity)
  • override detection

This last part is fairly important; after the container is configured initially, we do not want somebody altering it such that a different service is returned for two subsequent requests to the same service name. This can only be done if all setters use the same logic, and configure() is the correct place.

As @Ocramius notes, the various setter methods should generally not be used unless you are building the container service entries programmatically. Typical usage (see the zend-mvc and Expressive skeletons) uses a configuration-driven approach instead, where all services are injected during instantiation.

@fhein
Copy link
Author

fhein commented Jan 8, 2018

As of my PR the setters and configure utilize the same implemenations to address your bullets. There is no code duplication. validate guards against overriding. private doSetAlias does all of the alias resolution inclduding cycle detection. Not more required to handle all of this than

private function doSetAlias($alias, $target)
{
    $this->aliases[$alias] = $target;
    $this->resolvedAliases[$alias] =
        isset($this->resolvedAliases[$target]) ? $this->resolvedAliases[$target] : $target;

    if ($alias === $this->resolvedAliases[$alias]) {
        throw CyclicAliasException::fromAliasesMap([$alias]);
    }

    if (in_array($alias, $this->resolvedAliases)) {
        $r = array_intersect($this->resolvedAliases, [ $alias ]);
        foreach ($r as $name => $service) {
            $this->resolvedAliases[$name] = $target;
        }
    }
}

And even this is not a matter of software and language evolution. See Knuth, The Art of Computer Programming. Even when I studied serveral decades ago we had that as a recommended reference.

If you review the code, you will find that all setters use the same logic as configure. A setter uses the logic for the particular single request. configure uses said logic to handle all of the settings (requests) provided.

This PR also maintains the current handling of Invokables as you described it. This PR does not contradict any of the goals you are striving at. The only thing which changes is performance.

Please explain the rationale behind deprecating Invokables. Please see #223.

@Ocramius
Copy link
Member

Ocramius commented Jan 8, 2018 via email

@fhein
Copy link
Author

fhein commented Jan 8, 2018

Thanks. I will think about all the implications that has. Nothing for a quick shot reply.

Anyway, this PR does not touch how Invokables are handled. Semantics at all is not touched by this PR. It's about performance only.

I opened issues to discuss feature requests and changes that could change behaviour . (#222, #223, #224, #225).

@fhein
Copy link
Author

fhein commented Jan 8, 2018

The setters are not hot path

The setters are on my path, at least. And on the path of @malinink also. See #112.

@fhein fhein changed the title Performance enhancements for setService, setFactory, setShared and setAlias(!) Performance enhancements for has(), setService(), setFactory(), setShared() and setAlias()(!) Jan 9, 2018
Copy link
Member

@Ocramius Ocramius left a comment

Choose a reason for hiding this comment

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

Only skimmed through the patch and went through highlights. The diff is too messed up due to too many changes at the same time, so it has to be reviewed again as if this was completely new code.

use function sprintf;

class CyclicAliasException extends InvalidArgumentException
{
public static $cycle;
Copy link
Member

Choose a reason for hiding this comment

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

private

* @param string[] $aliases map of referenced services, indexed by alias name (string)
*
* @return self
*/
public static function fromAliasesMap(array $aliases)
public static function fromCyclicAlias($alias, $aliases)
Copy link
Member

Choose a reason for hiding this comment

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

This is a BC break

Copy link
Member

Choose a reason for hiding this comment

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

Agreed; create a new method, and leave the original in place. We can mark the original deprecated if it is no longer used internally.

* @return array|null
*/
private static function getCycleFor(array $aliases, $alias)
public function getCycle()
Copy link
Member

Choose a reason for hiding this comment

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

private

Copy link
Member

Choose a reason for hiding this comment

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

Also:

  • Why is it non-static?
  • I don't see this method ever used, so why does it exist?

@@ -429,7 +422,8 @@ private function configureAliases(array $aliases)
*/
public function setAlias($alias, $target)
{
$this->configure(['aliases' => [$alias => $target]]);
$this->validate($alias);
Copy link
Member

Choose a reason for hiding this comment

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

The naming validate() is too shallow - need to roughly express what it does in the method name

Copy link
Member

Choose a reason for hiding this comment

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

Agreed. There's another consideration as well: AbstractPluginManager also defines a validate() method, used to determine if an instance is a valid plugin. As such, having a validate() method in both could lead to a conflict and unexpected results.

Please rename the method to validateServiceName.

@@ -248,21 +253,26 @@ public function build($name, array $options = null)
*/
public function has($name)
{
$name = $this->resolvedAliases[$name] ?? $name;
Copy link
Member

Choose a reason for hiding this comment

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

When rebasing on develop, you will notice that this change leads to a test failure

Copy link
Author

Choose a reason for hiding this comment

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

This PR is based on develop. Please explain a little bit more. All tests pass locally.

@@ -519,59 +515,52 @@ public function setService($name, $service)
*/
public function setShared($name, $flag)
{
$this->configure(['shared' => [$name => (bool) $flag]]);
$this->validate($name);
$this->shared[$name] = (bool) $flag;
Copy link
Member

Choose a reason for hiding this comment

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

We should just add bool to the argument, but that may indeed be a BC break for subclasses and consumers being too lax. Possibly an issue for 4.0.0 :-\

@@ -18,137 +18,86 @@ class CyclicAliasExceptionTest extends TestCase
/**
* @dataProvider aliasesProvider
*
* @param string conflicting alias key
Copy link
Member

Choose a reason for hiding this comment

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

Changes in this file should be completely reverted

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.

I've marked a bunch of changes I would like to see, but there are two general ones I only reference a few times throughout:

  • Check for function invocations. All PHP functions used should be imported explicitly at the top of the file. During invocation, you do not need to qualify them, since they are already imported.
  • I am not a fan of having method names called do<Original Method Name Here>(); I feel it's lazy and generally doesn't indicate the actual purpose of the method. I've indicated two places where I'd change these specifically, and provide more semantically informed names when I do.

More concerning, however, is that in some cases, you create code duplication in order to save function calls, but in others, you introduce more function calls (e.g., ServiceManager::configure() now performs a function call for each and every abstract factory registered, instead of one for the entire set; it does the same when resolving aliases, adding a function call per alias instead of one for all aliases). This makes me suspect that initial creation of the container will be more expensive for the primary use case of a configuration-driven container. Interestingly, you do not provide benchmark comparisons for that scenario, and that is one of the ones we optimized for for version 3.

I recognize that you and one other person have indicated you use the setter methods as your primary interface. That said, they are not the primary interface we intend at this time. Both the zend-mvc skeleton and Expressive assume a "configure at instantiation" approach, and the zend-config-aggregator component exists to help aid such scenarios. Considering that these are the primary targets for zend-servicemanager, these are the needs where any performance optimizations need to focus.

As such, can you please provide such benchmarks? They would need to address a container with a mix of aliases, factories, invokables, delegators, abstract factories, etc., with multiple definitions for each service type, ideally in the dozens or more.

{
$this->validate($service);
parent::setService($name, $service);
}
Copy link
Member

Choose a reason for hiding this comment

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

I would argue this is a bugfix, and should be submitted separately, along with a unit test for the changed behavior.

Copy link
Author

Choose a reason for hiding this comment

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

To explain: AbstractPluginManager relied on ServiceManager::setService to call configure. Therefore APM implemented configure to do plugin validation. This broke with the new setService implementation of SM. So I had to catch setService in APM.

* @param string[] $aliases map of referenced services, indexed by alias name (string)
*
* @return self
*/
public static function fromAliasesMap(array $aliases)
public static function fromCyclicAlias($alias, $aliases)
Copy link
Member

Choose a reason for hiding this comment

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

Agreed; create a new method, and leave the original in place. We can mark the original deprecated if it is no longer used internally.

* @return array|null
*/
private static function getCycleFor(array $aliases, $alias)
public function getCycle()
Copy link
Member

Choose a reason for hiding this comment

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

Also:

  • Why is it non-static?
  • I don't see this method ever used, so why does it exist?

// /**
// * @var string[]
// */
// private $resolvedAliases = [];
Copy link
Member

Choose a reason for hiding this comment

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

Please do not leave commented-out functionality; if it needs to be revisited, that's what VCS history is for.

$object = $this->doCreate($resolvedName);

// Cache the object for later, if it is supposed to be shared.
if (($sharedService)) {
Copy link
Member

Choose a reason for hiding this comment

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

Remove the duplicate parens, please.

*/
private function validateOverrideSet(array $services, $type)
private function doAddAbstractFactory($abstractFactory)
Copy link
Member

Choose a reason for hiding this comment

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

Please rename this method to resolveAbstractFactoryInstance().

foreach ($services as $service) {
if (isset($this->services[$service])) {
$detected[] = $service;
if (\is_string($abstractFactory) && \class_exists($abstractFactory)) {
Copy link
Member

Choose a reason for hiding this comment

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

If these functions are not imported already, please do. Then drop the global resolution, as they'll already be imported.

Copy link
Author

Choose a reason for hiding this comment

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

Ok, I will check the import of all functions. But that's not code I touched. I think, in_array is the only one I introduced.

if (isset($this->services[$service])) {
$detected[] = $service;
if (\is_string($abstractFactory) && \class_exists($abstractFactory)) {
//Cached string
Copy link
Member

Choose a reason for hiding this comment

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

Space between a comment opener and the comment text, please.

Copy link
Author

Choose a reason for hiding this comment

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

Ok again. But I did not write that.

}

if (empty($detected)) {
if ($abstractFactory instanceof Factory\AbstractFactoryInterface) {
$abstractFactoryObjHash = \spl_object_hash($abstractFactory);
Copy link
Member

Choose a reason for hiding this comment

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

spl_object_hash is already imported, so no need to globally qualify it.

Copy link
Author

Choose a reason for hiding this comment

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

I did not write that either. I'll correct.

sprintf(
'An invalid abstract factory was registered; resolved to class "%s" ' .
'which does not exist; please provide a valid class name resolving ' .
'to an implementation of %s',
Copy link
Member

Choose a reason for hiding this comment

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

Put concatenation operators on the following lines, please.

Also, inline the sprintf with the line calling new, in order to reduce a level of indentation.

Personally, I'd likely create named constructors for this and the following throw statements, in order to make the code here simpler: throw InvalidArgumentException::forInvalidAbstractFactoryClassName($abstractFactory);

Copy link
Author

Choose a reason for hiding this comment

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

I'll do. But I did not touch that either. :)

@fhein
Copy link
Author

fhein commented Jan 10, 2018

Currently one test on travis fails in line 294 of ServiceManagerTest.php:
ZendTest\ServiceManager\ServiceManagerTest::testAbstractFactoryShouldBeCheckedForResolvedAliasesInsteadOfAliasNameName

My ServiceManagerTest.php does not contain named test end ends with line 287. I compared several branches, but I could not find a version of this file containing that test. Please help.

@fhein
Copy link
Author

fhein commented Jan 10, 2018

Thank you for the thorough review. Highly appreciated. In particular, thanks @weierophinney for the pointer to configure() performance.

There is a benchmark already in place (FetchNewServiceManagerBench.php) for configure.

I run my tests usually with the whole suite --revs=100000. Because FetchNewServiceManager can not stand that much revs (or better, my machine can't stand it) I left that out the last days. Shame on me. :(

I was shocked to see that configure() slowed down dramatically. I tracked that down locally to array_intersect which is horribly expensive.

To proof that configure() can get accelerated significantly I adapted Tarjan's Strongly Connected Components Algorithm to the alias resolution and cycle detection problem.

Please see the comparison against develop following in the next note.

@fhein
Copy link
Author

fhein commented Jan 10, 2018

Here are the benchmark results. The test creates a SM with 1,001 factories, invokables, services and aliases each.

$ vendor\bin\phpbench report --file=FetchNewServiceManagerBench.develop.xml --file=FetchNewServiceManagerBench.221.xml --report=hein
benchmark: FetchNewServiceManagerBench
+----------------------------------+--------------------+------------------+--------+
| subject                          | suite:develop:mean | suite:PR221:mean | factor |
+----------------------------------+--------------------+------------------+--------+
| benchFetchServiceManagerCreation | 871.500µs          | 561.000µs        | 1.55x  |
+----------------------------------+--------------------+------------------+--------+

@fhein fhein changed the title Performance enhancements for has(), setService(), setFactory(), setShared() and setAlias()(!) Performance enhancements for has(), setService(), setFactory(), setShared(), setAlias() and configure() Jan 10, 2018
@fhein
Copy link
Author

fhein commented Jan 10, 2018

May I please ask again for the ServiceManagerTest.php which is different from my local version.

@Ocramius
Copy link
Member

Ocramius commented Jan 10, 2018 via email

@fhein
Copy link
Author

fhein commented Jan 10, 2018

To upstream develop? Says: Already up to date.

@Ocramius
Copy link
Member

Ocramius commented Jan 10, 2018 via email

*
* @return self
*/
public static function fromCyclicAlias($alias, $aliases)
Copy link
Member

Choose a reason for hiding this comment

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

Looks like we can have typehint array on the second parameter

@@ -8,10 +8,32 @@
namespace Zend\ServiceManager\Exception;

use InvalidArgumentException as SplInvalidArgumentException;
use Zend\ServiceManager\Initializer\InitializerInterface;
use Zend\ServiceManager\AbstractFactoryInterface;
Copy link
Member

Choose a reason for hiding this comment

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

Alphabetical order of imports please.

use function is_callable;
use function is_object;
use function is_string;
use function spl_autoload_register;
use function spl_object_hash;
use function sprintf;
use function trigger_error;
use function in_array;
Copy link
Member

Choose a reason for hiding this comment

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

Alphabetical order of imported function please.

Copy link
Author

Choose a reason for hiding this comment

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

I addressed this requests in #227 by mistake. But because #227 is more or less work missing here, that might be ok. Please tell me, if not.

@fhein
Copy link
Author

fhein commented Jan 16, 2018

@weierophinney: Thank you very much. I feel honoured that this piece of work made it. :)

@weierophinney weierophinney merged commit 1ccde79 into zendframework:develop Jan 16, 2018
weierophinney added a commit that referenced this pull request Jan 16, 2018
weierophinney added a commit that referenced this pull request Jan 16, 2018
@weierophinney
Copy link
Member

Thanks, @fhein; this one is now merged to develop.

@weierophinney
Copy link
Member

@fhein Also, be sure to rebase your other open PRs based on this one against current develop; I did a number of minor consistency changes when merging that may cause conflicts.

@fhein fhein deleted the PERFORMANCE-Optimize-configuration-speed branch January 16, 2018 23:05
@fhein
Copy link
Author

fhein commented Jan 17, 2018

@weierophinney; Please be aware, that this PR still has that nasty bug I discovered while adding tests to increase code coverage. None of the current tests fails on this bug. A test and a bug fix is provided in #227.
What to do?

@fhein
Copy link
Author

fhein commented Jan 17, 2018

@weierophinney; Minimum action required: Copy and replace mapAliasesToTargets member function from #227 to develop. This will not include the newly introduced test, but it will restore a stable state for develop. Alternatively you may consider to accept #227. This will add the test which fails on current develp also.

Future PRs will be more self-contained. I am sorry.

@Ocramius
Copy link
Member

Ocramius commented Jan 17, 2018 via email

fhein added a commit to fhein/zend-servicemanager that referenced this pull request Jan 18, 2018
fhein added a commit to fhein/zend-servicemanager that referenced this pull request Jan 18, 2018
PERFORMANCE-Optimize-configuration-speed-contd
fhein added a commit to fhein/zend-servicemanager that referenced this pull request Jan 18, 2018
fhein added a commit to fhein/zend-servicemanager that referenced this pull request Jan 24, 2018
fhein added a commit to fhein/zend-servicemanager that referenced this pull request Jan 25, 2018
fhein added a commit to fhein/zend-servicemanager that referenced this pull request Jan 30, 2018
fhein added a commit to fhein/zend-servicemanager that referenced this pull request Jan 30, 2018
fhein added a commit to fhein/zend-servicemanager that referenced this pull request Feb 2, 2018
fhein pushed a commit to fhein/zend-servicemanager that referenced this pull request Feb 7, 2018
fhein added a commit to fhein/zend-servicemanager that referenced this pull request Feb 7, 2018
fhein pushed a commit to fhein/zend-servicemanager that referenced this pull request Feb 7, 2018
fhein added a commit to fhein/zend-servicemanager that referenced this pull request Feb 7, 2018
fhein added a commit to fhein/zend-servicemanager that referenced this pull request Feb 7, 2018
fhein added a commit to fhein/zend-servicemanager that referenced this pull request Feb 8, 2018
fhein added a commit to fhein/zend-servicemanager that referenced this pull request Feb 8, 2018
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.

6 participants