Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve performance by compiling into lambda method calls, property getters etc #4

Open
VitaliyMF opened this issue Sep 23, 2016 · 5 comments

Comments

@VitaliyMF
Copy link
Contributor

VitaliyMF commented Sep 23, 2016

Right now all invocations are performed through reflection (InvokeMethod / InvokeDelegate / InvokeIndexer / InvokePropertyOrField). At the same time, evaluation performance is acceptable for most applications: 10,000 evals take about 20-30ms (depending on CPU).

It is possible to improve evaluation performance in cases if invocation is not ambiguous (only one method overload is available according to number of parameters); this might be useful if LambdaParser is used for in-memory filtering of large dataset (say, >100k rows) by user-defined expression.

If someone needs this improvement please leave a comment or vote for this issue.

@lafar6502
Copy link

lafar6502 commented Nov 9, 2018

Hi, i was just going to add an issue for that, but what you described is quite close
The idea i had was to use NReco lambda parser to dynamically build lambda expressions for Linq queries

assuming I have IQueryable interface, i'd like to build filtering predicate expression for the where function: Expression<Func<T, bool>>
but im not sure if it's possible with NReco to build right type of expression (lambda with correct parameter type). Could you give me some suggestions here if/how to do that?

** edit
I see how the parsing works in nreco - all parameters identified in the expression are then translated to lambda parameters - so you have to supply all of them to invoke.
But i'd like something different - could the parser treat all parameters as fields on the input object (the single parameter to the lambda)? This way we could easily make lambdas for linq operations on collections.
Example

this expression
"\"Hello, \" + Name + \", your birth year is \" + BirthDate.Year"

will be parsed as such lambda:
(Name, BirthDate) => "Hello, " + Name + ", your birth year is " + BirthDate.Year

what i'd like to get is
x => "Hello, " + x.Name + ", your birth year is " + x.BirthDate.Year

where x is an object of some type T (T can be known at the time of parsing)

possible?

Thanks
RG

@VitaliyMF
Copy link
Contributor Author

But i'd like something different - could the parser treat all parameters as fields on the input object (the single parameter to the lambda)?

yes - this is possible. In fact you already can do that with some piece of extra code smth like:

Expression expr = new LambdaParser().Parse("a>5");
var exprParams = LambdaParser.GetExpressionParameters(expr);
// then add code that maps Func<,> arguments to exprParams
// and compile it to Func<,bool> delegate that can be used in IQueryable 

Also you can check how object Eval(string expr, Func<string,object> getVarValue) is implemented in LambdaParser.cs, I believe its code can answer on most of your possible questions.

I guess it is possible to add overloads like Expression<Func<T1,T2>> LambdaParser.Parse<T>(string expr, string t1VarNameInExpr) that will make all these things inside LambdaParser library; feel free to add new issue for this.

BTW, this issue is about quote another thing - evaluation performance optimization. Linq expressions itself are strongly typed, but in most cases this is badly usable for user-defined formulas or expressions, especially if variables are not strongly typed. LambdaParser supports evaluation that is similar to C# 'dynamic' type (thats why internally LambdaParameterWrapper is used) but this has some implication on execution performance - it starts to be noticeable when expression is evaluated > 100,000 times (say, for dataset filtering). It is still a place to get better execution time by replacing reflection methods invocation with cache of delegates.

@some1one
Copy link
Contributor

some1one commented Mar 28, 2019

So, I am using this for some configurable formula evaluations. I can always make a class that inherits from LambdaParser but it would be nice if this was already included. I want two things out of this; 1) To have a cached compiled delegate returned and 2) to have a dictionary of Func (value getters) so values could be dynamically retrieved upon invocation.

I might have gotten confused about the reason for this issue but here is a sample idea of how this could be done. A cached of value getter results may be useful too if something is static but that would take some thought as it kind of conflicts with this. This is also not very useful if caching is turned off.

public Func<object> CreateDelegate(string expr, IDictionary<string, Func<object>> valueGetters)
{
    return () => Eval(expr, valueGetters);
}

public object Eval(string expr, IDictionary<string, Func<object>> vars) {
    return Eval(expr, (varName) => {
        Func<object> val = null;
	vars.TryGetValue(varName, out val);
        return val?.Invoke();
    });
}

@VitaliyMF
Copy link
Contributor Author

@some1one this issue is actually about reflection-based invocations made with InvokeMethod.cs: they may be even faster when compiled to delegate, this is possible in some cases (when no method overloads for the same number of arguments).

As I understand, you mean caching of variables; I've no idea why you need one more "Eval" overload as you can easily cache variables on your side with public object Eval(string expr, Func<string,object> getVarValue) overload, for example:

Func<string,object> realGetValueDelegate = ... ;
var varCache = new Dictionary<string,object>();
var exprResult = lambdaParser.Eval(exprStr, (varName) => {
	if (varCache.TryGetValue(varName, out var v))
		return v;
	var val = realGetValueDelegate(varName);
	varCache[varName] = val;
	return val;
});

No need to inherit from LambdaParser; if you like you can add extension method that adds this 'variables caching' behavior and use it in your project.

However, caching of variables may cause various side-effects and usually this is very project-specific thing. For now I'm not sure that it is better to include this code into library.

@some1one
Copy link
Contributor

Okay, I understand now. I don't need that kind of performance for this project, but the custom filtering use case has already come up on another project that I am considering this for now. I'll have to do some benchmark s to see if will actually be a problem though.

And for the variable caching, yes, that makes a lot more sense when I think about it that way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants