2013-03-12 17 views
5

Ho notato che il tempo di avvio per l'analisi/compilazione di Roslyn è un costo una tantum abbastanza significativo. EDIT: sto usando il Roslyn CTP MSI (l'assembly è nel GAC). È previsto? C'è qualche soluzione?Roslyn tempo di avvio lento

L'esecuzione del codice seguente richiede quasi la stessa quantità di tempo con 1 iterazione (~ 3 secondi) come con 300 iterazioni (~ 3 secondi).

[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); 
    } 
} 
+2

Non sono del tutto sorpreso, ricordate che è necessario caricare tutti gli assembly di Roslyn e probabilmente c'è anche un sacco di compilation JIT. Potresti provare a delinearlo per vedere cosa succede. –

+0

In effetti, un profiler è tuo amico. Inoltre, cosa succede se si estrae quell'assemblaggio. Ciò non aiuterà affatto il perf. –

+0

Come si ottengono i file binari di Roslyn nel sistema? Se stai ricevendo il pacchetto NuGet, è probabile che questa volta sia JIT. Se è stato installato il CTP MSI, GAC e NGen verranno assemblati e probabilmente si vedranno prestazioni di avvio migliori. –

risposta

2

Penso che una questione utilizza un flusso di memoria, invece si dovrebbe provare a utilizzare un modulo dinamico e ModuleBuilder invece. Nel complesso, il codice è in esecuzione più veloce, ma ha ancora uno scenario di carico più pesante. Sono abbastanza nuovo per Roslyn, quindi non sono sicuro del motivo per cui è più veloce, ma qui è il codice modificato.

 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); 
      } 
     } 

Utilizzando questa tecnica la compilazione ha richiesto solo 96 millesimi di secondo, su iterazioni successive si impiegano circa 3 - 15ms. Quindi penso che potresti avere ragione in termini di primo scenario di caricamento aggiungendo un sovraccarico.

Spiacente, non posso spiegare perché è più veloce! Sto solo facendo ricerche su Roslyn e cercherò di scavare più tardi questa sera per vedere se riesco a trovare altre prove di ciò che ModuleBuilder fornisce nel memorandum.

0

Quando si chiama Compilation.Emit() è la prima volta che si richiedono effettivamente i metadati, quindi si verifica l'accesso al file di metadati. Dopo quello, è memorizzato nella cache. Anche se questo non dovrebbe tenere conto di 3 secondi solo per mscorlib.

0

Ho riscontrato lo stesso problema utilizzando il pacchetto Microsoft.CodeDom.Providers.DotNetCompilerPlatform di ASP.net. Si scopre che questo pacchetto avvia csc.exe che utilizza VBCSCompiler.exe come un server di compilazione. Per impostazione predefinita, il server VBCSCompiler.exe dura 10 secondi e il suo tempo di avvio è di circa 3 secondi. Questo spiega perché ci vuole circa lo stesso tempo per eseguire il tuo codice una o più volte. Sembra che Microsoft stia utilizzando questo server anche in Visual Studio per evitare di pagare un tempo di avvio extra ogni volta che si esegue una compilation.

Con questo pacchetto è possibile monitorare i processi e troverete una riga di comando che si presenta come csc.exe/Keep Alive: 10

La parte bella è che se questo server rimane vivo (anche tra le due sessioni di tuo applicazione), è possibile ottenere una compilazione veloce tutte le volte.

Sfortunatamente, il pacchetto di Roslyn non è veramente personalizzabile e il modo più semplice che ho trovato per modificare questa costante keepalive consiste nell'utilizzare la riflessione per impostare il valore di variabili non pubbliche. Da parte mia, l'ho definito per un giorno intero in quanto mantiene sempre lo stesso server anche se chiudo e riavvia la mia applicazione.

/// <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); 
    } 
0

TLDR: NGEN-ing DLL Roslyn rade 1.5s off del tempo di compilazione/esecuzione iniziale (nel mio caso da ~ a ~ 2s 0,5 s)


indagato su questo solo ora.

Con una nuova applicazione console e un riferimento a nuget su Microsoft.CodeAnalysis.Scripting, l'esecuzione iniziale di un piccolo snippet ("1 + 2") richiedeva circa 2 secondi, mentre quelli successivi erano molto più veloci - circa 80 ms (ancora un po 'alto per i miei gusti ma questo è un argomento diverso).

Perfview rivelato che il ritardo sia sostanzialmente dovuto ai jitting:

enter image description here

  • Microsoft.CodeAnalysis.CSharp.dll: 941ms (3.205 metodi jitted)
  • 426ms Microsoft.CodeAnalysis.dll (1.600 metodi jitted)

Ho usato ngen su Microsoft.CodeAnalysis.CSharp.dll (assicurandomi di specificare/ExeCondig: MyA pplication.exe a causa dei reindirizzamenti di binding in app.config) e ottenuto un buon miglioramento delle prestazioni, il tempo di prima esecuzione è sceso a ~ 580 ms.

Questo naturalmente dovrebbe essere fatto sulle macchine degli utenti finali. Nel mio caso, sto usando Wix come programma di installazione per il mio software e ci sono i file support for NGEN-ing al momento dell'installazione.

Problemi correlati