Roslyn: CSharpScript seemingly excessive memory usage

Created on 20 Sep 2017  Â·  17Comments  Â·  Source: dotnet/roslyn

Version Used: 2.3.0.0

I have an application that creates and pre-compiles a number of scripts.
For me though, it seems that ~39 scripts is the limit where compilation will fail with an OOM exception.

var script = CSharpScript.Create(text, scriptOptions);
script.Compile();

My scriptOptions contains 1 assembly reference and 2 imports.
Each script compilation consumes roughly over 50mb.

Releasing the references to these scripts allows the gc to free up memory and not get an OOM ex meaning each script reference seems to consume roughly the same amount of memory as the entire main application without scripts. This seems slightly excessive.

I will help with any details needed. All help is welcome.

Area-Interactive Interactive-ScriptingLogic

Most helpful comment

also one option would be to implement Microsoft.CodeAnalysis.MetadataReferenceResolver

public class MissingResolver : Microsoft.CodeAnalysis.MetadataReferenceResolver
        {
            public override bool Equals(object other)
            {
                throw new NotImplementedException();
            }

            public override int GetHashCode()
            {
                throw new NotImplementedException();
            }

            public override bool ResolveMissingAssemblies => false;

            public override ImmutableArray<PortableExecutableReference> ResolveReference(string reference, string baseFilePath, MetadataReferenceProperties properties)
            {
                throw new NotImplementedException();
            }
        }

ScriptOptions.Default.WithMetadataResolver(new MissingResolver())

When you don't Resolve Missing Assemblies - for instance i had a StringBuilder in my globals.

.Net Framework didn't crash and .Net Core 2 Crashed - Haven't tested with .Net Core 3

If I remove I do ResolveMissingAssemblies for .Net Core 2 - it will load around 31 Missing References just for StringBuilder. Memory was around 300MB

doing above for .Net Framework it was around 70MB

using MissingResolver class with the .Net Framework memory usage was 30MB

I am also include Mysql.Data for my scripts.

just what I have found so far.

All 17 comments

I see the same issue with roughly 20 CSharpScript objects. Currently we allow certain users the ability to write C# expressions that are intended to filter data for them and keep the cached scripts around for execution against a domain object. Unfortunately, being limited to 20-30 scripts before an OOM condition is not scalable (or testable).

As an update, I tried setting ScriptOptions.Default.WithEmitDebugInformation(false);, hoping that it's some kind of debug information taking the space, with no change.

Taking a snapshot of managed memory I can see that 5 Script objects consume only 10 234 720 bytes.

Microsoft.CodeAnalysis.Scripting.Script<Object> 5   220 10 234 720

From this it looks like the memory is consumed somewhere outside managed memory and i don't know how to provide better information without digging into the compilation process myself.

As an update I found the probable cause for this, which unfortunately i did not add to the issue description:

The globalsType i was passing to the script is defined in the main assembly. Debugging the script compilation process I could make out that the reference manager seems to load all assemblies which are referenced in the assembly in which the globalsType is located in.

Moving the globalsType to a separate assembly brought memory usage down to sane levels.

I will leave this issue open for now.

That did not work for me. My globalsType was in the assembly that referenced all of the numerous roslyn-related assemblies, and I moved it into a simple models class library, but memory consumption did not change at all for me.

You will find in issue #16897 a sample repro showing how to get OutOfMemoryException with C# scripting API.

Thanks @CyberSinh for referencing it
Btw I confirm what @tomthoros said: exporting the Globals type into a brand new assembly which has nothing except it, doesn't create this memory peak.

Thanks @molinch, but this workaround doesn't work for me because I have to manipulate with scripts several instances running from my main (and big) assembly. These memory peaks are really a big issue for me.

had the same problem, the memory consumption was very high.
the solution storing in a dynamic object the compiled and (MVVM)
I store compiled code in Sql server and execute it in runtime and there is no need to be compiling at all times.

my app store XAML,c# into sql server and créate screen on runtime

SaveScreenAssembly = store to sql server
loadlibraryy= load from sql server

i can send simple, my email Wilmer.[email protected]

