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

Early evaluation of invalid DefaultCompileItems recursive itemgroup performance issue during incremental builds #2502

Closed
jeromelaban opened this issue Sep 7, 2017 · 19 comments

Comments

@jeromelaban
Copy link
Contributor

Using the solution provided here: https://github.com/jeromelaban/MicrosoftIssues/tree/master/MsBuildEarlyEvalIssue,

it is possible to determine that the first invocation of LazyItemEvaluator.GetAllItems evaluates this Compile item group while DefaultLanguageSourceExtension and DefaultItemExcludes are not defined.

This has the effect of recursively and unconditionally search all files in the project's folder, even in the bin and obj folders, and the ret variable in the GetAllItems method contains all these files.

When building Xamarin.Android projects (because of Google SDKs java imports), or other project that a very deep file structure in the obj or bin folder, this recursive enumeration takes quite a long time to complete, and its result is seemingly discarded afterwards.

I'm also assuming this can be a big problem perf in VS, where design-time builds reload each targetframework to evaluate its content, where in some profiling sessions, ntfs.sys shows up regularly in perfview.

@davkean
Copy link
Member

davkean commented Sep 8, 2017

Help me understand this, you are saying that despite the condition being false, we're still evaluating the glob?

@jeromelaban
Copy link
Contributor Author

I'm not sure about the condition evaluation.

The behavior I'm seeing is ret returning a lot of files that don't match *.cs (e.g. pdb files in the obj folder) that come from this ItemGroup, and I'm guessing the only way this can happen is if DefaultLanguageSourceExtension is not set.

In a solution of mine that has a big Xamarin.Android project, this ret variable contains about 5000 items.

The strange part is if DefaultLanguageSourceExtension is not set, EnableDefaultItems should also not be defined, so I'm a bit confused by the behavior, because the ItemGroup should not be evaluated yet clearly is.

From the outside though, GetItems is behaving as expected, considering the sample sets EnableDefaultItems to false

@jeromelaban
Copy link
Contributor Author

jeromelaban commented Sep 15, 2017

I've tried to poke around the issue, and for using the sample solution above, loading a project that takes 1.3s with today's latest master, I'm down to 570ms.

The issue is indeed related to the evaluation of item operations for which the condition is false.

See this commit: jeromelaban@b476fa6

That being said, I'm not certain of the impact of this change, I'll dig deeper to see if it fails some tests.

Edit: if I exclude the JIT Time, the processing of the same project is down from 570ms to about 50ms, on a second run of the same msbuild project loading, but in a different app domain to avoid internal msbuild caching.

@davkean
Copy link
Member

davkean commented Sep 15, 2017

@jeromelaban You are right, what it appears to be doing is the following:

                        // Tell the lazy evaluator to compute the items and add them to _data
                        IList<LazyItemEvaluator<P, I, M, D>.ItemData> items = lazyEvaluator.GetAllItems();
                        // Don't box via IEnumerator and foreach; cache count so not to evaluate via interface each iteration
                        var itemsCount = items.Count;
                        for (var i = 0; i < itemsCount; i++)
                        {
                            var itemData = items[i];
                            if (itemData.ConditionResult)
                            {
                                _data.AddItem(itemData.Item);

                                if (_data.ShouldEvaluateForDesignTime)
                                {
                                    _data.AddToAllEvaluatedItemsList(itemData.Item);
                                }
                            }

                            if (_data.ShouldEvaluateForDesignTime)
                            {
                                _data.AddItemIgnoringCondition(itemData.Item);
                            }
                        }

                        // lazy evaluator can be collected now, the rest of evaluation does not need it anymore
                        lazyEvaluator = null;

It looks like we should be pushing that block into GetAllItems().

@cdmihai was there a reason it was written like this?

@jeromelaban
Copy link
Contributor Author

@davkean I did not notice the design-time implication of this. This may explain the very long delay I'm seeing in VS2017 when requesting a build, and that nothing happens until an msbuild process is done processing all the files (that can last about a minute). After that step, the nuget restore step starts, then the build starts.

@davkean
Copy link
Member

davkean commented Sep 15, 2017 via email

@cdmihai
Copy link
Contributor

cdmihai commented Sep 18, 2017

