自从上次生成解决方案以来,没有任何更改,为什么msbuild速度如此之慢?
我有109个项目的解决方案。混合了.NET、Silverlight、MVC、Web应用程序和控制台应用程序 现在,我使用msbuild在控制台上构建它。这需要一些时间。可以理解-很多项目,很多文件 但是,当我在第一次之后第二次构建它时——即使没有实际构建任何内容,它仍然需要很多时间——diag msbuild日志确认了这一点 例如,以下是第一次完整生成的任务性能摘要:自从上次生成解决方案以来,没有任何更改,为什么msbuild速度如此之慢?,msbuild,devenv,Msbuild,Devenv,我有109个项目的解决方案。混合了.NET、Silverlight、MVC、Web应用程序和控制台应用程序 现在,我使用msbuild在控制台上构建它。这需要一些时间。可以理解-很多项目,很多文件 但是,当我在第一次之后第二次构建它时——即使没有实际构建任何内容,它仍然需要很多时间——diag msbuild日志确认了这一点 例如,以下是第一次完整生成的任务性能摘要: Task Performance Summary: 4 ms GetSilverlightItemsFromPrope
Task Performance Summary:
4 ms GetSilverlightItemsFromProperty 1 calls
13 ms Move 1 calls
20 ms GetReferenceAssemblyPaths 27 calls
28 ms GetFrameworkPath 190 calls
29 ms ValidateSilverlightFrameworkPaths 163 calls
72 ms AssignCulture 192 calls
75 ms ResolveKeySource 179 calls
79 ms MakeDir 200 calls
95 ms CreateProperty 260 calls
100 ms CreateCSharpManifestResourceName 122 calls
102 ms Delete 442 calls
112 ms GenerateResource 3 calls
123 ms CopyFilesToFolders 1 calls
177 ms ReadLinesFromFile 190 calls
179 ms CreateHtmlTestPage 31 calls
181 ms CallTarget 190 calls
184 ms GetSilverlightFrameworkPath 163 calls
211 ms Message 573 calls
319 ms CreateSilverlightAppManifest 97 calls
354 ms FileClassifier 119 calls
745 ms ConvertToAbsolutePath 190 calls
868 ms PackagePlatformExtensions 94 calls
932 ms AssignProjectConfiguration 190 calls
1625 ms CategorizeSilverlightReferences 163 calls
1722 ms ResourcesGenerator 60 calls
2467 ms WriteLinesToFile 118 calls
5589 ms RemoveDuplicates 380 calls
8207 ms FindUnderPath 950 calls
17720 ms XapPackager 97 calls
38162 ms Copy 857 calls
38934 ms CompileXaml 119 calls
40567 ms Exec 14 calls
55275 ms ValidateXaml 119 calls
65845 ms AssignTargetPath 1140 calls
83792 ms Csc 108 calls
105906 ms ResolveAssemblyReference 190 calls
1163988 ms MSBuild 471 calls
msbuild声明经过的时间00:08:39.44
好的。现在,我再次运行相同的命令行并获得以下结果:
Task Performance Summary:
1 ms GetSilverlightItemsFromProperty 1 calls
11 ms WriteLinesToFile 1 calls
17 ms GetReferenceAssemblyPaths 27 calls
24 ms GetFrameworkPath 190 calls
32 ms ValidateSilverlightFrameworkPaths 163 calls
43 ms CopyFilesToFolders 1 calls
47 ms GenerateResource 3 calls
60 ms ResolveKeySource 179 calls
66 ms MakeDir 200 calls
69 ms AssignCulture 192 calls
70 ms PackagePlatformExtensions 94 calls
76 ms Delete 432 calls
89 ms CreateProperty 260 calls
98 ms CreateCSharpManifestResourceName 122 calls
136 ms GetSilverlightFrameworkPath 163 calls
156 ms CallTarget 190 calls
182 ms CreateHtmlTestPage 31 calls
207 ms XapPackager 97 calls
215 ms ReadLinesFromFile 190 calls
217 ms Message 573 calls
271 ms CreateSilverlightAppManifest 97 calls
350 ms FileClassifier 119 calls
526 ms ConvertToAbsolutePath 190 calls
795 ms AssignProjectConfiguration 190 calls
1658 ms CategorizeSilverlightReferences 163 calls
2237 ms Exec 2 calls
5703 ms RemoveDuplicates 380 calls
6948 ms Copy 426 calls
7550 ms FindUnderPath 950 calls
17126 ms CompileXaml 119 calls
54495 ms ValidateXaml 119 calls
78953 ms AssignTargetPath 1140 calls
97374 ms ResolveAssemblyReference 190 calls
603295 ms MSBuild 471 calls
msbuild声明经过的时间00:05:25.70
这就提出了以下问题:
resolvesassemblyReference
在第二次构建中花费了这么多时间?在第一次生成中创建的所有缓存文件仍然存在。一切都没有改变。那么,为什么它花费的时间几乎和以前一样——97秒对106秒ValidateXaml
和CompileXaml
正在运行?我的意思是,自从全面建设以来,一切都没有改变李>
现在我重复同样的实验,但这次我在命令行上使用devenv
而不是msbuild
进行构建。与msbuild一样,不使用并行生成,并且日志级别在diag上
devenv
最后没有给出这么好的摘要,它必须从每个项目的摘要中手动汇总
结果令我惊讶。我使用以下powershell脚本来聚合运行时间:
Param(
[Parameter(Mandatory=$True,Position=1)][string]$log
)
[timespan]::FromMilliseconds((sls -SimpleMatch "Time Elapsed" $log |% {[timespan]::Parse(($_ -split ' ')[2]) } | measure -Sum TotalMilliseconds).Sum).ToString()
使用命令行上的devenv
从完全相同的站点构建完全相同的解决方案,第一次构建需要00:06:10.9000000
,第二次构建需要00:00:03.1000000
只需3秒强>
我还编写了一个powershell脚本来汇总统计数据:
Param(
[Parameter(Mandatory=$True,Position=1)][string]$log
)
$summary=@{}
cat $log |% {
if ($collect) {
if ($_ -eq "") {
$collect = $false;
} else {
$tmp = ($_ -replace '\s+', ' ') -split ' ';
$cur = $summary[$tmp[3]];
if (!$cur) {
$cur = @(0, 0);
$summary[$tmp[3]] = $cur;
}
$cur[0] += $tmp[1];
$cur[1] += $tmp[4];
}
} else {
$collect = $_ -eq "Task Performance Summary:"
}
}
$summary.Keys |% {
$stats = $summary[$_];
$ms = $stats[0];
$calls = $stats[1];
[string]::Format("{0,10} ms {1,-40} {2} calls", $ms,$_,$calls);
} | sort
在第一个(完整)构建的日志上运行它会产生以下输出:
5 ms ValidateSilverlightFrameworkPaths 82 calls
7 ms Move 1 calls
9 ms GetFrameworkPath 108 calls
11 ms GetReferenceAssemblyPaths 26 calls
14 ms AssignCulture 109 calls
16 ms ReadLinesFromFile 108 calls
18 ms CreateCSharpManifestResourceName 61 calls
18 ms ResolveKeySource 97 calls
23 ms Delete 268 calls
26 ms CreateProperty 131 calls
41 ms MakeDir 118 calls
66 ms CallTarget 108 calls
70 ms Message 326 calls
75 ms ResolveNonMSBuildProjectOutput 104 calls
101 ms GenerateResource 1 calls
107 ms GetSilverlightFrameworkPath 82 calls
118 ms CreateHtmlTestPage 16 calls
153 ms FileClassifier 60 calls
170 ms CreateSilverlightAppManifest 49 calls
175 ms AssignProjectConfiguration 108 calls
279 ms ConvertToAbsolutePath 108 calls
891 ms CategorizeSilverlightReferences 82 calls
926 ms PackagePlatformExtensions 47 calls
1291 ms ResourcesGenerator 60 calls
2193 ms WriteLinesToFile 108 calls
3687 ms RemoveDuplicates 216 calls
5538 ms FindUnderPath 540 calls
6157 ms MSBuild 294 calls
16496 ms Exec 4 calls
19699 ms XapPackager 49 calls
21281 ms Copy 378 calls
28362 ms ValidateXaml 60 calls
29526 ms CompileXaml 60 calls
66846 ms AssignTargetPath 654 calls
81650 ms Csc 108 calls
82759 ms ResolveAssemblyReference 108 calls
现在,对于第二次构建,结果如下:
1 ms AssignCulture 1 calls
1 ms CreateProperty 1 calls
1 ms Delete 2 calls
1 ms ValidateSilverlightFrameworkPaths 1 calls
3 ms AssignTargetPath 6 calls
3 ms ConvertToAbsolutePath 1 calls
3 ms PackagePlatformExtensions 1 calls
3 ms ReadLinesFromFile 1 calls
3 ms ResolveKeySource 1 calls
4 ms ResolveNonMSBuildProjectOutput 1 calls
5 ms CreateCSharpManifestResourceName 1 calls
5 ms GetFrameworkPath 1 calls
10 ms CategorizeSilverlightReferences 1 calls
11 ms CallTarget 1 calls
11 ms FileClassifier 1 calls
11 ms FindUnderPath 5 calls
11 ms MakeDir 1 calls
13 ms Copy 2 calls
17 ms GetSilverlightFrameworkPath 1 calls
17 ms RemoveDuplicates 2 calls
30 ms AssignProjectConfiguration 1 calls
32 ms Message 25 calls
239 ms ResolveAssemblyReference 1 calls
351 ms MSBuild 2 calls
687 ms CompileXaml 1 calls
1413 ms ValidateXaml 1 calls
我们在这里讨论的是完全相同的解决方案
最后,以下是我使用解决方案构建的脚本:
msbuild:
@setlocal
set SHELFSET=msbuild
set MSBUILDLOGVERBOSERARSEARCHRESULTS=true
set AppConfig=app.config
set Disable_CopyWebApplication=true
set MvcBuildViews=false
call \tmp\undo.cmd
del /a:-R /s/q *.*
tf unshelve %SHELFSET% /recursive /noprompt
msbuild DataSvc.sln
msbuild Main.sln /v:diag > \tmp\00.Main.msbuild.full.log
msbuild Main.sln /v:diag > \tmp\01.Main.msbuild.incr.log
msbuild Main.sln /v:diag > \tmp\02.Main.msbuild.incr.log
@endlocal
@setlocal
set SHELFSET=msbuild
set MSBUILDLOGVERBOSERARSEARCHRESULTS=true
set AppConfig=app.config
set Disable_CopyWebApplication=true
set MvcBuildViews=false
call \tmp\undo.cmd
del /a:-R /s/q *.*
tf unshelve %SHELFSET% /recursive /noprompt
msbuild DataSvc.sln
devenv Main.sln /build > \tmp\00.Main.devenv.full.log
devenv Main.sln /build > \tmp\01.Main.devenv.incr.log
devenv Main.sln /build > \tmp\02.Main.devenv.incr.log
@endlocal
devenv:
@setlocal
set SHELFSET=msbuild
set MSBUILDLOGVERBOSERARSEARCHRESULTS=true
set AppConfig=app.config
set Disable_CopyWebApplication=true
set MvcBuildViews=false
call \tmp\undo.cmd
del /a:-R /s/q *.*
tf unshelve %SHELFSET% /recursive /noprompt
msbuild DataSvc.sln
msbuild Main.sln /v:diag > \tmp\00.Main.msbuild.full.log
msbuild Main.sln /v:diag > \tmp\01.Main.msbuild.incr.log
msbuild Main.sln /v:diag > \tmp\02.Main.msbuild.incr.log
@endlocal
@setlocal
set SHELFSET=msbuild
set MSBUILDLOGVERBOSERARSEARCHRESULTS=true
set AppConfig=app.config
set Disable_CopyWebApplication=true
set MvcBuildViews=false
call \tmp\undo.cmd
del /a:-R /s/q *.*
tf unshelve %SHELFSET% /recursive /noprompt
msbuild DataSvc.sln
devenv Main.sln /build > \tmp\00.Main.devenv.full.log
devenv Main.sln /build > \tmp\01.Main.devenv.incr.log
devenv Main.sln /build > \tmp\02.Main.devenv.incr.log
@endlocal
我的测试告诉我,msbuild
是一堆垃圾,我不应该在命令行中使用它来构建我的C#解决方案。增加了这种感觉
但也许我说错了,一个简单的调整将使msbuild在第二次构建中与devenv
一样高效
如何让msbuild在第二次构建中正常运行
编辑1
CompileXaml
任务是在C:\Program Files(x86)\MSBuild\Microsoft\Silverlight\v5.0\Microsoft.Silverlight.Common.targets中找到的MarkupCompilePass1
目标的一部分:
<Target Name="MarkupCompilePass1"
DependsOnTargets="$(CompileXamlDependsOn)"
Condition="'@(Page)@(ApplicationDefinition)' != '' " >
<CompileXaml
LanguageSourceExtension="$(DefaultLanguageSourceExtension)"
Language="$(Language)"
SilverlightPages="@(Page)"
SilverlightApplications="@(ApplicationDefinition)"
ProjectPath="$(MSBuildProjectFullPath)"
RootNamespace="$(RootNamespace)"
AssemblyName="$(AssemblyName)"
OutputPath="$(IntermediateOutputPath)"
SkipLoadingAssembliesInXamlCompiler="$(SkipLoadingAssembliesInXamlCompiler)"
TargetFrameworkDirectory="$(TargetFrameworkDirectory)"
TargetFrameworkSDKDirectory="$(TargetFrameworkSDKDirectory)"
ReferenceAssemblies ="@(ReferencePath);@(InferredReference->'$(TargetFrameworkDirectory)\%(Identity)')"
>
<Output ItemName="Compile" TaskParameter="GeneratedCodeFiles" />
<!-- Add to the list list of files written. It is used in Microsoft.Common.Targets to clean up
for a next clean build
-->
<Output ItemName="FileWrites" TaskParameter="GeneratedCodeFiles" />
<Output ItemName="_GeneratedCodeFiles" TaskParameter="GeneratedCodeFiles" />
</CompileXaml>
<Message Text="(Out) GeneratedCodeFiles: '@(_GeneratedCodeFiles)'" Condition="'$(MSBuildTargetsVerbose)'=='true'"/>
</Target>
正如我们所见,没有输入也没有输出
接下来,第二个生成的diag msbuild日志不包含任何可疑的单词,如“rebuilding”
最后,我想指出,msbuild和devenv都是在完全相同的环境下运行的,没有一个使用多线程构建。然而,两者之间的差异是巨大的——超过5分钟(msbuild)而不是3秒(devenv,命令行)
对我来说仍然是个谜
编辑2
我现在更了解devenvbuild的工作原理。它使用启发式方法来确定当前项目是否必须首先移交给msbuild。默认情况下会启用此启发式,但可以通过将disableFastUpdateCheck
msbuild属性设置为true
来禁用此启发式
现在,命令行devenv构建实际上需要3秒钟以上的时间来确定是否需要运行msbuild。总而言之,对于像我这样的解决方案,可能需要20秒甚至30秒才能确定不需要向msbuild传递任何内容
这种启发是造成时间上巨大差异的唯一原因。我猜VisualStudio团队意识到标准构建脚本的质量很差(MarkupCompilePass1之类的任务不是由输入和输出驱动的),并决定首先想出一种跳过msbuild的方法
但是有一个陷阱——启发式只检查csproj文件,没有检查导入的目标文件。此外,它对隐式依赖项一无所知,比如从其他TypeScript文件引用的TypeScript文件。因此,如果您的TypeScript文件引用了属于不同项目的其他TypeScript文件,并且没有从项目文件显式链接到其他TypeScript文件,则启发式方法不知道这些文件,您最好使用disableFastUpdateCheck=true
。构建速度会较慢,但至少是正确的
底线-我不知道如何修复msbuild,显然devenv的家伙也不知道。这似乎就是他们发明启发式的原因。首先,看看你正在生成的诊断日志。实际上,首先使用文件记录器而不是控制台操作符将控制台输出管道传输到日志,然后查看它们的日志 实际上,使用以下命令代替
/v:diag>msbuild.log
:
/v:min /fl3 /flp3:warningsonly;logfile=msbuild.wrn /fl4 /flp4:errorsOnly;logfile=msbuild.err /fl5 /flp5:Verbosity=diag;logfile=msbuild.log
现在,您的控制台缓冲区感谢您,您的开发人员也感谢您为调试提供了仅保留单独的错误日志和仅警告日志的远见
现在,检查诊断MsBuild日志,并在CTRL+F中查找运行时间较长的目标。您是否看到任何表示目标再次运行的措辞,即使没有任何更改?要跳过构建,目标需要定义输入和输出。如果输入(.cs)比输出(.dll、.pdb)新,那么它知道一定有什么东西发生了变化