0

I'm developing an application that works like an excel sheet: the user types a few values, and a lot of consequent calculation is done, in Blazor.

Right now I'm following this sequence (simplified code can be found at the end of question):

  1. Retrieve all expressions from DB into a List.
    The expressions can be written by the user, in another section. For example, the user can insert that BMI formula is "= @Weight / @Height ^2"
  2. Each property call "RetrieveValue(propertyName)"
  3. Substitute refferences in expression for it's value
    That means, if the expression is "= @Weight / @Height ^ 2", I'll replace '@Weight' and '@Height' for either user-typed values, or for values from other expressions
  4. Evaluate using FLEE (fast lightweight expression evalutor)
    I'd then send the expression "100 / 170 ^2" to be compiled and return it's value to the property

But it's taking too much time (around 300 evaluations taking 40 seconds), so I'm not sure this is the best method. I'm already storing an expression final form and it's result, so I don't have to evaluate again if it has already been evaluated.

What changes can I do to improve performance?

  1.     Expressions = expressionsLibrary.ToList();
    
  2.     public string? BMI
        {
            get
            {
                return RetrieveValue("BMI");
            }
            set
            {
                return;
            }
        }
    
  3.     public string RetrieveValue(string propertyName)
        {
            // Retrieve property expression from list
            var result = Expressions.FirstOrDefault(e => d.Name == propertyName) ?? null;
            object value = result.Value;
    
            // Check for null
            if (result is null)
            {
                return null;
            }
            else if (!(result.Value is null) && result.ValueTrim().StartsWith('='))
            {
                // Replace refferences for numeric valuess
                var replaced = ReplaceRefferences(result.Value)GetAwaiter().GetResult();
    
                // If I didn't evaluate this expression yet, or it has changed
                if (string.IsNullOrEmpty(result.ReplacedValue) || resultado.ReplacedValue != replaced)
                {
                    var index = Expressions.IndexOf(result);
    
                    // Updates expression in list
                    Expressions[index].ReplacedValue = replaced;
    
                    // Evaluate
                    value = Eval(replaced);
    
                    // Updates value in list
                    Expressions[index].EvaluatedValue = value;
                }
                else
                {
                    value = result.EvaluatedValue;
                }
            }
    
            return Convert.ToString(value);
        }
    
  4.     private string Eval(String expression)
        {
            cExpressionContext context = new ExpressionContext();
    
            context.ParserOptions.RecreateParser();
    
            // Allow the expression to use all static public methods of System.Math
            context.Imports.AddType(typeof(Math));
    
            Flee.PublicTypes.IDynamicExpression eDynamic = context.CompileDynamic(expression);
    
            return Convert.ToString(eDynamic.Evaluate());
        }
    

I'm already storing an expression final form and it's result, so I don't have to evaluate again if it has already been evaluated. It works well with a few expressions per call (10 or so)

  • You need to provide reproducible code. This is full of syntax issues and will not run. Please review posting rules in the FAQ. – Marius Dec 26 '22 at 22:02
  • FLEE is interesting in that it compiles to IL, but it appears to be designed around compiling and evaluating being sequential steps. If you can restrict the complexity of your expressions and limit the functions, you could create your own compiler (or use the C# compiler) to create lambda functions that represent the expressions with the variables as parameters, and then reuse them. Perhaps simpler, you might consider preserving your `context` so you don't have to import `Math` every time you evaluate an expression. – NetMage Dec 26 '22 at 22:09
  • You might also consider replacing your substitution step with FLEE's, by changing references to begin with `_` and adding their values as variables before compiling. Which way is best really depends on how many common expressions are in your list of expressions. – NetMage Dec 26 '22 at 22:10
  • @Marius sorry for the code problems, as I'm programming in another language I tried to translate only relevant sections – Wawawigor Dec 27 '22 at 11:03
  • @NetMage thank you for the answers, I'll look into the options and try and implement them. – Wawawigor Dec 27 '22 at 11:10
  • StackOverflow supports most languages - post your real code and you may get better help. – NetMage Dec 27 '22 at 21:19
  • Use .NET's stopwatch or the equivalent in your language to see which steps eat the most CPU time, then you can tweak the biggest chunks first. – John Silence Dec 28 '22 at 17:34

0 Answers0