Skip to content
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

Reduce calls to SplFileInfo::realpath() in the Magento\Setup\Module\Di\Code\Reader\ClassesScanner class #8965

Merged
merged 65 commits into from
Jun 20, 2017

Conversation

kschroeder
Copy link
Contributor

@kschroeder kschroeder commented Mar 21, 2017

Reduces calls to SplFileInfo::realpath() in the Magento\Setup\Module\Di\Code\Reader\ClassesScanner class.

Description

I have had issues with fairly slow setup:di:compile calls. In the course of my investigation, I ran the XDebug profiler and found that there were several million calls to SplFileInfo::realpath(). This change reduced my compile time from 9-10 minutes to 3 minutes.

[update]

I have been able to find several 10's of seconds (100+ seconds on my test box) of efficiency to be gained by creating a single extraction class that looks only for the first namespace/class match.

[update]

The single extraction class has been changed so that it now scans the entire PHP source file to find unconventionally defined classes as well

Manual testing scenarios

This test simply reduces the number of system calls on the filesystem. There is a functional change, not a logical change.

Contribution checklist

  • Pull request has a meaningful description of its purpose
  • All commits are accompanied by meaningful commit messages
  • All new or changed code is covered with unit/integration tests (if applicable)
  • All automated tests passed successfully (all builds on Travis CI are green)

…ce in a loop instead of several times. This can have a significant performance impact on large codebases.or slow filesystems.
@magento-cicd2
Copy link
Contributor

magento-cicd2 commented Mar 21, 2017

CLA assistant check
All committers have signed the CLA.

@piotrkwiecinski
Copy link
Contributor

@kschroeder nice one :) I've tested in on dev setup and for me it went down from 7 to 4 min and on CI server from 2:30 to 46 sec. Much appreciated you've saved quite a few CPU cycles :).

@kschroeder
Copy link
Contributor Author

@piotrkwiecinski Sweet, I was hoping it wasn't just me! :-)

@schmengler schmengler self-assigned this Mar 21, 2017
@schmengler schmengler added this to the March 2017 milestone Mar 21, 2017
@schmengler
Copy link
Contributor

Nice, verifiable PR with huge benefits! Unfortunately your change in ClassesScanner.php triggered the cyclomatic complexity check in the static test suite. This is not really a problem with your change, but rather with the original method. Let's take this as a chance to refactor the method a bit more. A simple extract method in the loop should do it.

@kschroeder
Copy link
Contributor Author

@schmengler I updated the PR with a new commit that separates the extract() method. If this one doesn't pass I'll separate the $classNames loop.

@maghamed maghamed self-requested a review March 21, 2017 22:04
@schmengler
Copy link
Contributor

--------------------------------------------------------------------------------
FOUND 1 ERROR(S) AFFECTING 1 LINE(S)
--------------------------------------------------------------------------------
 59 | ERROR | Missing function doc comment
--------------------------------------------------------------------------------

@PieterCappelle
Copy link
Contributor

The best community ever. Great PR.

@kschroeder
Copy link
Contributor Author

Note that I found another 2 minutes or so I could squeeze out of it, which is where those new commits are coming from. However, I found an edge case that is breaking it a little bit so it is not ready to be merged yet.

