ORM性能测试Benchmark(最终版)

ORM性能测试Benchmark(最终版) ORM性能测试Benchmark最终版本测试聚焦 ORM 在查询过程中对查询表达式解析、数据映射、流程处理的性能差异。由于 SQL 的实际执行由数据库引擎负责ORM 无法改变数据库层面的执行逻辑不同 ORM 的差异主要体现在 SQL 拼接、表达式解析和数据映射等实现细节例如插入操作可通过生成 SQL 或使用 BulkCopy 实现。因此本测试不对实现方式完全不同的操作如 BulkCopy进行比较而是重点衡量表达式解析与数据映射两方面的运行效率与内存占用。测试声明本测试不代表任何立场和原作者也没任何关系仅是在研究、学习、优化、测试对内部项目myTest整理过程中形成的测试有其它测式可下载源码自行添加实现。测试环境项目说明测试框架BenchmarkDotNet测试数据库SQLite单机性能优波动较小.NET 版本.NET 6.0测试硬件Intel Core i5-8265U CPU 1.60GHz测试的ORM下面列出了近年收集到的ORM除Dapper外未涉及到表达式解析的ORM没有加入此测试PackageReference IncludeChloe.SQLite Version5.55.0 / PackageReference IncludeDapper Version2.1.66 / PackageReference IncludeFreeSql.Provider.Sqlite Version3.5.305 / PackageReference IncludeMicrosoft.EntityFrameworkCore.Sqlite Version8.0.0-preview.6.23329.4 / PackageReference IncludeSqlSugarCore Version5.1.4.212-preview02 / PackageReference Includelinq2db Version6.2.0 / Reference IncludeFast.Framework具体测试项目如下对表达式解析进行测试 testQueryCondition对查询返回结果进行数据映射测试 testQueryResult自定义数据映射测试 testQueryAnonymousResult循环读取指定数据测试 testQueryLoop测试代码明细对表达式解析进行测试 testQueryCondition指定了查询条件和返回结果筛选这里可以反映表达式解析生成SQL效率ORM技术核心最困难的部份非常考验代码组织和逻辑query.Where(b b.F_String 111 b.F_Decimal 0 b.F_Bool true b.F_String.StartsWith(abc)).Select(b new { b.F_Float, b.F_Bool, b.F_Double, b.F_Byte, b.F_String, b.F_Decimal, b.F_Int64 }).ToSqlString();对查询返回结果进行数据映射测试 testQueryResult返回指定的数量的数据进行数据映射以测试映射效率和内存使用情况query.Take(100).ToList();自定义数据映射测试 testQueryAnonymousResult指定数据映射的结构以测试解析和映射效率、内存使用情况这里使用了匿名对象是一个比较特殊的处理query.Take(100).Select(b new { b.Id, b.F_Float, b.F_Bool, b.F_DateTime, b.F_Decimal, b.F_Double, b.F_Int64 }).ToList();循环读取指定数据测试 testQueryLoop循环多次查询以测试查询和映射效率这里验证数据连接效率和查询效率虽然每次查询的数据量很小但循环多次会放大差异能更明显的看出差别for (var i 0; i 20; i) { var item query.Where(b b.Id i).ToList(); }Benchmark测试代码样例[MemoryDiagnoser] public class ConditionTest : TestBase { [Benchmark] public void TestCondition() { Invoke(b b.testQueryCondition()); } }以下是具体测试结果,仅供参考表格字段说明Mean: 所有测量值的算术平均值 ns纳秒 μs微秒 ms毫秒。Error: 99.9% 置信区间的一半。StdDev: 所有测量值的标准差。Gen0: 第 0 代 GC 每 1000 次操作收集一次。Gen1: 第 1 代 GC 每 1000 次操作收集一次。Gen2: 第 2 代 GC 每 1000 次操作收集一次。Allocated: 每次操作分配的内存仅托管内存包含所有内容1KB 1024B。TestConditionDapper由于没有表达式解析空跑最低和最高不管是效率和内存占用差了一数量级提示拼接SQL会导致注入风险除了语法字符外还有字符编码问题MethodProvideTypeMeanErrorStdDevGen0Gen1AllocatedTestConditionChloeTest90.75 μs1.020 μs0.954 μs5.3711-16.68 KBTestConditionEfSqlliteTest217.38 μs2.950 μs2.615 μs19.5313-61.19 KBTestConditionFastFrameworkTest94.90 μs0.807 μs0.630 μs6.7139-20.71 KBTestConditionFreeSqlTest779.56 μs12.514 μs11.706 μs9.76563.906362.56 KBTestConditionLinqToDbTest103.50 μs2.002 μs2.056 μs6.1035-18.79 KBTestConditionMyTest43.00 μs0.505 μs0.395 μs4.8218-14.95 KBTestConditionSqlSugarTest366.82 μs7.205 μs6.016 μs33.6914-103.83 KBTestResult强类型直接转换最低和最高差别两倍EF效率比大部份好MethodProvideTypeMeanErrorStdDevGen0Gen1AllocatedTestResultChloeTest582.8 μs10.02 μs8.37 μs22.4609-70.68 KBTestResultDapperTest500.8 μs3.14 μs2.94 μs16.6016-52.68 KBTestResultEfSqlliteTest1,127.4 μs15.77 μs14.75 μs64.4531-202.9 KBTestResultFastFrameworkTest11,561.9 μs119.70 μs93.46 μs46.8750-153.21 KBTestResultFreeSqlTest1,300.0 μs24.25 μs22.68 μs31.250011.718896.32 KBTestResultLinqToDbTest1,273.8 μs20.71 μs18.36 μs17.5781-54.77 KBTestResultMyTest600.5 μs11.94 μs11.16 μs15.6250-48.34 KBTestResultSqlSugarTest1,408.5 μs27.42 μs38.44 μs46.8750-147.57 KBTestAnonymousResultDapper由于没有结果筛选同TestResult(SqlSugar内存溢出)MethodProvideTypeMeanErrorStdDevGen0Gen1AllocatedTestAnonymousResultChloeTest552.7 μs5.48 μs4.86 μs20.5078-65.54 KBTestAnonymousResultDapperTest484.2 μs4.71 μs4.18 μs17.5781-55.8 KBTestAnonymousResultEfSqlliteTest692.2 μs11.48 μs10.74 μs33.2031-103.18 KBTestAnonymousResultFastFrameworkTest7,838.4 μs67.26 μs52.51 μs31.2500-122.4 KBTestAnonymousResultFreeSqlTest1,940.9 μs20.24 μs18.93 μs70.312511.7188225.84 KBTestAnonymousResultLinqToDbTest1,241.1 μs18.58 μs14.51 μs15.6250-53.21 KBTestAnonymousResultMyTest473.4 μs4.67 μs4.14 μs12.6953-39.89 KBTestAnonymousResultSqlSugarTest8,667.7 μs133.08 μs111.13 μs1046.8750-3210.65 KBTestQueryLoop循环多次查询调用由于调用sqlite驱动不同时间差别较大但从内存使用上能看出差别MethodProvideTypeMeanErrorStdDevGen0Gen1AllocatedTestQueryLoopChloeTest2.535 ms0.0427 ms0.0379 ms74.2188-231.78 KBTestQueryLoopDapperTest1.433 ms0.0274 ms0.0243 ms17.5781-54.7 KBTestQueryLoopEfSqlliteTest5.410 ms0.0697 ms0.0618 ms375.0000-1156.31 KBTestQueryLoopFastFrameworkTest230.424 ms4.5239 ms4.0103 ms666.6667-2332.27 KBTestQueryLoopFreeSqlTest16.176 ms0.3208 ms0.6257 ms93.750031.2500664.93 KBTestQueryLoopLinqToDbTest16.881 ms0.2541 ms0.2252 ms125.0000-406.34 KBTestQueryLoopMyTest2.058 ms0.0405 ms0.0379 ms46.8750-148.13 KBTestQueryLoopSqlSugarTest4.210 ms0.0739 ms0.0655 ms210.9375-651.17 KB引入不同数据库实现的方式本次测试使用的sqlite数据库各种引入的方式也不相司大致分为三类引用相关数据库的项目扩展包(ORM主体扩展数据库驱动)直接包含所有支持的数据库驱动(ORM主体数据库驱动*N)按需自动或手动配置引入的数据库驱动(ORM主体按需数据库驱动)对于第一种多数项目都采用这种方式缺点是需要引入多个包增加了依赖关系并且强绑定了数据库驱动对于第二种虽然可以支持多种数据库但会增加项目的体积并且可能引入不必要的依赖也强绑定了数据库驱动对于第三种封装度最高虽然可以按需引入数据库驱动减少了项目的体积和依赖复杂度但由于没有强依赖部份实现可能由于驱动不一致而实现复杂如 BulkCopy以LinqToDb为例直接引入LinqToDb包手动配置连接串 new DataOptions().UseConnectionString(ProviderName.SQLite, ITest.sqlLiteDb) 它就能自别识别当前项目引入的sqlite驱动并且对特殊方法也进行了封装BulkCopy不管是哪种数据库引入驱动后所有方法行为一至无其它依赖。各种测试项目的引入方式如下表所示ProvideType引入方式ChloeTest扩展包手动配置DapperTest扩展包EfSqlliteTest扩展包FastFrameworkTest手动配置FreeSqlTest扩展包MyTest手动或自动配置SqlSugarTest全包含LinqToDbTest自动配置如何使用此测试使用Release发布此项目运行dbTest.exe输入序号选择需要运行测试的方法,示例如下---------------------[Program]---------------------[1]testQueryResult[2]testQueryAnonymousResult[3]testQueryCondition[4]testQueryLoop[5]testMethod---------------------[invokeAll]---------------------[6]invokeAllinvokemethod:输入序号后回车等待测试完成测试结果会输出到控制台并在运行目录生成BenchmarkDotNet.Artifacts文件夹里面有详细的测试结果和分析报告运行效果截图测试项目代码https://gitee.com/hubro/dbTest.git参考使用 BenchmarkDotNet 对 .NET 代码进行性能基准测试https://cloud.tencent.com/developer/article/2483382mysql注入-字符编码技巧https://developer.aliyun.com/article/1658273