扯淡
當前市面上 ORM 很多,有跑車型的,如 Dapper,有中規中矩型的,如 Mybatis.Net,也有重量型的,如 EntityFramework 和 NHibernate,還有一些出自草根之手的,如 Chloe.ORM。各式各樣,層出不窮。試問,為何要重複造輪子?很簡單,我們來自火星,目前地球上還沒一款輪子適合我們這輛火星車~
為加深對各個 ORM 框架的瞭解,同時也想看看我們自己的框架效能如何,也可以讓對 Chloe 感興趣的同學有所瞭解,今兒,做個效能比較測試。測試物件為大家較熟悉的 EntityFramework 和有“效能之王”之稱的 Dapper,以及草根框架 Chloe.ORM。
基本介紹
- EntityFramework:EntityFramework 是微軟官方提供的 ORM,俗稱 EF,擁有堅不可摧的後臺,可謂無人不知,無人不嘵。其對 Linq 完美支援,功能豐富,但 EntityFramework Core 之前的版本,一直被業界人員貼上笨重、不可控、效能差的標籤,很多人 Hold 不住它。可見,EntityFramework 的伯樂不多啊!不知道 EntityFramework Core 變化了多少,期待! 本文測試使用的版本是 EntityFramework 6.1。
- Dapper:Dapper 的背景同樣不簡單,目前支撐國外知名網站 stackoverflow 的資料訪問層,其知名度也很高。在眾多 ORM 中,堪稱效能之王。作為一款微型 ORM,很受國內開發者的歡迎,畢竟經過大網站 stackoverflow 的考驗。很多自主開發的 ORM 做效能測試,都會選擇 Dapper 作為比較物件。Chloe.ORM 也不例外,哈哈!
- 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系統。
表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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, CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED ( [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 條資料。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
declare @i int = 0; begin tran; while(@i<=1000000) begin INSERT INTO [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萬條資料。測試程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
class MappingSpeedTest { static int takeCount = 50 * 10000; public static void GCMemoryTest() { /* * 記憶體分配測試通過 vs2013, 分析 --> 效能與診斷 --> 記憶體使用率 測試 * 每次執行程式只能呼叫下面中的一個方法,不能同時呼叫 */ ChloeQueryTest(takeCount); //ChloeSqlQueryTest(takeCount); //DapperQueryTest(takeCount); //EFLinqQueryTest(takeCount); //EFSqlQueryTest(takeCount); } public static void 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().Take(takeCount).ToList(); } } static void ChloeSqlQueryTest(int takeCount) { using (MsSqlContext context = new MsSqlContext(DbHelper.ConnectionString)) { var list = context.SqlQuery(string.Format("select top {0} * from TestEntity", takeCount.ToString())).ToList(); } } static void DapperQueryTest(int takeCount) { using (IDbConnection conn = DbHelper.CreateConnection()) { var list = conn.Query(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(string.Format("select top {0} * from TestEntity", takeCount.ToString())).ToList(); } } } |
執行效果圖:
為公平起見,所有測試都是在非 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
2.GC 次數:EFLinqQuery
2.查詢能力測試
查詢能力包括 sql 生成能力和對映能力,即一個完整的查詢,比較符合程式實際執行情況。本測試針對 ORM 效能評測,為減少資料庫執行 sql 耗時的干擾,我設計方案是,一次查詢只查一條資料,同時考驗下對 lambda 的解析能力,加了個 a.Id > id(0) 的 where 條件,執行多次查詢(本測試我選擇執行20000次查詢)。
測試 2.1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
/// /// 測試 ORM 的查詢能力。測試方法:一次查詢一條資料,迴圈執行多次查詢,計算所用時間和記憶體分配以及GC次數 /// 該類測試迴圈體內包括建立 DbContext 上下文 /// class LoopQueryTest { static int takeCount = 1; static int queryCount = 20000; public static void GCMemoryTest() { /* * 記憶體分配測試通過 vs 自帶診斷與分析工具測,vs --> 分析 --> 效能與診斷 --> 記憶體使用率。 * 每次執行程式只能呼叫下面中的一個方法,不能同時呼叫 */ ChloeQueryTest(takeCount, queryCount); //ChloeSqlQueryTest(takeCount, queryCount); //DapperQueryTest(takeCount, queryCount); //EFLinqQueryTest(takeCount, queryCount); //EFSqlQueryTest(takeCount, queryCount); } public static void 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 using (MsSqlContext context = new MsSqlContext(DbHelper.ConnectionString)) { int id = 0; var list = context.Query().Where(a => a.Id > id).Take(takeCount).ToList(); } } } static void ChloeSqlQueryTest(int takeCount, int loops) { for (int i = 0; i using (MsSqlContext context = new MsSqlContext(DbHelper.ConnectionString)) { int id = 0; var list = context.SqlQuery(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 using (IDbConnection conn = DbHelper.CreateConnection()) { int id = 0; var list = conn.Query(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 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 using (EFContext efContext = new EFContext()) { int id = 0; var list = efContext.Database.SqlQuery(string.Format("select top {0} * from TestEntity where Id>@Id", takeCount.ToString()), new SqlParameter("@Id", id)).ToList(); } } } } |
執行效果圖:
執行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 居然如此“差強人意”,好傷心。
不過仔細看測試2.1的測試程式碼,發現迴圈體內包含了建立 DbContext 的程式碼,我在想會不會是因為多次建立 DbContext 而導致 EF 如此慢?如果把建立 DbContext 的程式碼放到迴圈體外 EF 會不會更好點?於是就有了測試2.2。
測試2.2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
/// /// 測試 ORM 的查詢能力。測試方法:一次查詢一條資料,迴圈執行多次查詢,計算所用時間和記憶體分配以及GC次數 /// 該類測試迴圈體內不包括建立 DbContext 上下文 /// class LoopQueryTestWithNotCreateDbContext { static int takeCount = 1; static int queryCount = 20000; public static void GCMemoryTest() { /* * 記憶體分配測試通過 vs 自帶診斷與分析工具測,vs --> 分析 --> 效能與診斷 --> 記憶體使用率。 * 每次執行程式只能呼叫下面中的一個方法,不能同時呼叫 */ //ChloeQueryTest(takeCount, queryCount); //ChloeSqlQueryTest(takeCount, queryCount); //DapperQueryTest(takeCount, queryCount); //EFLinqQueryTest(takeCount, queryCount); EFSqlQueryTest(takeCount, queryCount); } public static void 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) { using (MsSqlContext context = new MsSqlContext(DbHelper.ConnectionString)) { for (int i = 0; i int id = 0; var list = context.Query().Where(a => a.Id > id).Take(takeCount).ToList(); } } } static void ChloeSqlQueryTest(int takeCount, int loops) { using (MsSqlContext context = new MsSqlContext(DbHelper.ConnectionString)) { for (int i = 0; i int id = 0; var list = context.SqlQuery(string.Format("select top {0} * from TestEntity where Id>@Id", takeCount.ToString()), DbParam.Create("@Id", id)).ToList(); } } } static void DapperQueryTest(int takeCount, int loops) { using (IDbConnection conn = DbHelper.CreateConnection()) { for (int i = 0; i int id = 0; var list = conn.Query(string.Format("select top {0} * from TestEntity where Id>@Id", takeCount.ToString()), new { Id = id }).ToList(); } } } static void EFLinqQueryTest(int takeCount, int loops) { using (EFContext efContext = new EFContext()) { for (int i = 0; i int id = 0; var list = efContext.TestEntity.AsNoTracking().Where(a => a.Id > id).Take(takeCount).ToList(); } } } static void EFSqlQueryTest(int takeCount, int loops) { using (EFContext efContext = new EFContext()) { for (int i = 0; i int id = 0; var list = efContext.Database.SqlQuery(string.Format("select top {0} * from TestEntity where Id>@Id", takeCount.ToString()), new SqlParameter("@Id", id)).ToList(); } } } } |
執行5次,得到以下結果:
ChloeQueryTest(ms) | ChloeSqlQueryTest(ms) | DapperQueryTest(ms) | EFLinqQueryTest(ms) | EFSqlQueryTest(ms) | |
第1輪 | 15281 | 11858 | 11981 | 31394 | 19309 |
第2輪 | 15194 | 12177 | 12314 | 31464 | 18161 |
第3輪 | 15967 | 12348 | 12366 | 31082 | 18030 |
第4輪 | 15371 | 11793 | 12479 | 32314 | 18356 |
第5輪 | 15350 | 11921 | 11937 | 35023 | 18356 |
平均 | 15411 | 12019 | 12215 | 32255 | 18442 |
GC 情況:
ChloeQueryTest | ChloeSqlQueryTest | DapperQueryTest | EFLinqQueryTest | EFSqlQueryTest | |
GC回收次數 | 111 | 41 | 47 | 368 | 205 |
看起來改後測試資料較測試2.1稍微提升了點,提升幅度最明顯的就是 EF,時間減少近 10 秒,GC 次數也減少了很多。如此看來,EF 建立和銷燬 DbContext 上下文也是個挺耗效能的環節。不測還真不知道。不過, EF 的測試結果仍然不盡人意,EFLinqQueryTest 耗時依然比同為物件化查詢的 ChloeQueryTest 多出一倍的時間,GC 次數是 ChloeQueryTest 的3倍還高。而且作為 EF 原生 sql 查詢的 EFSqlQueryTest 在耗時、GC次數上居然比 ChloeQueryTest 要差,有點說不過去~真想知道 EF 內部做了什麼不為人知的工作!
DapperQuery 和 ChloeSqlQuery 都是原生 sql 查詢,在測試2.1中 DapperQuery 稍微快點,但在測試2.2中 ChloeSqlQuery 實現了反超。仔細想想,其實也不難理解,Chloe 的 DbContext 建立會伴隨很多物件的建立,也耗不少資源,在測試2.2中,只建立了一個 DbContext 物件,隨之,各方面提升也是肯定的。ChloeQuery 跟前兩者壓根沒可比性,畢竟前兩者沒任何解析和生成 sql 過程,ChloeQuery 相對慢那是必然的。慢的那幾秒就是用於解析 lambda 和生成 sql 。魚和熊掌不可兼得,想要獲得開發方便,效能損耗在所難免!
EF…“名副其實”的慢,不說了,都是淚- -。跪求大神來給 EF 正名!
結論:
1.速度:取平均值,EFLinqQuery
2.GC 次數:EFLinqQuery
評測總結
- 對映能力:從 DataReader 向實體轉換過程,Dapper 和 Chloe 都是利用 Emit 動態生成委託的方式,我相信 EF 也是這樣。因此,在建立實體和屬性賦值環節3者都相當。但不同的是,Chloe 在讀 DataReader 資料的上做到了極致,所以在對映轉換效能上相對比 Dapper 和 EF 要高些。Dapper 和 EF 則差不多。其實,速度上3者都相當,主要差別就是在記憶體開銷上。
2.查詢能力:查詢能力是指框架執行一次完整的查詢所耗時多少與記憶體開銷大小。從測試2.1和測試2.2測試結果資料中我們可以看到,Dapper 和 Chloe 的原生 sql 查詢效能幾乎一樣,差距不大,Chloe 的物件化查詢較前兩者稍微差點,主要是生成 sql 過程比較消耗效能。EF “不負眾望”,以墊底的姿勢存在,無論是在速度還是GC次數上都比前兩者差一大截。EF 的對映能力其實不差(從對映能力測試資料中可以知道),查詢速度之所以慢,毫無疑問,問題出現在執行 sql 之前的環節。不過,EF 完美支援 Linq,豐富強大的功能著實讓眾多草根框架望塵莫及,希望 EntityFramework Core 版本效能有所大幅度提升。
關於 Chloe.ORM
我的開發原則是,只要在我的能力範圍內,不影響大局,要做就做到極致,這是一種追求。Chloe.ORM 還有部分地方可以優化,但優化後對效能提升也不會很大,最近忙,也懶得折騰了。目前框架整體架構、功能都已經穩定,現在只支援 SqlServer,接下來的發展目標是支援 MySql,對 Chloe 感興趣的同學可加入《.NET技術共享》群(群號:325936847)。為防止一些不相干人等混入群內,申請加群時需要您回答問題,只要你願意,即可進群!謝謝合作。
關於原始碼,目前很缺乏註釋與規範,一些類、方法以及變數的命名不是很好(英文,硬傷- -),給對原始碼感興趣的同學閱讀帶來很大的困擾,在此說聲抱歉。往後,我會適當的加上一些註釋。很多時候,除了一些極其關鍵點,我一般不會去寫註釋,特別是開發階段,程式碼變換太頻繁,維護程式碼的同時還要維護相應的註釋,太幸苦,我們吃不消。所謂好程式碼就是最好的註釋,像我這種懶人,不習慣註釋,如果程式碼也不好好寫的話,回過頭我自己讀自己的程式碼我估計都讀不懂。因此,不寫註釋,也形成了一種自我逼迫,促使我必須把程式碼寫得乾淨利落、優雅、易讀。如果有必要,往後有空我也可以出篇 Chloe.ORM 框架內部架構設計和實現介紹的文章。
結語
本次測試並不是想證明誰好誰壞,只是想通過對比去了解各個 ORM 之間在效能上的差異,以便我們更好的為專案做技術選型。一樣東西,存在必然有它存在的價值,專案開發,選擇合適的框架才是重要的。Dapper 堪稱效能之王,但它極度依賴手寫 sql,開發效率低,容錯率不高,如果一個專案不是高效能要求,選擇一個快速開發框架就好,短時間內完成專案才是主要的。我們要做的就是利益最大化。實際上,並非個個專案都是 stackoverflow!
倘若一個專案資料層用 Dapper 也好,用 EntityFramework 也罷,在同樣的伺服器上跑,都可以完美執行,使用者完全感覺不到差別,我們為何不選開發效率高的 EntityFramework 呢?就目前,我們公司裡部分專案,使用者群體不是面向大眾,我們就是用 EntityFramework,用它開發效率就是高,專案進展快,我們沒理由用其它框架,很簡單。
作為一名開發人員,很多時候真正的價值並不是你程式碼寫得多好,程式執行多快,而是如何能在同樣的時間內給使用者、公司、社會帶來最大的收益。
ps:所有的測試程式碼都已同步在 GitHub,地址:https://github.com/shuxinqin/Chloe/tree/master/src/DotNet/ChloePerformanceTest。