dotnet / roslyn

The Roslyn .NET compiler provides C# and Visual Basic languages with rich code analysis APIs.
https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/
MIT License
18.9k stars 4.01k forks source link

CSharpScript speed (or lack of) #16897

Open epsitec opened 7 years ago

epsitec commented 7 years ago

I am experimenting with version 2.0.0-rc3, running on Windows 7 x64 with .NET Framework 4.6.2.

When running very simple scripts such as these two:

var script = CSharpScript.Create<int> ("42", options: options, globalsType: typeof (Record));
var result = await script1.RunAsync (new Record ());

or

var result = await CSharpScript.EvaluateAsync ("42", globals: new Record ());

I consistently get execution times which lie between 70 and 100 milliseconds. I understand there is a cost to compile the script, however I cannot use CSharpScript with such execution times, as I need to evaluate hundreds of little code snippets.

What is the recommended approach if I need to evaluate lots of small pieces of C# - I'd like to pay the startup time only once, and be able to then execute my snippets one after the other, getting back the results as fast as possible.

amrkarthik commented 6 years ago

@mattwar @tmat I'm also getting ~5 seconds while executing the expression for the first time and subsequent calls take milliseconds. We are having multiple scripts to execute, so we combined them now to reduce the execution time. But still, the first execution is not acceptable for us. Is there any way to reduce the first execution time?

HaseebKhanMd commented 6 years ago

@mattwar @tmat Every time we load a new script ( dynamic logic) the penalty is around 4-5s. Earlier I anticipated that this time is just a one time of assembly loading and linking. It seems like there is a penalty whenever you call CSharpScript.Create for even a small change in the script. definitely looking for some workaround or optimization or some help here

Anteru commented 5 years ago

Seems to get better with .NET Core, I'm getting roughly ~1.8 sec there. Not good enough yet, but not as horrible as it used to be.

bhupi196 commented 5 years ago

I am facing the samw issue, Whenever I am loading a new script, It is taking 4-5 seconds, Please let me know if anyone has got a workaround for this ? The following line takes 4 seconds on my machine:

var script2 = CSharpScript.Create<object>("System.Console.WriteLine(\"Something\");");
script2.Compile();

Any help will be appreciated

jnm2 commented 5 years ago

For cold startup in a net472 console app, that snippet takes 2155.4ms on my machine. Subsequent executions take 5.4ms.

bhupi196 commented 5 years ago

@jnm2 I am not sure what is the issue, This is my new test console App with zero code, I am just testing the performance, Following is the installed API version information. rooslyn

bhupi196 commented 5 years ago

For cold startup in a net472 console app, that snippet takes 2155.4ms on my machine. Subsequent executions take 5.4ms.

2+ seconds is still not a fair result, considering the amount of code it is compiling. Please let me know if there is any way-out of this

jnm2 commented 5 years ago

I would like to know what the bottleneck is, too. Best thing would be to profile the console application running the snippet exactly once, and looking at that 2 seconds of data to see where the time is going. Maybe DLL loading from the file system or jitting, for instance?

Miggleness commented 5 years ago

I'm having two problems, too: 1) As reported by this issue, compile time is rather slow. On my X1 Carbon, it takes 3.5s on the first run and 160 on successive runs 2) High memory utilization as reported in #22219

While there are things that need improvement, are there any general guidelines to improve performance of using the scripting API?

CaptainAgilek commented 5 years ago

Create method takes 10 seconds. Is this normal?? Didn't find any solution in this thread.

` hostScriptingAPI = new ScriptingAPI(this);

        var loadedScript = "DebugMessage(\"debug\")";

        var options = ScriptOptions.Default;

        script = CSharpScript.Create(loadedScript, options, hostScriptingAPI.GetType());
        script.RunAsync(hostScriptingAPI);`
silvairsoares commented 4 years ago

I have the same slowness problem here. Following the tip of the user mattwar (extending from an empty standard script), I managed to greatly improve the performance, but it is still not satisfactory.

What am I doing:

I abandoned the practice of passing the object with data to be validated, in the "globals" parameter. This practice is definitely not feasible at the moment. Instead, I am simplifying the expression before sending it out for validation.

