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倍)

    内存开销结果:

  1. 手工映射              Gen 0: 3
  2. EmitMapper         Gen 0:3
  3. NLiteMapper        Gen 0: 4
  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

你可能感兴趣的:(mapper)