… word "class" was in a class. This change will now only return the Namespace\Class up until the first {. Given that there should only be one class per file this should have no effect beyond reducing the potential for problems.
…structures are scanned five times over the course of compilation. /var/generation is excluded.
…y so other unit tests wouldn't break. It just checks to see if /generation/ is in the filename,
@schmengler
Copy link
Contributor

@maghamed the PR still needs your approval, please leave feedback. This is an important change and it would be a shame if we don't get it into the next release.

if ($directoryList === null) {
$directoryList = ObjectManager::getInstance()->get(DirectoryList::class);
}
$this->generationDirectory = $directoryList->getPath(DirectoryList::GENERATION);
Copy link
Contributor

Choose a reason for hiding this comment

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

it's not allowed to make function calls in the constructor, just assignments to class variables.

here we receive $directoryList and after that make a call to get Path


namespace Magento\Setup\Module\Di\Code\Reader;

class InvalidFileException extends \InvalidArgumentException
Copy link
Contributor

Choose a reason for hiding this comment

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

Custom Exceptions are not recommended to be introduced in each module.

Here is more discussion on this subject - https://github.com/magento/magento2/pull/9315/files#r112514206

Copy link
Contributor

Choose a reason for hiding this comment

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

For Magento\Setup\Module\Di\Code\Reader\FileClassScanner::__construct I'd suggest to go with \Magento\Framework\Exception\FileSystemException. It's used the same way in \Magento\Framework\Filesystem\Directory\Write::assertIsFile.

For Magento\Setup\Module\Di\Code\Reader\FileClassScanner::isBracedNamespace, I'm a bit undecided which of the existing exeptions is the best fit. Maybe \Magento\Framework\Exception\NotFoundException, at least for one of the two cases?

*
* @var string
*/

Copy link
Contributor

Choose a reason for hiding this comment

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

empty line

* Warnings are suppressed for this method due to a micro-optimization that only really shows up when this logic
* is called several millions of times, which can happen quite easily with even moderately sized codebases.
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
Copy link
Contributor

Choose a reason for hiding this comment

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

So I would like a little guidance on what you would prefer: reduce the individual method complexity or notate MD to ignore CC?

If reducing the method complexity has performance impact, I'd say go for optimized performance in this case and mark the class as ignored.

@schmengler Performance and Complexity are two different metrics. And in this specific case these metrics do not correlate. Because high Cyclomatic complexity doesn't lead to high performance and vice versa.

In Magento we have a rule, that any new code should not have SuppressWarnings for CouplingBetween objects and CyclomaticComplexity. Because it's from the beginning has a Technical Debt.

High CyclomaticComplexity often says that some functionality has not been fully covered with automated testing. Because it's too hard to cover all the possible conditional logic and exit points for this method.

Moreover such code is fragile and really hard to maintain in future.

Some time ago we even actively used CRAP metrics for this.

So, this method should be decomposed on some smaller once

)
);
}
$this->filename = $filename;
Copy link
Contributor

Choose a reason for hiding this comment

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

magento constructors should contain just assigments, and no business logic allowed inside

Copy link
Contributor

Choose a reason for hiding this comment

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

@maghamed I'd like to argue that this is a valid use case according to 2.3.1 of the Technical Guidelines:

2.3.1. Constructor SHOULD throw an exception when validation of an argument has failed.

In the example for 2.3.1, an array of options is validated and an InvalidArgumentException is thrown if one of the objects in the array doesn't implement a particular interface.

In the given code the provided filename has to be an existing file so for me, this is a valid validation. Which alternative approach would you suggest?

@kschroeder
Copy link
Contributor Author

Forget this. I'm done. I have about a thousand other things I need to be working on.

@mzeis
Copy link
Contributor

mzeis commented Apr 23, 2017

@kschroeder Thank you for putting so much work into this pull request! I hope we still can get your changes into core because - as @schmengler said - it would be a shame to not have it in future Magento versions.

@okorshenko okorshenko modified the milestones: April 2017, May 2017 May 9, 2017
@mzeis mzeis self-assigned this May 17, 2017
@joshuaadickerson
Copy link

@kschroeder I was about to jump to your defense on rewriting that method and I still think it should be accepted and then it can be reworked at any point in the future. Then I checked out the class and I looked at your argument. Taking a method and rewriting it to use internal (to the class) methods, even at 55 million calls, should not make any noticeable difference. I think all of those variables you're declaring in the extract() method are properties of the class. When you call extract(), they represent the state of that object. The class's only purpose is for that method (which is good).

I don't care to submit a PR or follow the coding guidelines, but I created a test of what it would look like and how that would affect performance: https://gist.github.com/joshuaadickerson/240f3633d99106fb7aed44a98a0ef0fd

It would be interesting to see what the difference between your class and my class are for the build that you said requires 55m calls.

@okorshenko okorshenko modified the milestones: May 2017, June 2017 Jun 1, 2017
@mzeis
Copy link
Contributor

mzeis commented Jun 4, 2017

@maghamed Please can you have a look at the comments I gave on April 23rd? Thanks!

@antonkril
Copy link
Contributor

The PR improves the state of the product. Let's merge.

@okorshenko okorshenko self-assigned this Jun 20, 2017
@magento-team magento-team merged commit 8f043c1 into magento:develop Jun 20, 2017
magento-team pushed a commit that referenced this pull request Jun 20, 2017
…\Setup\Module\Di\Code\Reader\ClassesScanner class #8965
magento-team pushed a commit that referenced this pull request Jun 20, 2017
…\Setup\Module\Di\Code\Reader\ClassesScanner class #8965

 - fixed code style
magento-team pushed a commit that referenced this pull request Jun 20, 2017
…\Setup\Module\Di\Code\Reader\ClassesScanner class #8965

 - fixed code style
magento-team pushed a commit that referenced this pull request Jun 20, 2017
…\Setup\Module\Di\Code\Reader\ClassesScanner class #8965

 - fixed code style
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet