Automapper解析器性能

本文关键字:性能 Automapper | 更新日期: 2023-09-27 18:27:24

由于我正在处理一个涉及多个DTO及其之间映射的大型项目,我有点担心在两个类和第三个源之间解析和映射值的各种方法的性能。因此,我编写了这个小程序来测试从所提供的变量映射一些公共属性的各种方法。

#region
using System;
using System.Linq;
using System.Threading;
using AutoMapper;
#endregion
namespace ConsoleApplication1
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            Mapper.Reset();
            Thread.Sleep(2000);
            Console.WriteLine("Starting");
            for (int i = 0; i < 10; i++)
            {
                Test1();
                Test2();
                Test3();
                Test4();
            }
            Console.WriteLine("Finished");
            Thread.Sleep(2000);
        }
        private static void Test1()
        {
            Mapper.Reset();
            Mapper.CreateMap<Quote, Policy>()
                .ForMember(m => m.Id, o => o.Ignore())
                .ForMember(m => m.Number, o => o.Ignore())
                .ForMember(m => m.AuditName, o => o.ResolveUsing<UserNameResolver>())
                .ForMember(m => m.AuditTime, o => o.ResolveUsing<AuditTimeResolver>());
            Mapper.AssertConfigurationIsValid();
            Mapper.Configuration.Seal();
            for (int i = 0; i < 10000; i++)
            {
                UserSetting us = new UserSetting {UserName = "Test", ExecTime = DateTime.Now};
                Quote q = new Quote {Id = i, Description = i.ToString("0000000")};
                Policy p = Mapper.Map<Policy>(q, o => o.Items[Constants.UserSetting] = us);
            }
        }
        private static void Test2()
        {
            Mapper.Reset();
            Mapper.CreateMap<Quote, Policy>()
                .ForMember(m => m.Id, o => o.Ignore())
                .ForMember(m => m.Number, o => o.Ignore())
                .ForMember(m => m.AuditName, o => o.ResolveUsing(s => s.Context.Options.GetRequestUserName()))
                .ForMember(m => m.AuditTime, o => o.ResolveUsing(s => s.Context.Options.GetRequestExecTime()));
            Mapper.AssertConfigurationIsValid();
            Mapper.Configuration.Seal();
            for (int i = 0; i < 10000; i++)
            {
                UserSetting us = new UserSetting {UserName = "Test", ExecTime = DateTime.Now};
                Quote q = new Quote {Id = i, Description = i.ToString("0000000")};
                Policy p = Mapper.Map<Policy>(q, o => o.Items[Constants.UserSetting] = us);
            }
        }
        private static void Test3()
        {
            Mapper.Reset();
            Mapper.CreateMap<Quote, Policy>()
                .ForMember(m => m.Id, o => o.Ignore())
                .ForMember(m => m.Number, o => o.Ignore())
                .ForMember(m => m.AuditName, o => o.ResolveUsing(s => s.Context.Options.GetCastRequestUserName()))
                .ForMember(m => m.AuditTime, o => o.ResolveUsing(s => s.Context.Options.GetCastRequestExecTime()));
            Mapper.AssertConfigurationIsValid();
            Mapper.Configuration.Seal();
            for (int i = 0; i < 10000; i++)
            {
                UserSetting us = new UserSetting {UserName = "Test", ExecTime = DateTime.Now};
                Quote q = new Quote {Id = i, Description = i.ToString("0000000")};
                Policy p = Mapper.Map<Policy>(q, o => o.Items[Constants.UserSetting] = us);
            }
        }
        private static void Test4()
        {
            Mapper.Reset();
            Mapper.CreateMap<Quote, Policy>()
                .ForMember(m => m.Id, o => o.Ignore())
                .ForMember(m => m.Number, o => o.Ignore())
                .ForMember(m => m.AuditName,
                    o =>
                        o.ResolveUsing<SingletonUserNameResolver>()
                            .ConstructedBy(() => SingletonUserNameResolver.Instance))
                .ForMember(m => m.AuditTime,
                    o =>
                        o.ResolveUsing<SingletonAuditTimeResolver>()
                            .ConstructedBy(() => SingletonAuditTimeResolver.Instance));
            Mapper.AssertConfigurationIsValid();
            Mapper.Configuration.Seal();    
            for (int i = 0; i < 10000; i++)
            {
                UserSetting us = new UserSetting {UserName = "Test", ExecTime = DateTime.Now};
                Quote q = new Quote {Id = i, Description = i.ToString("0000000")};
                Policy p = Mapper.Map<Policy>(q, o => o.Items[Constants.UserSetting] = us);
            }
        }
    }
    internal class UserSetting
    {
        public string UserName { get; set; }
        public DateTime ExecTime { get; set; }
    }
    public class Policy
    {
        public long Id { get; set; }
        public string Description { get; set; }
        public string Number { get; set; }
        public string AuditName { get; set; }
        public DateTime AuditTime { get; set; }
    }
    internal class Quote
    {
        public long Id { get; set; }
        public string Description { get; set; }
        public string AuditName { get; set; }
        public DateTime AuditTime { get; set; }
    }
    public static class Extensions
    {
        public static string GetRequestUserName(this MappingOperationOptions options)
        {
            // ReSharper disable once PossibleNullReferenceException
            return (options.Items[Constants.UserSetting] as UserSetting).UserName;
        }
        public static DateTime GetRequestExecTime(this MappingOperationOptions options)
        {
            // ReSharper disable once PossibleNullReferenceException
            return (options.Items[Constants.UserSetting] as UserSetting).ExecTime;
        }
        public static string GetCastRequestUserName(this MappingOperationOptions options)
        {
            // ReSharper disable once PossibleNullReferenceException
            return (options.Items.Values.Cast<UserSetting>().First()).UserName;
        }
        public static DateTime GetCastRequestExecTime(this MappingOperationOptions options)
        {
            // ReSharper disable once PossibleNullReferenceException
            return (options.Items.Values.Cast<UserSetting>().First()).ExecTime;
        }
    }
    public static class Constants
    {
        public const string UserSetting = "UserSetting";
    }
    public class UserNameResolver : IValueResolver
    {
        #region IValueResolver Members
        public ResolutionResult Resolve(ResolutionResult source)
        {
            return source.New((source.Context.Options.Items[Constants.UserSetting] as UserSetting).UserName);
        }
        #endregion
    }
    public class SingletonUserNameResolver : IValueResolver
    {
        private static Lazy<SingletonUserNameResolver> _instance =
            new Lazy<SingletonUserNameResolver>(() => new SingletonUserNameResolver());
        public static SingletonUserNameResolver Instance
        {
            get { return _instance.Value; }
        }
        #region IValueResolver Members
        public ResolutionResult Resolve(ResolutionResult source)
        {
            return source.New((source.Context.Options.Items[Constants.UserSetting] as UserSetting).UserName);
        }
        #endregion
    }
    public class AuditTimeResolver : IValueResolver
    {
        #region IValueResolver Members
        public ResolutionResult Resolve(ResolutionResult source)
        {
            return source.New((source.Context.Options.Items[Constants.UserSetting] as UserSetting).ExecTime);
        }
        #endregion
    }
    public class SingletonAuditTimeResolver : IValueResolver
    {
        private static Lazy<SingletonAuditTimeResolver> _instance =
            new Lazy<SingletonAuditTimeResolver>(() => new SingletonAuditTimeResolver());
        public static SingletonAuditTimeResolver Instance
        {
            get { return _instance.Value; }
        }
        #region IValueResolver Members
        public ResolutionResult Resolve(ResolutionResult source)
        {
            return source.New((source.Context.Options.Items[Constants.UserSetting] as UserSetting).ExecTime);
        }
        #endregion
    }
}

