I am porting an app from .NET Framework to .NET Core, which used runtime code generation.
Here's the OLD code:
var refs = new []
{
typeof(Foo).Assembly.Location,
typeof(Enumerable).Assembly.Location
};
var options = new CompilerParameters(refs)
{
GenerateExecutable = false,
GenerateInMemory = true
};
var provider = new CSharpCodeProvider();
var result = provider.CompileAssemblyFromSource(options, rawText);
return result.CompiledAssembly;
The new code, using Roslyn, looks like this:
var root = Path.GetDirectoryName(typeof(object).Assembly.Location);
var typeRefs = new[]
{
typeof(object).Assembly.Location,
Path.Combine(root, "System.Collections.dll"),
Path.Combine(root, "System.Runtime.dll"),
typeof(Foo).Assembly.Location,
typeof(Enumerable).Assembly.Location
};
var source = Measure("SourceText", () => SourceText.From(rawText));
var parseOptions = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp7);
var syntaxTree = Measure("Parse", () => SyntaxFactory.ParseSyntaxTree(source, parseOptions));
var compilation = Measure("Compile", () => CSharpCompilation.Create(
"Bar.dll",
new[] {syntaxTree},
typeRefs.Select(x => MetadataReference.CreateFromFile(x)),
new CSharpCompilationOptions(
OutputKind.DynamicallyLinkedLibrary,
optimizationLevel: OptimizationLevel.Debug
)
));
using var stream = new MemoryStream();
var emitResult = Measure("Emit", () => compilation.Emit(stream));
if(!emitResult.Success)
throw new Exception("Failed to compile parser code!");
stream.Seek(0, SeekOrigin.Begin);
return Assembly.Load(stream.ToArray());
The problem is: the new code works at least 2x slower.
I used a very simple Measure
function to track down the bottleneck:
private static T Measure<T>(string caption, Func<T> generator)
{
var now = DateTime.Now;
var result = generator();
var end = DateTime.Now - now;
Debug.WriteLine($"{caption}: {end.TotalMilliseconds} ms");
return result;
}
And here's what it prints:
SourceText: 4,7926 ms
Parse: 598,5749 ms
Compile: 43,4261 ms
Emit: 5256,0411 ms
As you can see, generating the assembly via compilation.Emit
takes a whopping 5.2 seconds.
Is there any way to speed this up? Maybe some flags in the options that affect compilation time? I tried the following, without any success:
- Changing between
OptimizationLevel.Debug
andOptimizationLevel.Release
- Splitting the single source to multiple source parts, one per class (this actually made compilation slower)
Any ideas?