You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Blazor类Excel应用多数学表达式求值性能优化咨询

Blazor类Excel应用性能优化问题

我正在Blazor中开发一款类Excel应用:用户输入若干值后,系统会执行大量后续计算。当前约300次求值耗时40秒,少量表达式(10个左右)时表现正常,已存储表达式最终形式及结果避免重复求值,寻求性能提升方案。

当前执行流程

  1. 从数据库读取所有表达式至List:表达式由用户在其他模块编写,例如BMI公式可写为"= @Weight / @Height ^2"
  2. 每个属性调用RetrieveValue(propertyName)
  3. 替换表达式中的引用为对应值:比如将"= @Weight / @Height ^ 2"里的@Weight和@Height替换为用户输入值或其他表达式的计算结果
  4. 使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 11:05:23