在Test1中,我使用自定义值解析器从Context.Options属性的Items字典中获取用户名和执行时间(标准审核字段)。在Test2中,我绕过解析器,使用MappingOperationsOptions上的扩展方法直接查找字典。在Test3中,我也使用了相同的字典,但使用了LINQ泛型Cast,因为我知道只有一个UserSetting类型的对象。

当在RedGate的ANTS Profiler的评测辅助下运行时,Test3总共花费了559.667ms,Test2花费了565.783ms,而Test1花费了1752.538ms。随着我深入研究,发现自定义值解析器的构造函数在测试的每次迭代中都会被调用,每次调用十万次。有没有可能避免那些昂贵的(在生产场景中为1-4ms)呼叫。还是我错过了一些显而易见的东西?

更新

首先,代码已经更新,以便实际执行映射。然后我添加了一个Singleton反模式来只构造一次自定义解析器。以下是结果:

Test1 - 3189.186ms 
Test2 - 1297.870ms
Test3 - 1807.477ms
Test4 - 1381.149ms

因此,字典上的扩展方法仍然是最快的,但单例解析器紧随其后。

测试是在联想ThinkStation C20上执行的,该C20具有24GB RAM和双四核Xeon 2.27GHz CPU。

更新2不确定我是否达到了Peer先生概述的性能基准测试标准,但在将构建更改为Release并"预热"测试后,以下是新的结果:

    private static void Main(string[] args)
    {
        Mapper.Reset();
        Thread.Sleep(2000);
        Console.WriteLine("Warming up");
        RunTests();
        Console.WriteLine("Starting");
        for (int i = 0; i < 10; i++)
        {
            RunTests();
        }
        Console.WriteLine("Finished");
        Thread.Sleep(2000);
    }
    private static void RunTests()
    {
        Test1();
        Test2();
        Test3();
        Test4();
    }

结果(与儿童相处的时间以毫秒为单位):

Test1 3405.485
Test2 1620.115
Test3 2005.364
Test4 1499.720

Visual Studio重新启动后

Test1 3654.204
Test2 1568.931
Test3 1901.894
Test4 2080.056

谢谢你到目前为止对我的帮助。

Automapper解析器性能

所以,长话短说,在使用自定义解析器时要注意,如果需要,可以应用Singleton反模式,或者使用扩展方法来提高性能。