罗斯林启动时间慢

我注意到Roslyn解析/编译的启动时间是一个相当重要的一次性成本。 编辑:我正在使用Roslyn CTP MSI(程序集在GAC中)。 这是预期的吗? 有没有解决方法?

运行下面的代码与1次迭代(约3秒)和300次迭代(约3秒)的时间几乎相同。

[Test] public void Test() { var iters = 300; foreach (var i in Enumerable.Range(0, iters)) { // Parse the source file using Roslyn SyntaxTree syntaxTree = SyntaxTree.ParseText(@"public class Foo" + i + @" { public void Exec() { } }"); // Add all the references we need for the compilation var references = new List(); references.Add(new MetadataFileReference(typeof(int).Assembly.Location)); var compilationOptions = new CompilationOptions(outputKind: OutputKind.DynamicallyLinkedLibrary); // Note: using a fixed assembly name, which doesn't matter as long as we don't expect cross references of generated assemblies var compilation = Compilation.Create("SomeAssemblyName", compilationOptions, new[] {syntaxTree}, references); // Generate the assembly into a memory stream var memStream = new MemoryStream(); // if we comment out from this line and down, the runtime drops to ~.5 seconds EmitResult emitResult = compilation.Emit(memStream); var asm = Assembly.Load(memStream.GetBuffer()); var type = asm.GetTypes().Single(t => t.Name == "Foo" + i); } } 

我认为一个问题是使用内存流,而应该尝试使用动态模块和ModuleBuilder。 总体而言,代码执行速度更快,但仍然有较重的首次加载方案。 我自己对Roslyn很陌生,所以我不确定为什么这会更快但是这里改变了代码。

  var iters = 300; foreach (var i in Enumerable.Range(0, iters)) { // Parse the source file using Roslyn SyntaxTree syntaxTree = SyntaxTree.ParseText(@"public class Foo" + i + @" { public void Exec() { } }"); // Add all the references we need for the compilation var references = new List(); references.Add(new MetadataFileReference(typeof(int).Assembly.Location)); var compilationOptions = new CompilationOptions(outputKind: OutputKind.DynamicallyLinkedLibrary); // Note: using a fixed assembly name, which doesn't matter as long as we don't expect cross references of generated assemblies var compilation = Compilation.Create("SomeAssemblyName", compilationOptions, new[] { syntaxTree }, references); var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new System.Reflection.AssemblyName("CustomerA"), System.Reflection.Emit.AssemblyBuilderAccess.RunAndCollect); var moduleBuilder = assemblyBuilder.DefineDynamicModule("MyModule"); System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch(); watch.Start(); // if we comment out from this line and down, the runtime drops to ~.5 seconds var emitResult = compilation.Emit(moduleBuilder); watch.Stop(); System.Diagnostics.Debug.WriteLine(watch.ElapsedMilliseconds); if (emitResult.Diagnostics.LongCount() == 0) { var type = moduleBuilder.GetTypes().Single(t => t.Name == "Foo" + i); System.Diagnostics.Debug.Write(type != null); } } 

通过使用这种技术,编译只需96毫秒,在后续迭代中需要大约3到15毫秒。 所以我认为你可能在第一个负载方案方面增加一些开销。

对不起我无法解释为什么它更快! 我本人只是在研究Roslyn,今晚晚些时候会做更多的挖掘,看看我是否能找到更多关于ModuleBuilder在内存流上提供的证据。

我使用ASP.net的Microsoft.CodeDom.Providers.DotNetCompilerPlatform包遇到了同样的问题。 事实certificate,这个软件包启动了csc.exe,它使用VBCSCompiler.exe作为编译服务器。 默认情况下,VBCSCompiler.exe服务器存活10秒,其启动时间约为3秒。 这解释了为什么一次或多次运行代码需要大约相同的时间。 看起来Microsoft在Visual Studio中也使用此服务器,以避免每次运行编译时都支付额外的启动时间。

使用此程序包您可以监视您的进程,并找到一个类似于csc.exe / keepalive的命令行:10

好的部分是,如果此服务器保持活动状态(即使在应用程序的两个会话之间),您也可以始终进行快速编译。

不幸的是,Roslyn包不是真正可定制的,我发现改变这个keepalive常量的最简单方法是使用reflection来设置非公共变量值。 在我这边,我把它定义为一整天,因为它总是保持相同的服务器,即使我关闭并重新启动我的应用程序。

  ///  /// Force the compiler to live for an entire day to avoid paying for the boot time of the compiler. ///  private static void SetCompilerServerTimeToLive(CSharpCodeProvider codeProvider, TimeSpan timeToLive) { const BindingFlags privateField = BindingFlags.NonPublic | BindingFlags.Instance; var compilerSettingField = typeof(CSharpCodeProvider).GetField("_compilerSettings", privateField); var compilerSettings = compilerSettingField.GetValue(codeProvider); var timeToLiveField = compilerSettings.GetType().GetField("_compilerServerTimeToLive", privateField); timeToLiveField.SetValue(compilerSettings, (int)timeToLive.TotalSeconds); } 

当您调用Compilation.Emit()时,它是您第一次真正需要元数据,因此会发生元数据文件访问。 在那之后,它的缓存。 虽然这不应该只为mscorlib占3secs。

tldr:NGEN-ing roslyn dlls在初始编译/执行时间内减少1.5秒(在我的情况下从~2s到~0.5s)


刚刚调查了这一点。

使用全新的控制台应用程序和对Microsoft.CodeAnalysis.Scripting的nuget引用,小片段(“1 + 2”)的初始执行大约需要2秒,而后续的执行速度要快得多 – 大约80ms(仍然有点高)为了我的口味但这是一个不同的主题)。

Perfview透露,延迟主要是由于jitting:

在此处输入图像描述

  • Microsoft.CodeAnalysis.CSharp.dll:941ms(3,205种方法)
  • Microsoft.CodeAnalysis.dll 426ms(1,600种方法jitted)

我在Microsoft.CodeAnalysis.CSharp.dll上使用了ngen(确保指定/ExeCondig:MyApplication.exe,因为app.config中的绑定重定向)并获得了不错的性能提升,首次执行时间降至~580ms。

这当然需要在最终用户机器上完成。 就我而言,我使用Wix作为我软件的安装程序,并且在安装时支持NGEN文件。