sklose / NCalc2

expression evaluator for .NET with built-in compiler
MIT License
166 stars 58 forks source link

Add built-in math functions to use in lambdas as in NCalc #96

Closed eugenca closed 5 months ago

eugenca commented 5 months ago

Only changes in LambdaExpressionVistor.cs

eugenca commented 5 months ago

You can check this is actually working in lambda here: https://eugenca.github.io/wasm_projects/runge-kutta/ image

eugenca commented 5 months ago

Actually, 12 000 000 evaluations (4 per step of 3kk steps) of sin(x)+cos(sin(x))-tan(log10(x+2)) plus some math (rk4) in 2908ms in browser wasm. C++ could make 72 000 000 evaluations (in absolutely cache-friendly loop)

Bykiev commented 5 months ago

Can you please attach BenchmarkDotNet tests to compare performance with the current version?

eugenca commented 5 months ago

@david-brink-talogy @sklose this is experimental behavior, and it could break some existing code, so I ask about possibilities of existence of that functionality

eugenca commented 5 months ago

@Bykiev

Can you please attach BenchmarkDotNet tests to compare performance with the current version?

Performance is just the same. If you have considerations of different tests please tell and I'll test.

Sample code (I made internal dictionary to have SINX and COSX to test performance)

public struct Context
{
    public double x;
    public double Sin(double x) { return Math.Sin(x); }
    public double Cos(double x) { return Math.Cos(x); }
}

public class NCalcBench
{
    private RK4Func funcInternal;
    private RK4Func funcContextMethods;
    private Context context; 

    public NCalcBench()
    {
        funcInternal = FunctionParser.Parse<Context>("sinx(x) + cosx(x)");
        funcContextMethods = FunctionParser.Parse<Context>("sin(x) + cos(x)");
        context = new Context() { x = 5 };
    }
    [Benchmark]
    public void RunInternalFunc()
    {
        double result = 0;
        result = funcInternal(context);
    }
    [Benchmark]
    public void RunContextFunc()
    {
        double result = 0;
        result = funcContextMethods(context);
    }
}

Results:

| Method          | Mean     | Error    | StdDev   |
|---------------- |---------:|---------:|---------:|
| RunInternalFunc | 10.49 ns | 0.175 ns | 0.164 ns |
| RunContextFunc  | 10.66 ns | 0.021 ns | 0.020 ns |
sklose commented 5 months ago

Hi @eugenca,

I like adding support for these functions to the lambda compilation visitor. I didn't get a chance to review these changes in detail. 2 things I would like to ensure with this:

  1. the interpreter and the compiler have support for the same set of functions with the same behavior
  2. the user can still override these functions as part of the context (that way it's not a breaking change and the behavior is customizable)

Happy to get this merged if we can ensure these two things.

eugenca commented 5 months ago

Ok! I will try to ensure this. If anyone have additional ideas for tests please share

eugenca commented 5 months ago

@sklose Added tests to ensure your requirements. Tested Lambda with double types only for now

@david-brink-talogy Added method caching into static dictionary But it seems that getting method without caching brings no extra overhead compared to lambda creation. I felt no difference with or without cache

sklose commented 5 months ago

LGTM, just needs to be rebased

eugenca commented 5 months ago

Maybe it is worth to update readme with examples or links, to allow users to know about this functionality