Speeding up CSharpCompilation.Emit?

Viewed 362

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 and OptimizationLevel.Release
  • Splitting the single source to multiple source parts, one per class (this actually made compilation slower)

Any ideas?

0 Answers
Related