The lazy item implementation follows the implementation that existed before. From what I can tell, "Design Time Evaluations" (evaluations started via the Project class) gather more evaluation data than "Build Time Evaluation" (evaluations triggered via the ProjectInstance class).

The current lazy item evaluator mimics the behaviour that existed before: When evaluation is done by the Project class, Project.Data.ShouldEvaluateForDesign is hardcoded to true. This causes evaluation of itemgroups and items even when their condition is false, and also causes gathering more data. Here are snippets from the legacy code:

I don't understand the requirements for this extra data collection. It probably enables various analysis in VS (@lifengl).
If the extra data provided by "Design Time Evaluations" is not needed anymore by VS, we can allow turning off Project.Data.ShouldEvaluateForDesign to avoid the CPU / memory waste.

Internal usages of these APIs (MSFT internal links). Maybe they are stale and work just as well with APIs available to ProjectInstance.
http://index/?leftProject=Microsoft.Build&leftSymbol=rxlmvtaoqs4l&file=Definition%5CProject.cs
http://index/?leftProject=Microsoft.Build&leftSymbol=y5xmgdq1vxhm&file=Definition%5CProject.cs
http://index/?leftProject=Microsoft.Build&leftSymbol=ezznkg6tnsyl&file=Definition%5CProject.cs
http://index/?leftProject=Microsoft.Build&leftSymbol=cgin8wlvma8t&file=Definition%5CProject.cs

@jeromelaban
Copy link
Contributor Author

This solution demonstrates the issue, when loading the solution in VS after having built it once.

@lifengl
Copy link

lifengl commented Sep 22, 2017

I don't remember CPS need resolve items excluded by conditions. I wonder it might be used by traditional projects which wants to show all items in different configurations? + @AArnott

Anyway, it sounds that some features may want to know all files listed in the project no matter the condition. Globbings defined inside SDK are completely different beast. A project may want to disable them, and choose their own way to include items. Resolving them might break the expectation of those features using ItemsIgnoringCondition in a different way.

@AArnott
Copy link
Contributor

AArnott commented Sep 22, 2017

As I recall, the legacy project system evaluates all items without regard to condition. CPS does not do this AFAIK. I can't think of any good reason why CPS should ever read items whose condition is false.
I think the legacy project system did this because it couldn't handle items appearing and disappearing from evaluation without explicit changes to them by the project system. But CPS can handle it just fine.

@lifengl
Copy link

lifengl commented Sep 25, 2017

Thanks, Andrew. That matches what I thought. @davkean: maybe this can partially explain why evaluation is still slow after disabling globbings in .Net Core projects? @cdmihai , is it possible to suppress this behavior through some flags?

@jeromelaban
Copy link
Contributor Author

@lifengl, running a small profiling session using the original sample of this issue, here's what comes out of the top native contributors for the second run of "dotnet build":

image

The wall clock time is 3.5 seconds for these samples.

@cdmihai
Copy link
Contributor

cdmihai commented Sep 26, 2017

@lifengl

Currently it is hardcoded to always ignore conditions if you are evaluating via Project. The easiest way to turn this behaviour off is via a new ProjectLoadSettings value. Would this work for you?
However, if you set this, all the data that is dependent on conditions not being evaluated (like ItemsIgnoringConditions) will be empty.

@lifengl
Copy link

lifengl commented Sep 28, 2017

I think it will work for us. Is it possible to get a private build to test with this disabled?

@davkean
Copy link
Member

davkean commented Oct 11, 2017

This has been fixed on the MSBuild side, it still needs to be consumed by CPS.

radical pushed a commit to mono/msbuild that referenced this issue Oct 25, 2017
@kzu
Copy link
Contributor

kzu commented Nov 21, 2018

I know this is old @davkean, but it was mentioned in a devcom feedback at https://developercommunity.visualstudio.com/comments/130197/view.html and I'm interested in knowing whether this ended up shipping.

@davkean
Copy link
Member

davkean commented Nov 21, 2018

We turned this on for CPS-based project. Legacy project system builds its tree ignoring all conditions, so will run into this problem.

@davkean
Copy link
Member

davkean commented Nov 21, 2018

Note, if you walk through the session @kzu we log evaluation times for CPS which will confirm/deny.

@kzu
Copy link
Contributor

kzu commented Nov 24, 2018 via email

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

No branches or pull requests

7 participants