当前市面上 ORM 很多,有跑车型的,如 Dapper,有中规中矩型的,如 Mybatis.Net,也有重量型的,如 EntityFramework 和 NHibernate,还有一些出自草根之手的,如 Chloe.ORM。各式各样,层出不穷。试问,为何要重复造轮子?很简单,咱来自火星,目前地球上还没一款轮子适合咱这辆火星车~
为加深对各个 ORM 框架的了解,同时也想看看咱自己的框架性能如何,也可以让对 Chloe 感兴趣的同学有所了解,今儿,做个性能比较测试。测试对象为大家较熟悉的 EntityFramework 和有“性能之王”之称的 Dapper,以及草根框架 Chloe.ORM。
导航本次只对各 ORM 查询效率做评测。ORM 的性能损耗主要在 DataReader 向实体转换和生成 sql 语句这两过程,因此,本次测试的内容就考察以下两方面:
1.映射能力。
2.查询能力(由于无法仅测试 sql 生成阶段的性能,所以这点测试包括 sql 生成和映射能力),即一个完整的查询。
测试指标1.速度。即执行相同的查询所用时 。
2.GC 回收次数。即执行相同的查询 GC 执行回收次数。GC 次数越多说明程序运行内存开销越大。本指标测试通过 vs2013 自带的性能分析工具,它可以自动帮我们分析统计程序运行分配的内存以及 GC 回收次数,不懂的同学可以去了解下。打开 vs,分析 --> 性能与诊断 --> 内存使用率。
机器:戴尔 xps13 笔记本,CPU 为 i7-4510U,内存8G,win 10系统。
表:
CREATE TABLE [dbo].[TestEntity]( [Id] [int] IDENTITY(1,1) NOT NULL, [F_Byte] [tinyint] NULL, [F_Int16] [smallint] NULL, [F_Int32] [int] NULL, [F_Int64] [bigint] NULL, [F_Double] [float] NULL, [F_Float] [real] NULL, [F_Decimal] [decimal](18, 0) NULL, [F_Bool] [bit] NULL, [F_DateTime] [datetime] NULL, [F_Guid] [uniqueidentifier] NULL, [F_String] [nvarchar](100) NULL, ( [Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY]
数据准备:
向 TestEntity 表插入 1000000 条数据。
declare @i int = 0; begin tran; while(@i<=1000000) [dbo].[TestEntity] ([F_Byte] ,[F_Int16] ,[F_Int32] ,[F_Int64] ,[F_Double] ,[F_Float] ,[F_Decimal] ,[F_Bool] ,[F_DateTime] ,[F_Guid] ,[F_String]) VALUES (1 ,2 ,@i ,@i ,@i ,@i ,@i ,@i%2 ,GETDATE() ,NEWID() ,'Chloe' + CAST(@i AS nvarchar(1000)) ) set @i=@i+1; end commit;
测试方案 1.映射能力测试映射能力是指 DataReader 向实体转换,这过程有很多方式,主流的就是反射和 Emit 动态生成委托两种方式。反射的性能相对比较差,据了解,早期的一批 ORM 大部分是用反射,后来大家意识到反射性能问题,基本都转 Emit 或用其它方式代替了。为减少程序其它方面对测试结果的影响,我设计的方案是一次查询 N 条数据,并且不加任何 where 条件,这样就可以提高映射性能损耗在整个测试中占比,减少其它方面(数据库执行查询、sql 生成等)对测试结果在整个测试中性能影响占比,总之,N 值越大,额外因素对测试结果影响越小,数据更真实。本次测试,我选择一次查50万条数据。测试代码:
class MappingSpeedTest { static int takeCount = 50 * 10000; GCMemoryTest() { /* * 内存分配测试通过 vs2013, 分析 --> 性能与诊断 --> 内存使用率 测试 * 每次运行程序只能调用下面中的一个方法,不能同时调用 */ ChloeQueryTest(takeCount); } SpeedTest() { long useTime = 0; //预热 ChloeQueryTest(1); useTime = SW.Do(() => { ChloeQueryTest(takeCount); }); Console.WriteLine("ChloeQueryTest 一次查询{0}条数据总用时:{1}ms", takeCount, useTime); GC.Collect(); useTime = SW.Do(() => { ChloeSqlQueryTest(takeCount); }); Console.WriteLine("ChloeSqlQueryTest 一次查询{0}条数据总用时:{1}ms", takeCount, useTime); GC.Collect(); //预热 DapperQueryTest(1); useTime = SW.Do(() => { DapperQueryTest(takeCount); }); Console.WriteLine("DapperQueryTest 一次查询{0}条数据总用时:{1}ms", takeCount, useTime); GC.Collect(); //预热 EFLinqQueryTest(1); useTime = SW.Do(() => { EFLinqQueryTest(takeCount); }); Console.WriteLine("EFLinqQueryTest 一次查询{0}条数据总用时:{1}ms", takeCount, useTime); GC.Collect(); //预热 EFSqlQueryTest(1); useTime = SW.Do(() => { EFSqlQueryTest(takeCount); }); Console.WriteLine("EFSqlQueryTest 一次查询{0}条数据总用时:{1}ms", takeCount, useTime); GC.Collect(); Console.WriteLine("GAME OVER"); Console.ReadKey(); } static void ChloeQueryTest(int takeCount) { using (MsSqlContext context = new MsSqlContext(DbHelper.ConnectionString)) { var list = context.Query<TestEntity>().Take(takeCount).ToList(); } } static void ChloeSqlQueryTest(int takeCount) { using (MsSqlContext context = new MsSqlContext(DbHelper.ConnectionString)) { var list = context.SqlQuery<TestEntity>(string.Format("select top {0} * from TestEntity", takeCount.ToString())).ToList(); } } static void DapperQueryTest(int takeCount) { using (IDbConnection conn = DbHelper.CreateConnection()) { var list = conn.Query<TestEntity>(string.Format("select top {0} * from TestEntity", takeCount.ToString())).ToList(); } } static void EFLinqQueryTest(int takeCount) { using (EFContext efContext = new EFContext()) { var list = efContext.TestEntity.AsNoTracking().Take(takeCount).ToList(); } } static void EFSqlQueryTest(int takeCount) { using (EFContext efContext = new EFContext()) { var list = efContext.Database.SqlQuery<TestEntity>(string.Format("select top {0} * from TestEntity", takeCount.ToString())).ToList(); } } }
View Code运行效果图: