C# Roslyn缓慢启动时间

C# Roslyn缓慢启动时间,c#,roslyn,C#,Roslyn,我注意到Roslyn解析/编译的启动时间是相当大的一次性成本。编辑:我使用的是Roslyn CTP MSI(组件在GAC中)。这是预期的吗?有什么解决办法吗 运行下面的代码所花费的时间与运行300次迭代所花费的时间(约3秒)几乎相同 [测试] 公开无效测试() { 电压互感器=300; foreach(可枚举范围(0,iters)中的var i) { //使用Roslyn解析源文件 SyntaxTree SyntaxTree=SyntaxTree.ParseText(@“public class

我注意到Roslyn解析/编译的启动时间是相当大的一次性成本。编辑:我使用的是Roslyn CTP MSI(组件在GAC中)。这是预期的吗?有什么解决办法吗

运行下面的代码所花费的时间与运行300次迭代所花费的时间(约3秒)几乎相同

[测试]
公开无效测试()
{
电压互感器=300;
foreach(可枚举范围(0,iters)中的var i)
{
//使用Roslyn解析源文件
SyntaxTree SyntaxTree=SyntaxTree.ParseText(@“public class Foo”+i+@“{public void Exec(){}”);
//添加编译所需的所有参考资料
var references=新列表();
Add(新的MetadataFileReference(typeof(int.Assembly.Location));
var compilationOptions=新的compilationOptions(outputKind:outputKind.DynamicallyLinkedLibrary);
//注意:使用固定的程序集名称,只要我们不希望生成的程序集发生交叉引用,这一点就无关紧要
var compilation=compilation.Create(“SomeAssemblyName”,compilationOptions,new[]{syntaxTree},references);
//将程序集生成到内存流中
var memStream=new MemoryStream();
//如果我们从这一行向下注释,运行时将下降到大约0.5秒
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<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);
            }
        }
var iters=300;
foreach(可枚举范围(0,iters)中的var i)
{
//使用Roslyn解析源文件
SyntaxTree SyntaxTree=SyntaxTree.ParseText(@“public class Foo”+i+@“{public void Exec(){}”);
//添加编译所需的所有参考资料
var references=新列表();
Add(新的MetadataFileReference(typeof(int.Assembly.Location));
var compilationOptions=新的compilationOptions(outputKind:outputKind.DynamicallyLinkedLibrary);
//注意:使用固定的程序集名称,只要我们不希望生成的程序集发生交叉引用,这一点就无关紧要
var compilation=compilation.Create(“SomeAssemblyName”,compilationOptions,new[]{syntaxTree},references);
var assemblyBuilder=AppDomain.CurrentDomain.DefinedDynamicAssembly(新的System.Reflection.AssemblyName(“CustomerA”),
System.Reflection.Emit.AssemblyBuilderAccess.RunAndCollect);
var moduleBuilder=assemblyBuilder.DefinedDynamicModule(“MyModule”);
System.Diagnostics.Stopwatch=新的System.Diagnostics.Stopwatch();
watch.Start();
//如果我们从这一行向下注释,运行时将下降到大约0.5秒
var emitResult=compilation.Emit(moduleBuilder);
看,停;
系统。诊断。调试。写入线(watch.elapsedmillyses);
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在memorystream上提供的更多证据。

当调用Compilation.Emit()时,这是您第一次真正需要元数据,因此元数据文件访问会发生。之后,它将被缓存。尽管这不应该仅仅是mscorlib的3sec。我在使用ASP.net包时遇到了同样的问题。原来这个包启动了csc.exe,它使用VBCSCompiler.exe作为编译服务器。默认情况下,VBCSCompiler.exe服务器的运行时间为10秒,启动时间约为3秒。这解释了为什么一次或多次运行代码所需的时间大致相同。微软似乎也在VisualStudio中使用此服务器,以避免每次运行编译时都要支付额外的启动时间

有了这个软件包,您可以监视您的进程,并将找到一个类似于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);
    }
//
///强制编译器运行一整天,以避免为编译器的启动时间付费。
/// 
私有静态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(“\u compilerServerTimeToLive”,privateField);
timeToLiveField.SetValue(编译器设置,
    /// <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);
    }