This simplification consists of using the values ​​of the attributes of the object that I intend to validate, converted to primary types of C #, instead of sending the object itself in the "globals" parameter to the "CSharpScript.EvaluateAsync ()" method. I will certainly have a little more code, but this practice has dramatically improved performance here:

1 - Approach with low performance:

/*
sampleVM = 
{
    "Value1": 15,
    "Value2": 40,
    "ValueA": "ABC",
    "ValueB": "DEF"
}
*/
private readonly string DynamicRule = "(Value1 > 10 && Value2 < 50 && ValueA == 'ABC' && ValueB == 'DEF')";
string expression = DynamicRule.Replace("\'", "\"");
var resultScript = CSharpScript.EvaluateAsync(expression, globals: sampleVM).Result;

1

2 - Approach with the expression simplifier:

In this approach, I am using the "ContinueWith ()" method (user tip "mattwar") from a previously instantiated expression, embedded in my controllers.

private readonly string DynamicRule = "(Value1 > 10 && Value2 < 50 && ValueA == 'ABC' && ValueB == 'DEF')";
string expression = SimplifyExpression(sampleVM);
var resultScript = _DynamicExpressions.CreateScript().ContinueWith(expression).RunAsync().GetResult().ReturnValue;

private string SimplifyExpression(SampleVM sampleVM)
{
    string expression = DynamicRule.Replace("\'", "\"");

    expression = expression.Replace("Value1", sampleVM.Value1.ToString());
    expression = expression.Replace("Value2", sampleVM.Value2.ToString());
    expression = expression.Replace("ValueA", "\"" + sampleVM.ValueA.ToString() + "\"");
    expression = expression.Replace("ValueB", "\"" + sampleVM.ValueB.ToString() + "\"");

    return expression;
}

2

Comparing approach number 2 with number 1, I had a performance gain of 265%.

Here is the demo project with all the implementation details I’m using.

CSharpScriptWebApi on GitHub

Note: Although, in addition to performance in the first run is poor. In the approach I'm using, performance deteriorates with each new request. Hinting that something is being accumulated in the context of script execution.

silvairsoares commented 4 years ago

If the need is "just" to validate dynamic expressions at run time, I strongly advise you to take a look at this library here, it worked like a glove for me. It does the same thing that I was doing with "CSharp.Scripting" a thousandth of the time.

Allsetra-RB commented 3 years ago

If the need is "just" to validate dynamic expressions at run time, I strongly advise you to take a look at this library here, it worked like a glove for me. It does the same thing that I was doing with "CSharp.Scripting" a thousandth of the time.

I tried that NuGet package, and I can verify that the performance seems excellent (especially when you keep an instance of the context alive across calls). This is the performance I was expecting from Roslyn. It runs simple formula snippets in about 0.1ms per snippet, resulting in running the same expression 3000 times in ~350ms. It handles strings, conditional expressions and accepts function imports for e.g. Math and custom classes as well.

Maybe the Roslyn script engine can use some assumptions to improve performance, like running code snippets in the current application context using the current set of references. If more complicated actions are needed, you could define some kind of isolated environment before running scripts on it. For simple code snippets à la NCalc, Roslyn is still very slow.

mgood7123 commented 8 months ago

please note this https://weblog.west-wind.com/posts/2022/Jun/07/Runtime-CSharp-Code-Compilation-Revisited-for-Roslyn

Roslyn also includes a Scripting API that is meant to simplify this process by letting you run just a snippet of code or an individual expression rather than having to first create a class manually.

Behind the scenes the code still produces a class and assembly, but with the Scripting API this is hidden so you just pass in a code snippet.

Although the scripting code is significantly simpler than the full compilation code, it's important to understand that behind the scenes the library still creates and compiles a C# type and produces an in-memory assembly.

By default scripts create new assemblies on every script run which can bloat a processes memory usage very quickly.

cjohnson57 commented 3 months ago

If the need is "just" to validate dynamic expressions at run time, I strongly advise you to take a look at this library here, it worked like a glove for me. It does the same thing that I was doing with "CSharp.Scripting" a thousandth of the time.

I also found DynamicExpresso helped me similarly.