我注意到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<MetadataReference>();
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);
}
}
答案 0 :(得分:2)
我认为一个问题是使用内存流,而应该尝试使用动态模块和ModuleBuilder。总体而言,代码执行速度更快,但仍然有较重的首次加载方案。我自己对罗斯林很陌生,所以我不确定为什么这会更快,但这里是改变后的代码。
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<MetadataReference>();
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在内存流上提供的证据。
答案 1 :(得分:1)
我使用ASP.net的Microsoft.CodeDom.Providers.DotNetCompilerPlatform包遇到了同样的问题。事实证明,这个软件包启动了csc.exe,它使用VBCSCompiler.exe作为编译服务器。默认情况下,VBCSCompiler.exe服务器存活10秒,其启动时间约为3秒。这解释了为什么一次或多次运行代码需要大约相同的时间。看起来Microsoft在Visual Studio中也使用此服务器,以避免每次运行编译时都支付额外的启动时间。
使用此程序包您可以监视您的进程,并找到一个类似于csc.exe / keepalive的命令行:10
好的部分是,如果此服务器保持活动状态(即使在应用程序的两个会话之间),您也可以一直进行快速编译。
不幸的是,Roslyn包不是真正可定制的,我发现改变这个keepalive常量的最简单方法是使用反射来设置非公共变量值。在我这边,我把它定义为一整天,因为它总是保持相同的服务器,即使我关闭并重新启动我的应用程序。
/// <summary>
/// Force the compiler to live for an entire day to avoid paying for the boot time of the compiler.
/// </summary>
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);
}
答案 2 :(得分:0)
当您调用Compilation.Emit()时,它是您第一次真正需要元数据,因此会发生元数据文件访问。之后,它的缓存。虽然这不应该只为mscorlib占3secs。
答案 3 :(得分:0)
tldr:NGEN-ing roslyn dlls在初始编译/执行时间内减少1.5秒(在我的情况下从~2s到~0.5s)
刚才调查了这一点。
使用全新的控制台应用程序和Microsoft.CodeAnalysis.Scripting
的nuget引用,小片段(&#34; 1 + 2&#34;)的初始执行大约需要2秒,而后续的执行速度要快得多 - 大约80毫秒(我的口味仍然有点高,但这是一个不同的主题)。
Perfview透露,延迟主要是由于咬合:
我在Microsoft.CodeAnalysis.CSharp.dll上使用了ngen(确保指定/ExeCondig:MyApplication.exe,因为app.config中的绑定重定向)并获得了不错的性能提升,第一次执行时间降到了〜580ms。
这当然需要在最终用户机器上完成。在我的情况下,我使用Wix作为我的软件的安装程序,并在安装时使用support for NGEN-ing个文件。