Blazor类Excel应用多数学表达式求值性能优化咨询
Blazor类Excel应用性能优化问题
我正在Blazor中开发一款类Excel应用:用户输入若干值后,系统会执行大量后续计算。当前约300次求值耗时40秒,少量表达式(10个左右)时表现正常,已存储表达式最终形式及结果避免重复求值,寻求性能提升方案。
当前执行流程
- 从数据库读取所有表达式至List:表达式由用户在其他模块编写,例如BMI公式可写为
"= @Weight / @Height ^2" - 每个属性调用RetrieveValue(propertyName)
- 替换表达式中的引用为对应值:比如将
"= @Weight / @Height ^ 2"里的@Weight和@Height替换为用户输入值或其他表达式的计算结果 - 使用FLEE求值:把替换后的表达式(如"100 / 170 ^2")传入编译并返回结果给属性
简化代码示例
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 => e.Name == propertyName) ?? null; object value = result.Value; // Check for null if (result is null) { return null; } else if (!(result.Value is null) && result.Value.Trim().StartsWith('=')) { // Replace references for numeric values var replaced = ReplaceRefferences(result.Value).GetAwaiter().GetResult(); // If I didn't evaluate this expression yet, or it has changed if (string.IsNullOrEmpty(result.ReplacedValue) || result.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()); }
性能优化方案
1. 缓存FLEE编译后的表达式
当前Eval方法每次都重新创建上下文、添加引用并编译表达式,这是核心性能瓶颈。新增字典缓存编译后的表达式,避免重复编译:
private Dictionary<string, Flee.PublicTypes.IDynamicExpression> _compiledExprCache = new Dictionary<string, Flee.PublicTypes.IDynamicExpression>(); private string Eval(string expression) { if (_compiledExprCache.TryGetValue(expression, out var compiledExpr)) { return Convert.ToString(compiledExpr.Evaluate()); } var context = new ExpressionContext(); context.Imports.AddType(typeof(Math)); // 移除不必要的RecreateParser调用,默认已初始化解析器 var dynamicExpr = context.CompileDynamic(expression); _compiledExprCache[expression] = dynamicExpr; return Convert.ToString(dynamicExpr.Evaluate()); }
若表达式会频繁变更,可在表达式更新时清理对应缓存项。
2. 优化表达式查找效率
用Dictionary<string, 表达式实体类>替代List存储表达式,将表达式名称作为Key,把查找复杂度从O(n)降为O(1):
// 初始化时替换List为字典 private Dictionary<string, ExpressionItem> _expressionDict = expressionsLibrary.ToDictionary(e => e.Name); // 修改RetrieveValue中的查找逻辑 if (!_expressionDict.TryGetValue(propertyName, out var result)) { return null; }
3. 消除异步方法的同步阻塞
代码中ReplaceRefferences(result.Value).GetAwaiter().GetResult()会导致线程阻塞,建议将RetrieveValue改为异步方法,或把ReplaceRefferences改为同步实现:
// 异步版本示例 public async Task<string?> RetrieveValueAsync(string propertyName) { if (!_expressionDict.TryGetValue(propertyName, out var result)) { return null; } object value = result.Value; if (!(result.Value is null) && result.Value.Trim().StartsWith('=')) { var replaced = await ReplaceRefferences(result.Value); if (string.IsNullOrEmpty(result.ReplacedValue) || result.ReplacedValue != replaced) { result.ReplacedValue = replaced; // 把求值放到后台线程避免阻塞UI value = await Task.Run(() => Eval(replaced)); result.EvaluatedValue = value; } else { value = result.EvaluatedValue; } } return Convert.ToString(value); }
4. 优化表达式替换逻辑
预编译正则表达式匹配@xxx格式的引用,减少字符串操作开销:
private static readonly Regex _referenceRegex = new Regex(@"@(\w+)", RegexOptions.Compiled); private string ReplaceRefferences(string expression) { return _referenceRegex.Replace(expression, match => { var propName = match.Groups[1].Value; // 直接从缓存读取已计算的值,避免递归调用RetrieveValue return _expressionDict.TryGetValue(propName, out var item) ? item.EvaluatedValue?.ToString() ?? "0" : "0"; }); }
5. 批量计算而非按需求值
构建表达式依赖图,当用户输入变更时,仅重新计算依赖该输入的表达式,避免重复触发求值:
- 提前分析每个表达式的依赖项(如BMI依赖Weight、Height)
- 按依赖顺序批量计算,减少零散调用的开销
6. 移除冗余操作
删除Eval方法中的context.ParserOptions.RecreateParser(),该操作会强制重建解析器,无必要且增加性能开销。
内容的提问来源于stack exchange,提问作者Wawawigor
相关产品推荐
相关产品推荐

