EmitMapper,AutoMapper,NLiteMapper和手工映射性能大比拼
在大比拼之前先講一個小插曲,我這個人以前比較低調,做了很多好東西僅僅在公司內的朋友圈項目圈內分享,很少在博客園內進行分享,后來在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

浙公網安備 33010602011771號