为公平起见,所有测试都是在非 Debug 下运行,且都经过预热。总共测试5轮,下面是测试结果数据:
ChloeQueryTest(ms)
ChloeSqlQueryTest(ms)
DapperQueryTest(ms)
EFLinqQueryTest(ms)
EFSqlQueryTest(ms)
第1轮
6976
7170
7948
7704
7744
第2轮
7357
6853
8410
8328
7783
第3轮
7610
7833
8107
9795
8706
第4轮
7296
6957
7760
8643
7873
第5轮
9636
6705
8805
8946
8544
平均
7775
7103
8206
8683
8130
上表是单纯测试用时,下面是一次查询50万条数据,GC次数情况。
运行效果图:
GC统计结果,由于相同的代码运行,内存分配和GC情况都一样,所以这个测试不必运行多轮:
ChloeQueryTest
ChloeSqlQueryTest
DapperQueryTest
EFLinqQueryTest
EFSqlQueryTest
GC回收次数
22
22
38
40
35
可见,Chloe.ORM 在各方面略优,看到这个结果,估计大家觉得不可能。毕竟部分国人喜欢扬眉崇外,国外的月亮比国内圆。开始看到这个结果我也觉得有点不可思议,后来翻看 Dapper 源码,发现 Chloe 稍微快一点是出乎意外,但也在情理之中。EF 映射方式我没去了解,Chloe 和 Dapper 两者创建实体以及属性赋值都是用 Emit 生成委托,这方面性能可以说毫无差距。但不同的是,Chloe 从 DataReader 读数据是调用强类型方法(GetInt(int i)、GetDateTime(int i)、GetString(int i)...等),所以对于值类型数据的读取,避免了装箱和拆箱,从而减少了很多垃圾对象,随之 GC 次数减少。Dapper 则不然,它从 DataReader 读取数据是使用 DataReader[int i],其内部实现就是调用 DataReader.GetValue(int i),如果是值类型的数据,会引起大量的装箱和拆箱,需要 CPU 大量计算的同时还会产生很多的垃圾对象,随之 GC 次数增加。我想这就是 Chloe 在映射方面略胜一筹的原因。可见,Chloe 在映射方面已经做到极致。
其实,三者在映射能力方面差距不大。为了能看出性能差异,我们一次查询了大量的数据,这仅仅是为了测试效果,在实际生产中是不会有这种情况。
结论:
1.速度:取平均值,EFLinqQuery < DapperQuery ≈ EFSqlQuery < ChloeQuery < ChloeSqlQuery
2.GC 次数:EFLinqQuery < DapperQuery < EFSqlQuery< ChloeQuery = ChloeSqlQuery
2.查询能力测试查询能力包括 sql 生成能力和映射能力,即一个完整的查询,比较符合程序实际运行情况。本测试针对 ORM 性能评测,为减少数据库执行 sql 耗时的干扰,我设计方案是,一次查询只查一条数据,同时考验下对 lambda 的解析能力,加了个 a.Id > id(0) 的 where 条件,执行多次查询(本测试我选择执行20000次查询)。
测试 2.1:
LoopQueryTest { static int takeCount = 1; static int queryCount = 20000; GCMemoryTest() { /* * 内存分配测试通过 vs 自带诊断与分析工具测,vs --> 分析 --> 性能与诊断 --> 内存使用率。 * 每次运行程序只能调用下面中的一个方法,不能同时调用 */ ChloeQueryTest(takeCount, queryCount); } SpeedTest() { long useTime = 0; //预热 ChloeQueryTest(1, 1); useTime = SW.Do(() => { ChloeQueryTest(takeCount, queryCount); }); Console.WriteLine("ChloeQueryTest 执行{0}次查询总用时:{1}ms", queryCount, useTime); GC.Collect(); useTime = SW.Do(() => { ChloeSqlQueryTest(takeCount, queryCount); }); Console.WriteLine("ChloeSqlQueryTest 执行{0}次查询总用时:{1}ms", queryCount, useTime); GC.Collect(); //预热 DapperQueryTest(1, 1); useTime = SW.Do(() => { DapperQueryTest(takeCount, queryCount); }); Console.WriteLine("DapperQueryTest 执行{0}次查询总用时:{1}ms", queryCount, useTime); GC.Collect(); //预热 EFLinqQueryTest(1, 1); useTime = SW.Do(() => { EFLinqQueryTest(takeCount, queryCount); }); Console.WriteLine("EFLinqQueryTest 执行{0}次查询总用时:{1}ms", queryCount, useTime); GC.Collect(); //预热 EFSqlQueryTest(1, 1); useTime = SW.Do(() => { EFSqlQueryTest(takeCount, queryCount); }); Console.WriteLine("EFSqlQueryTest 执行{0}次查询总用时:{1}ms", queryCount, useTime); GC.Collect(); Console.WriteLine("GAME OVER"); Console.ReadKey(); } static void ChloeQueryTest(int takeCount, int loops) { for (int i = 0; i < loops; i++) { using (MsSqlContext context = new MsSqlContext(DbHelper.ConnectionString)) { int id = 0; var list = context.Query<TestEntity>().Where(a => a.Id > id).Take(takeCount).ToList(); } } } static void ChloeSqlQueryTest(int takeCount, int loops) { for (int i = 0; i < loops; i++) { using (MsSqlContext context = new MsSqlContext(DbHelper.ConnectionString)) { int id = 0; var list = context.SqlQuery<TestEntity>(string.Format("select top {0} * from TestEntity where Id>@Id", takeCount.ToString()), DbParam.Create("@Id", id)).ToList(); } } } static void DapperQueryTest(int takeCount, int loops) { for (int i = 0; i < loops; i++) { using (IDbConnection conn = DbHelper.CreateConnection()) { int id = 0; var list = conn.Query<TestEntity>(string.Format("select top {0} * from TestEntity where Id>@Id", takeCount.ToString()), new { Id = id }).ToList(); } } } static void EFLinqQueryTest(int takeCount, int loops) { for (int i = 0; i < loops; i++) { using (EFContext efContext = new EFContext()) { int id = 0; var list = efContext.TestEntity.AsNoTracking().Where(a => a.Id > id).Take(takeCount).ToList(); } } } static void EFSqlQueryTest(int takeCount, int loops) { for (int i = 0; i < loops; i++) { using (EFContext efContext = new EFContext()) { int id = 0; var list = efContext.Database.SqlQuery<TestEntity>(string.Format("select top {0} * from TestEntity where Id>@Id", takeCount.ToString()), new SqlParameter("@Id", id)).ToList(); } } } }
View Code运行效果图:
运行5次,下面是用时结果:
ChloeQueryTest(ms)
ChloeSqlQueryTest(ms)
DapperQueryTest(ms)
EFLinqQueryTest(ms)
EFSqlQueryTest(ms)
第1轮
15083
12594
13134
41163
24339
第2轮
15597
12711
12133
40294
25281
第3轮
15356
11885
11587
44913
25707
第4轮
16419
13089
12803
46196
25635
第5轮
16216
12463
12221
40064
23749
平均
15734
12548
12375
42526
24942
再来看看GC情况:
ChloeQueryTest
ChloeSqlQueryTest
DapperQueryTest
EFLinqQueryTest
EFSqlQueryTest
GC回收次数
116
47
49
538
359
不测不知道,一测吓一跳。看到这个结果,我吃了那啥好几斤,不知道是我代码有问题还是怎么回事,EF 居然如此“差强人意”,好伤心。