Using Roslyn Emit method with a ModuleBuilder instead of a MemoryStream
Asked Answered
A

1

16

I was having trouble with performance when using Roslyn to compile to a dynamic assembly. Compilation was taking ~3 seconds, compared to ~300 milliseconds to compile the same code when using the CodeDom compiler. Here's a pared-down version of the code I'm using to do the compilation:

var compilation = CSharpCompilation.Create(
                                      "UserPayRules.dll",
                                      syntaxTrees,
                                      assembliesToAdd);

using (var stream = new MemoryStream())
{
    stopWatch.Start();
    var result = compilation.Emit(stream);
    stopWatch.Stop();
    Debug.WriteLine("Compilation: {0}", stopWatch.ElapsedMilliseconds);
    if (!result.Success)
    {
        throw new InvalidOperationException();
    }
    var assembly = Assembly.Load(stream.GetBuffer());
}

This answer suggests passing a ModuleBuilder object into the Emit method instead of a MemoryStream in order to speed things up. I tried to follow that pattern, like so:

var compilation = CSharpCompilation.Create(
                                      "UserPayRules.dll",
                                      syntaxTrees,
                                      assembliesToAdd);

var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
                                       new AssemblyName("ThisAssembly"),
                                       AssemblyBuilderAccess.RunAndCollect);

var moduleBuilder = assemblyBuilder.DefineDynamicModule("ThisModule");
var result = compilation.Emit(moduleBuilder);

Debug.WriteLine("Compilation: {0}", stopWatch.ElapsedMilliseconds);
if (!result.Success)
{
    throw new InvalidOperationException();
}
var assembly = Assembly.Load(stream.GetBuffer());

But my version of Roslyn apparently doesn't have an overload of the Emit method that takes a ModuleBuilder. That version is:

Id: Microsoft.CodeAnalysis
Version: 0.6.4033103-beta (Prerelease)
Project Information: http://msdn.microsoft.com/en-US/roslyn

Obviously, this is a prerelease, so it's not strange that the api might have changed. However,

My Question(s)

  1. Does anyone know why the Emit method no longer seems to have an overload that takes a ModuleBuilder?
  2. Is there another way to make this compilation faster while still using Roslyn (Roslyn offers a couple advantages over the CodeDom and Mono compilers that I'd prefer not to give up)?
Aceto answered 9/4, 2014 at 21:40 Comment(3)
Looking at the source code, Roslyn seems to be using CCI, which is (among other things) a replacement for Reflection.Emit. I think that's why ModuleBuilder is not supported.Vd
Thanks for the link to CCI--I wasn't familiar with it. The site claims it's (among other things) more efficient than Reflection.Emit. I'll have to dig into their documentation more to figure out why that's not the case for me.Aceto
You are looking at a CTP. Never judge the performance of a CTP. Also, the "Known Issues.docx" file states "After starting Visual Studio, the first build may be slower than expected due to the compilers not being NGen’d in the Preview." That would obviously apply to every program using the CTP, not just VS.Monica
C
12

Roslyn currently doesn't expose ability to emit dynamic assemblies. We removed it because it was problematic.

You can still emit to a MemoryStream using Compilation.Emit APIs and then use Assembly.Load(byte[]) to load the resulting binary.

Note that this assembly won't be freed until the containing AppDomain is unloaded.

Cimah answered 10/4, 2014 at 1:42 Comment(6)
if you are on the Roslyn team: Consider adding this info to your profile so that your answers stand out as authoritative.Costumer
Thanks for the explanation, @Tomas. Do you know of any places to look/options to change in order to improve the compilation performance? Right now it's pretty slow compared to using CodeDom.Aceto
I should qualify that last comment: it's really slow the first time it runs. About 4500 milliseconds the first run and about 15 on subsequent runs. Compared to about 300 milliseconds the first run and 80 milliseconds on subsequent runs for CodeDom. Since this code will mostly run on App_Start, though, it doesn't really matter what how fast the subsequent runs are if the startup takes several seconds.Aceto
I'm guessing it's slower due to Roslyn not being NGEN'd. If you NGEN the Roslyn binaries the perf should get better.Cimah
You might be interested to vote for this feature request:visualstudio.uservoice.com/forums/121579-visual-studio/…Dowd
@TomasMatousek can you clarify how exactly this is "problematic" and whether this is in the pipeline for Roslyn at all? Collectible assemblies have been supported in Reflection.Emit since at least .NET 4.0 so having the ability to compile syntax trees down to a ModuleBuilder seems like a highly desirable target for all users wanting proper scripting in their apps...Dungaree

© 2022 - 2024 — McMap. All rights reserved.