if (this.isMVVM == true) { if (_code == string.Empty) { _code = ((Inicio)Application.Current.MainWindow).DicScreen[idpnt].Code.ToString().Trim(); } if (_loadScreen == string.Empty) { _loadScreen = ((Inicio)Application.Current.MainWindow).DicScreen[idpnt].Load_Screen.ToString().Trim(); } //var scriptOptions = ScriptOptions.Default; ScriptOptions scriptOptions = ScriptOptions.Default; // adiciona referencias scriptOptions = scriptOptions.AddImports("System.Xml", "System.Data.SqlClient", "System", "System.Collections.Generic", "System.ComponentModel", "System.Data", "System.Data.DataSetExtensions", "Microsoft.CSharp", "System.Xml.Linq", "System.Windows", "System.Windows.Controls", "SiasoftApp", "System.Windows.Markup").AddReferences("PresentationCore", "PresentationFramework", "WindowsBase", "System.Core"); scriptOptions = scriptOptions.AddReferences(this.GetType().Assembly.Location).AddReferences("System", "System.Data", "System.Data.DataSetExtensions", "Microsoft.CSharp", "System.Xml.Linq", "System.Windows", "System.Windows.Controls", "SiasoftApp"); _loadScreen = _loadScreen.Replace("_DirLibrary", ((Inicio)Application.Current.MainWindow)._DirLibrary); var engine = CSharpScript.Create(_loadScreen + Environment.NewLine + _code + Environment.NewLine, scriptOptions, typeof(SiasoftApp.HostObjectTab)); //engine = engine.ContinueWith(" UIElement myElement = (UIElement)XamlReader.Parse(usercontrol.__xaml);GridControl.Content = myElement;" + Environment.NewLine); //engine.RunAsync(globals: hostObj).Wait(); //AdjuntarEventos(0, this.GridControl); //engine = engine.ContinueWith(_RegistraObjetos + Environment.NewLine); if (tabitem.Maestra.Trim() == string.Empty) { BarraMaestra.Visibility = Visibility.Visible; ActivaDesactivaMaestra(_EstadoAdEdMae); } else { //if (_EstadoAdEdMae == 0) ActivaDesactivaMaestra(_EstadoAdEdMae); if (IniActivo == false) { _EstadoAdEdMae = 0; ActivaDesactivaMaestra(_EstadoAdEdMae); } } engine.RunAsync(globals: hostObj); //////////// save dll screen ////// var compilacion = engine.GetCompilation(); //compilacion.WithOptions(scriptCompilationOptions); var ms = new MemoryStream(); var diagnostics = compilacion.GetDiagnostics(); System.Text.StringBuilder sb = new System.Text.StringBuilder(); if (diagnostics.Length > 0) { foreach (var diagnostic in diagnostics) { sb.Append(diagnostic.Id + " - " + diagnostic.ToString() + "\n"); } MessageBox.Show(sb.ToString()); } else { Byte[] b = new Byte[ms.Length]; b = ms.ToArray(); SaveScreenAssembly(idpnt, b); ((Inicio)Application.Current.MainWindow).DicScreen[idpnt].CodeMvVm = b; loadlibraryy(b,idpnt); //File.WriteAllBytes(Libraryz, b); } ms.Close(); sb = null; }

SaveScreenAssembly = store to sql server
loadlibraryy= load from sql server

i can send simple, my email Wilmer.[email protected]

Hi Team is there any update on the Issue. We need this to be fixed ASAP.

hi, my friend, i can send app sample and your check…
all code c# and XAML is store in sql server and compiler in run time.  is very good  3 year init this project
Wilmer Barrios Villa

El ‎jueves‎, ‎julio‎ ‎05‎, ‎2018‎ ‎02‎:‎20‎:‎15‎ ‎AM‎ ‎-05, UdayaBhandaru <[email protected]> escribió:

Hi Team is there any update on the Issue. We need this to be fixed ASAP.

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub, or mute the thread.

Hi,
We are using Microsoft.CodeAnalysis.CSharp.Scripting > CSharpScript class to compile scripts
When the script to compile is less then 50 then there is no issue but if it is near to 65-70 memory consumption is beyond excess level and project crashes.
As per checking found on each record compile around 50mb of ram is used so it is the problem.

Is there any update going to be implemented in near future, Can you please suggest some alternate method of using it so memory consumption is not too much.

@tomthoros or @molinch Can you please provide any sample code or project that you have used to overcome the memory issue by removing Microsoft.CodeAnalysis.CSharp.Scripting into seperate project.

Thanks

@RenishVNair are you passing in globalsType? Try moving it's definition to a separate clean assembly.

@tomthoros
We are using global type in the same project as per below image, will try to move it to a separate project and use that project's dll
image

The solutions that puts the Globals object in a separate assemby nailed it for me. However it is important to highlight that caching the Script objects is important to prevent memory from rising sky high.

My code looks like this:

public class FormulaEvaluator
    {
        private static Regex normalizeRegex = new Regex(@"(\[(\w+)\])", RegexOptions.Compiled);
        private Dictionary<string, Script<double>> compiled = new Dictionary<string, Script<double>>();


        public async Task<double> Evaluate(string formula, CalculatorGlobals globals)
        {

            Script<double> script;
            if (!compiled.TryGetValue(formula, out script))
            {
                string normalized = Normalize(formula);
                script = CSharpScript.Create<double>(normalized, globalsType: typeof(CalculatorGlobals));
                script.Compile();
                compiled.Add(formula, script);
            }

            var result = await script.RunAsync(globals);
            return result.ReturnValue;
        }

        private string Normalize(string formula)
        {
            var matches = normalizeRegex.Matches(formula);

            if (matches.Count == 0)
                return formula;

            StringBuilder sb = new StringBuilder(formula);
            foreach (Match match in matches)
            {
                sb.Replace(match.Groups[1].Value, match.Groups[2].Value);
            }
            return sb.ToString();
        }
    }

The normalization step is just because the system that inputs the formulas I have to execute on mine formats variables as [A]+[B] which is not valid C# code.

The memory footprint will rise every time your program goes across the script.Compile(); line. You can see it happening by putting a breakpoint there and looking at the Memory chat in Diagnostics Tools, and that is why we have to cache already compiled scripts.

also one option would be to implement Microsoft.CodeAnalysis.MetadataReferenceResolver

public class MissingResolver : Microsoft.CodeAnalysis.MetadataReferenceResolver
        {
            public override bool Equals(object other)
            {
                throw new NotImplementedException();
            }

            public override int GetHashCode()
            {
                throw new NotImplementedException();
            }

            public override bool ResolveMissingAssemblies => false;

            public override ImmutableArray<PortableExecutableReference> ResolveReference(string reference, string baseFilePath, MetadataReferenceProperties properties)
            {
                throw new NotImplementedException();
            }
        }

ScriptOptions.Default.WithMetadataResolver(new MissingResolver())

When you don't Resolve Missing Assemblies - for instance i had a StringBuilder in my globals.

.Net Framework didn't crash and .Net Core 2 Crashed - Haven't tested with .Net Core 3

If I remove I do ResolveMissingAssemblies for .Net Core 2 - it will load around 31 Missing References just for StringBuilder. Memory was around 300MB

doing above for .Net Framework it was around 70MB

using MissingResolver class with the .Net Framework memory usage was 30MB

I am also include Mysql.Data for my scripts.

just what I have found so far.

To have less memory consumption you should use script.CreateDelegate()

var script = CSharpScript.Create<int>("X*Y", globalsType: typeof(Globals)); ScriptRunner<int> runner = script.CreateDelegate();

The delegate doesn’t hold compilation resources (syntax trees, etc.) alive.
https://github.com/dotnet/roslyn/wiki/Scripting-API-Samples#-create-a-delegate-to-a-script

Workaround:
if you will use script.CreateDelegate(); and then call GC.Collect then your memory will be cleaned up.

To summarize - script.CreateDelegate() allocates a lot of memory but this memory will be collected
during next GC.

Hi,
my name is Wilmer Barrios From Bogota colombia, i download my first Roslyn in year 2014
I have been developing software with Rosly for 3 years, this software has all the source code of c # and XAML of Wpf in Sql server and then it is armed in Runtime,

I would like to be able to show and publish this great development for the community of Visula FoxPro and WPF

Send images

El viernes, 19 de abril de 2019 10:11:08 a. m. GMT-5, Alexey Badyl <[email protected]> escribió:

To have less memory consumption you should use script.CreateDelegate()

var script = CSharpScript.Create("X*Y", globalsType: typeof(Globals)); ScriptRunner runner = script.CreateDelegate();

The delegate doesn’t hold compilation resources (syntax trees, etc.) alive.
https://github.com/dotnet/roslyn/wiki/Scripting-API-Samples#-create-a-delegate-to-a-script

Workaround:
if you will use script.CreateDelegate(); and then call GC.Collect then your memory will be cleaned up.

To summarize - script.CreateDelegate() allocates a lot of memory but this memory will be collected
during next GC.

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub, or mute the thread.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ghost picture ghost  Â·  229Comments

mgravell picture mgravell  Â·  119Comments

MadsTorgersen picture MadsTorgersen  Â·  170Comments

ilexp picture ilexp  Â·  167Comments

davidroth picture davidroth  Â·  158Comments