在大比拼之前先讲一个小插曲,我这个人以前比较低调,做了很多好东西仅仅在公司内的朋友圈项目圈内分享,很少在博客园内进行分享,后来在dudu 老大的文章博客园现代化建设——AutoMapper有感便推荐一下OOMapper 组件,于是乎接连写了几篇入门性的介绍使用文章:
在园友Repository 兄的NLiteMapper与EmitMapper性能简单比较中了解到NLiteMapper与EmitMapper的性能巨大差距,于是乎进行了两天的性能优化,同时总结了优化过程:一次性能优化最佳实践。在这里非常感谢Repository 兄的测试,也非常感谢他把OOMapper纠正为NLiteMapper,否则NLiteMapper的性能是非常低下的,同时感谢dudu,感谢博客园给大家一个平台,在这个平台使我学到了很多很多......
不说废话进入主题。
准备工作:
- 软硬件环境:VS2008,.net3.5, xp 双核
- 测试组件(都是最新Release版本):AutoMapper.dll(v2.0), EmitMappe.dll (V1.0),NLite.dll(V1.0)
性能测试工具:老赵的CodeTimer
测试接口代码:
[Contract]
public interface IObjectToObjectMapper
{
//初始化映射器
void Initialize();
//执行映射
void Map();
}
为了输出更友好的结果定义一下测试元数据代码:
//测试映射器元数据
public interface IMapperMetadata
{
//目录
string Category { get; }
//名称
string Name { get; }
string Descrption { get; }
}
//映射器元数据注解
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
[MetadataAttributeAttribute]
public class MapperAttribute : ComponentAttribute
{
public string Category { get; set; }
public string Name { get; set; }
public string Descrption { get; set; }
}
利用NLite的Mini容器书写测试框架代码如下:测试次数10万次
class Program
{
[InjectMany]
private Lazy<IObjectToObjectMapper,IMapperMetadata>[] Mappers;
//初始化映射器,并做一次映射操作
void Init()
{
foreach (var item in Mappers)
{
item.Value.Initialize();
item.Value.Map();
}
}
//进行测试
void Run()
{
foreach (var item in Mappers)
CodeTimer.Time(item.Metadata.Category +"->" + item.Metadata.Name , 100000,() => item.Value.Map());
}
static void Main(string[] args)
{
ServiceRegistry.RegisteryFromAssemblyOf<Program>();
var host = new Program();
ServiceRegistry.Compose(host);
host.Init();
host.Run();
Console.Read();
}
}
这样完成了测试框架的搭建,现在就开始书写测试代码了。
定义测试数据:
public class ModelObject
{
public DateTime BaseDate { get; set; }
public ModelSubObject Sub { get; set; }
public ModelSubObject Sub2 { get; set; }
public ModelSubObject SubWithExtraName { get; set; }
}
public class ModelSubObject
{
public string ProperName { get; set; }
public ModelSubSubObject SubSub { get; set; }
}
public class ModelSubSubObject
{
public string IAmACoolProperty { get; set; }
}
public class ModelDto
{
public DateTime BaseDate { get; set; }
public string SubProperName { get; set; }
public string Sub2ProperName { get; set; }
public string SubWithExtraNameProperName { get; set; }
public string SubSubSubIAmACoolProperty { get; set; }
}
定义测试基类:
public abstract class MapperBase : IObjectToObjectMapper
{
protected ModelObject _source;
protected ModelDto _target;
protected virtual void OnInitialize() { }
public void Initialize()
{
OnInitialize();
_source = new ModelObject
{
BaseDate = new DateTime(2007, 4, 5),
Sub = new ModelSubObject
{
ProperName = "Some name",
SubSub = new ModelSubSubObject
{
IAmACoolProperty = "Cool daddy-o"
}
},
Sub2 = new ModelSubObject
{
ProperName = "Sub 2 name"
},
SubWithExtraName = new ModelSubObject
{
ProperName = "Some other name"
},
};
}
public abstract void Map();
}
手工映射代码:
[Mapper(Category = "Flattening.Class", Name = "Manual")]
public class ManualMapper : MapperBase
{
public override void Map()
{
var destination = new ModelDto
{
BaseDate = _source.BaseDate,
Sub2ProperName = _source.Sub2.ProperName,
SubProperName = _source.Sub.ProperName,
SubSubSubIAmACoolProperty = _source.Sub.SubSub.IAmACoolProperty,
SubWithExtraNameProperName = _source.SubWithExtraName.ProperName
};
}
}
AutoMapper 映射代码:
[Mapper(Category = "Flattening.Class", Name = "AutoMapper")]
public class AutoMapperWrapper : MapperBase
{
protected override void OnInitialize()
{
AutoMapper.Mapper.Initialize(cfg =>
{
cfg.CreateMap<ModelObject, ModelDto>();
});
AutoMapper.Mapper.AssertConfigurationIsValid();
}
public override void Map()
{
_target =AutoMapper.Mapper.Map<ModelObject, ModelDto>(_source);
}
}
EmitMapper映射代码:
[Mapper(Category = "Flattening.Class", Name = "EmitMapper")]
public class EmitMapperWrapper : MapperBase
{
ObjectsMapper<ModelObject, ModelDto> mapper;
protected override void OnInitialize()
{
mapper = ObjectMapperManager.DefaultInstance.GetMapper<ModelObject, ModelDto>(new FlatteringConfig());
}
protected override ModelDto MapImp()
{
return mapper.Map(Source);
}
}
EmitMapper映射器默认不支持Flatter 映射,如果支持需要写自定义配置:
class FlatteringConfig : DefaultMapConfig { protected Func<string, string, bool> nestedMembersMatcher; public FlatteringConfig() { nestedMembersMatcher = (m1, m2) => m1.StartsWith(m2); } public override IMappingOperation[] GetMappingOperations(Type from, Type to) { var destinationMembers = GetDestinationMemebers(to); var sourceMembers = GetSourceMemebers(from); var result = new List<IMappingOperation>(); foreach (var dest in destinationMembers) { var matchedChain = GetMatchedChain(dest.Name, sourceMembers).ToArray(); if (matchedChain == null || matchedChain.Length == 0) { continue; } result.Add( new ReadWriteSimple { Source = new MemberDescriptor(matchedChain), Destination = new MemberDescriptor(new[] { dest }) } ); } return result.ToArray(); } public DefaultMapConfig MatchNestedMembers(Func<string, string, bool> nestedMembersMatcher) { this.nestedMembersMatcher = nestedMembersMatcher; return this; } private List<MemberInfo> GetMatchedChain(string destName, List<MemberInfo> sourceMembers) { var matches = sourceMembers.Where(s => MatchMembers(destName, s.Name) || nestedMembersMatcher(destName, s.Name)); int len = 0; MemberInfo match = null; foreach (var m in matches) { if (m.Name.Length > len) { len = m.Name.Length; match = m; } } if (match == null) { return null; } var result = new List<MemberInfo> { match }; if (!MatchMembers(destName, match.Name)) { result.AddRange( GetMatchedChain(destName.Substring(match.Name.Length), GetDestinationMemebers(match)) ); } return result; } private static List<MemberInfo> GetSourceMemebers(Type t) { return GetMemebers(t) .Where( m => m.MemberType == MemberTypes.Field || m.MemberType == MemberTypes.Property || m.MemberType == MemberTypes.Method ) .ToList(); } private static List<MemberInfo> GetDestinationMemebers(MemberInfo mi) { Type t; if (mi.MemberType == MemberTypes.Field) { t = mi.DeclaringType.GetField(mi.Name).FieldType; } else { t = mi.DeclaringType.GetProperty(mi.Name).PropertyType; } return GetDestinationMemebers(t); } private static List<MemberInfo> GetDestinationMemebers(Type t) { return GetMemebers(t).Where(m => m.MemberType == MemberTypes.Field || m.MemberType == MemberTypes.Property).ToList(); } private static List<MemberInfo> GetMemebers(Type t) { BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public; return t.GetMembers(bindingFlags).ToList(); } }
NLiteMapper映射代码:
[Mapper(Category = "Flattening.Class", Name = "NLiteMapper")]
public class NLiteMaperWrapper : MapperBase
{
private NLite.Mapping.IMapper<ModelObject, ModelDto> mapper;
protected override void OnInitialize()
{
base.OnInitialize();
mapper = NLite.Mapper.CreateMapper<ModelObject, ModelDto>();
}
public override void Map()
{
_target = mapper.Map(_source);
}
}
Ok,完成代码用Release编译,然后再输出bin中找到exe文件,连续执行三次,下面是三次执行结果的截图:
------ Test started: Assembly: NLite.Test.dll ------ Flattening.Class->AutoMapper Time Elapsed: 1,112ms CPU Cycles: 6,718,750 Gen 0: 173 Gen 1: 1 Gen 2: 0 Flattening.Class->NLiteMapper Time Elapsed: 68ms CPU Cycles: 781,250 Gen 0: 4 Gen 1: 1 Gen 2: 0 Flattening.Class->EmitMapper Time Elapsed: 23ms CPU Cycles: 156,250 Gen 0: 3 Gen 1: 0 Gen 2: 0 Flattening.Class->Manual Time Elapsed: 8ms CPU Cycles: 0 Gen 0: 3 Gen 1: 1 Gen 2: 0
------ Test started: Assembly: NLite.Test.dll ------ Flattening.Class->AutoMapper Time Elapsed: 1,701ms CPU Cycles: 10,468,750 Gen 0: 173 Gen 1: 0 Gen 2: 0 Flattening.Class->NLiteMapper Time Elapsed: 69ms CPU Cycles: 781,250 Gen 0: 4 Gen 1: 1 Gen 2: 0 Flattening.Class->EmitMapper Time Elapsed: 22ms CPU Cycles: 0 Gen 0: 3 Gen 1: 0 Gen 2: 0 Flattening.Class->Manual Time Elapsed: 10ms CPU Cycles: 312,500 Gen 0: 3 Gen 1: 1 Gen 2: 0 1 passed, 0 failed, 0 skipped, took 2.98 seconds (NUnit 2.5.5).
------ Test started: Assembly: NLite.Test.dll ------ Flattening.Class->AutoMapper Time Elapsed: 1,205ms CPU Cycles: 10,156,250 Gen 0: 177 Gen 1: 0 Gen 2: 0 Flattening.Class->NLiteMapper Time Elapsed: 66ms CPU Cycles: 781,250 Gen 0: 4 Gen 1: 0 Gen 2: 0 Flattening.Class->EmitMapper Time Elapsed: 18ms CPU Cycles: 312,500 Gen 0: 3 Gen 1: 0 Gen 2: 0 Flattening.Class->Manual Time Elapsed: 9ms CPU Cycles: 0 Gen 0: 3 Gen 1: 0 Gen 2: 0 1 passed, 0 failed, 0 skipped, took 2.56 seconds (NUnit 2.5.5).
通过测试结果可以看出:
- 手工映射速度最快
- EmitMapper第二(大约比手工慢了2-6倍,)
- NLiteMapper第三(大约比EmitMapper慢了3倍)
- 最后是AutoMapper(大约比手工慢了200倍)
内存开销结果:
- 手工映射 Gen 0: 3
- EmitMapper Gen 0:3
- NLiteMapper Gen 0: 4
- AutoMapper Gen 0:173
总结 :无论从性能和内存EmitMapper都接近于手工,NLiteMapper次之,AutoMapper最后。NLiteMapper,EmitMapper,AutoMapper都是通过Emit的方式进行Get和Set的,为什么性能差别如此之大,设想如果NLiteMapper不进行优化的话(NLiteMapper一直是通过Emit方式进行的),那么NLiteMapper肯定是高高垫背的(NLiteMapper比EmitMapper慢了15000倍)。。。。。。
这次测试结果不代表整体结果,仅仅代表Class->Class(包括级联) 的映射性能,欢迎大家对这几种OO映射器进行性能比较。最后附上整个测试代码:测试代码。
备注:EmitMapper的测试代码修改过,添加了FlatteringConfig class 这样测试就公平了。
附上的源代码是老代码,最新代码:http://nlite.codeplex.com/SourceControl/changeset/view/76359#1528885