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

创建LogEventInfo时反射的性能影响及优化实现方案咨询

优化NLog LogEventInfo生成的性能方案

针对百万级调用场景,反射带来的性能损耗确实不可忽视,以下是几种可行的优化方案,按性能从高到低排序:

1. 手动属性映射(性能最优)

如果ILogItem的具体实现类数量不多,直接编写手动赋值的代码,完全避免反射开销。示例:

// 针对具体实现类的扩展方法
public static LogEventInfo ToLogEventInfo(this OrderLogItem data, string message, Exception ex = null)
{
    var eventInfo = new LogEventInfo();

    if (ex != null)
        eventInfo.Exception = ex;

    data.EventMessage = message;
    data.LogTime = DateTime.UtcNow; // 替换原转换逻辑,直接获取UTC时间更高效
    data.LogId = Guid.NewGuid();

    // 手动映射所有属性
    eventInfo.Properties[nameof(data.LogId)] = data.LogId;
    eventInfo.Properties[nameof(data.LogTime)] = data.LogTime;
    eventInfo.Properties[nameof(data.EventMessage)] = data.EventMessage;
    // 其他属性依次添加...

    return eventInfo;
}

优势:运行时无任何反射开销,性能达到极致;缺点:新增ILogItem实现类时需要同步维护映射代码,灵活性较差。

2. 缓存属性访问委托(平衡性能与灵活性)

通过表达式树编译属性访问的委托,首次调用时编译并缓存,后续复用编译后的委托,性能接近手动映射,同时保留灵活性。

首先创建缓存类:

public static class LogEventInfoMapperCache
{
    private static readonly Dictionary<Type, Action<ILogItem, LogEventInfo>> _mapperCache = new();
    private static readonly object _lockObj = new();

    public static Action<ILogItem, LogEventInfo> GetMapper(Type logItemType)
    {
        if (_mapperCache.TryGetValue(logItemType, out var mapper))
            return mapper;

        lock (_lockObj)
        {
            if (_mapperCache.TryGetValue(logItemType, out mapper))
                return mapper;

            mapper = CreatePropertyMapper(logItemType);
            _mapperCache.Add(logItemType, mapper);
            return mapper;
        }
    }

    private static Action<ILogItem, LogEventInfo> CreatePropertyMapper(Type logItemType)
    {
        var dataParam = Expression.Parameter(typeof(ILogItem), "data");
        var eventParam = Expression.Parameter(typeof(LogEventInfo), "eventInfo");

        // 将ILogItem转换为具体类型
        var castData = Expression.TypeAs(dataParam, logItemType);

        var assignmentExpressions = new List<Expression>();
        var properties = logItemType.GetProperties(BindingFlags.Public | BindingFlags.Instance);

        foreach (var prop in properties)
        {
            // 生成 eventInfo.Properties[prop.Name] = data.PropName 的表达式
            var propNameConst = Expression.Constant(prop.Name);
            var propValue = Expression.Property(castData, prop);
            var propertiesIndex = Expression.Property(eventParam, nameof(LogEventInfo.Properties));
            var assignment = Expression.Assign(
                Expression.Property(propertiesIndex, "Item", propNameConst),
                propValue
            );

            assignmentExpressions.Add(assignment);
        }

        var block = Expression.Block(assignmentExpressions);
        return Expression.Lambda<Action<ILogItem, LogEventInfo>>(block, dataParam, eventParam).Compile();
    }
}

然后修改原扩展方法:

public static LogEventInfo ToLogEventInfo(this ILogItem data, string message, Exception ex = null)
{
    var eventInfo = new LogEventInfo();

    if (ex != null)
        eventInfo.Exception = ex;

    if (data != null)
    {
        data.EventMessage = message;
        data.LogTime = DateTime.UtcNow; // 优化UTC时间获取
        data.LogId = Guid.NewGuid();

        // 使用缓存的委托完成属性映射
        var mapper = LogEventInfoMapperCache.GetMapper(data.GetType());
        mapper(data, eventInfo);
    }
    else
    {
        if (!string.IsNullOrEmpty(message))
            eventInfo.Message = message;
    }

    return eventInfo;
}

优势:仅首次调用某类型时存在编译开销,后续调用性能接近手动映射;支持任意ILogItem实现类,无需手动维护映射代码。

3. 源生成器(C# 9+ 推荐)

利用C#源生成器在编译时自动生成ILogItem实现类的映射代码,既保留手动映射的性能,又无需手动维护代码。

核心思路:

  • 创建实现ISourceGenerator的类,扫描所有实现ILogItem的类型
  • 为每个类型生成对应的ToLogEventInfo扩展方法,代码逻辑与手动映射一致
  • 编译时自动将生成的代码纳入项目,运行时无反射开销

优势:性能与手动映射一致,且新增ILogItem实现类时自动生成映射代码,零维护成本;缺点:需要熟悉C#源生成器的编写,对项目环境有版本要求(C# 9+)。

额外小优化

原代码中TimeZoneInfo.ConvertTime(DateTime.Now, TimeZoneInfo.Utc)可以直接替换为DateTime.UtcNow,两者结果一致,但后者无需转换操作,性能更优。

内容的提问来源于stack exchange,提问作者wadefanyaoxia

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 09:07:08