Entity Framework Core 6.0 预览4 性能改进

2023-05-16

起因

微软在Build2021开发者大会上,发布Entity Framework Core 6.0(简称EFCore 6)预览第四版,号称是性能版本,性能提升主要对于Entity Framework Core 5.

性能改进:

  1. EFCore 5.0和EFCore 6.0基准测试,提升了70%.

  2. 在查询时,比EFCore5.0提升了31%.

  3. 内存改进了不少,减少43%.减少内存分配,就是减少GC回收的次数,降低GC回收的压力.

运行时性能

在以往的几年,EF Core团队主要以功能和稳定为中心,缩小与EF(Entity Framework)的功能差距.当EF Core团队在给EF Core 6.0规划时以性能(高性能和低延迟)为中心.EF Core 6.0以Dapper(这里不给Dapper过多的介绍)为目标.

在发布EF Core 6.0 pre 4的时候,EF Core 6与Dapper在TechEmpower基准测试中的差距由原来的55%,缩小到5% .

当然在TechEmpower基准测试是特定的环境(有针对性的优化,比如加大DbContext池的默认大小改为1024),基准测试是在高性能和低延迟的环境下执行的,EF Core也关闭了跟踪查询(关闭后,无法对实体进行修改),这些和线上生产环境不同.因为EF Core在执行查询时(在线上生产环境)的性能瓶颈,主要是在网络和数据库的IO上.

DbContext创建和回收

使用EF Core的人都知道DbContext是操作数据的入口,通常直接实例化DbContext,或者通过依赖注入的方式获得DbContext的实例,使用它进行一些数据库操作(工作单元),使用结束后进行释放.

通常直接实例化DbContext是一个很好的方式.在有些环境需要和容器(依赖注入)集成到一起,又需要高性能的时候,协调DbContext和容器一起工作是需要时间的.所以在EF Core团队加入DbContextPool,对DbContext进行池化管理.DbContext在使用完毕后放入到DbContextPool允许被重新使用.

通过DbContextPool(DbContext池)减少DbContext创建和回收,减少DbContext的创建,降低堆内存分配,也就减少GC回收的次数.减少GC的压力.

在EF Core 6.0调整池中的实例数量,从原来128个改为1024个.对于大多数程序128个DbContext实例是足够使用的.但在TechEmpower 测试是有些不太够,将实例数量改为1024,TechEmpower 基准吞吐率增加了23%.

DbContext不是万能的,EF Core在查询时是要使用Ado.Net对象的,如DbConnection/DbCommand及DbDataReader,以及使用这些对象中的内部对象,但这些对象在内存使用比较高时,就需要对这些对象进行回收,这些对象回收是有序的.因此,每个DbContext都有专属的自己实例(指DbConnection等),重用的时候也是使用这些对象.这种可重用的对象图以DbContext为根节点延伸到Npgsql(PostgreSQL),这次优化在执行查询时内存分配减少了23%.

日志调整

EF Core有很多可以扩展的地方,允许用户获得关于查询执行的各个阶段的信息,并将其挂钩到查询执行的各个阶段,比如说要对数据库执行Sql查询时,EF Core调用DbCommand.ExcuteReaderAsync(可以记录Sql执行前和运行的时间),也可以写一个DiagnosticSource事件,调用用户配置的拦截器,可以让用户操作之前的命令,EF Core虽然提供了一组灵活切强大的扩展,但这些扩展的开销并不低.一次查询有7个事件,每个事件都有2个扩展(执行前和执行后).一直在检查是否启用日志和注册DiagnosticListener 事件,这些开销都可以在性能分析中体现出.

在日志这一块改进,是检查是否开启任何类型的日志或者拦截器,如果没有启用,默认就对该事件和日志禁用1秒.禁用后,基准吞吐率提升了7%,这为使用EF Core的用户,带来了很好的收益,如果在程序的某一时间注册DiagnosticListener,那么只需要等待一秒钟的时间,就可以看到结果.

线程安全检查

在EF Core中DbContext并不是线程安全的,DbContext内部封装了数据库连接对象,数据库连接对象本身也不允许并发使用.所以我们对DbContext的实例对象不应该在有并发的时候使用,但在EF Core内部有一个线程安全检测机制,但检测到有跨线程使用,会抛出异常,这样方便解决DbContext跨线程使用带来的问题.

EF Core在查询时线程安全机制也是会进行检测的,在异步查询的时候,会使用AsyncLocal将状态加锁并传递给查询线程,AsyncLocal会在堆上进行比较多的内存分配,有太多的堆内存分配,导致GC也会进行频繁回收,最终导致EF Core的吞吐率下降.

EF Core不知道是什么时候检测是否线程安全,也不知道什么时候不需要进行线程安全,在EF Core 6.0加入了线程安全检查机制的关闭参数,通过DbContextOptionsBuilder的EnableThreadSafetyChecks方法进行设置.本想在测试项目测试一下呢?发现在EF Core 6.0 preview 4的具体数据库的实现依赖.都还是EF Core 5.0的.只有Npgsql.EntityFrameworkCore.PostgreSQL内部依赖有EF Core 6.0的,SqlServer和MySQL暂时没有更新.

qqio


在关闭线程安全检测后,在TechEmpower 性能基准测试提升了6.7%.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Entity Framework Core 6.0 预览4 性能改进 的相关文章

随机推荐