【Agent】MemOS 源码笔记---(4)---KV Cache
一、mysql参数的成本使用BenchmarkDotNet测试1. 测试代码如下CreateParameter直接构造参数Clone预先构造参数名和类型,复制后只设置参数值private static readonly MySqlCommand _command new();private static MySqlParameter _idParameter;[Benchmark(Baseline true)]public DbParameter CreateParameter(){var id _command.CreateParameter();id.ParameterName Id;id.DbType System.Data.DbType.Int64;id.Value 1L;return id;}[Benchmark]public DbParameter Clone(){var id _idParameter.Clone();id.Value 1L;return id;}[GlobalSetup]public void Setup(){_idParameter _command.CreateParameter();_idParameter.ParameterName Id;_idParameter.DbType System.Data.DbType.Int64;}2. 测试结果如下通过复制方式节省了80%的时间感觉有搞头,所以希望把复制参数的功能加入到DBShadow.net中用来提高性能Method Mean Error StdDev Ratio Gen0 Allocated Alloc RatioCreateParameter 58.60 ns 0.185 ns 0.213 ns 1.00 0.0064 112 B 1.00Clone 11.01 ns 0.387 ns 0.431 ns 0.19 0.0064 112 B 1.00二、Clone重写参数预编译1. 生成的代码如下预编译反射command和参数类型,以便生成更原生更快的代码其中cached是预先构造好的参数缓存作为常量cached含有参数名和类型信息Clone后只需要设置参数值即可(DbCommand command, Todo param) {MySqlParameterCollection parameters ((MySqlCommand)command).Parameters;MySqlParameter t cached.Clone();t.Value param.Id;// MySqlParameter Add(MySqlParameter parameter)return parameters.Add(t);}2. 选择更合适的原生方法MySqlParameterCollection有两个Add方法很明显重载Add(MySqlParameter parameter)更合适调用重载Add(object value)需要做多次类型转换既然生成的代码可以控制,就需要选用更合适的重载public MySqlParameter Add(MySqlParameter parameter);public override int Add(object value);3. 类型嗅探预编译是ShadowBuilder负责,基于ADO.net(DbCommand)为了生成更原生的代码,需要知道具体的Command类型所以ShadowBuilder需要更多实际的类型信息为此本来ShadowExecutor才需要的数据源信息,现在ShadowBuilder也需要3.1 ShadowBuilder以前的代码class ShadowBuilder(ISqlEngine engine, IMapperOptions options);3.2 ShadowBuilder现在的代码其中CommandBuilder可以通过DbDataSourcet推导出来也就是实际只增加了DbDataSourceclass ShadowBuilder(IMapperOptions options, ISqlEngine engine, DbDataSource dataSource, CommandBuilder commandBuilder);var command dataSource.CreateConnection().CreateCommand();var parameterType command.CreateParameter().GetType();CommandBuilder commandBuilder CommandBuilder.Create(command, parameterType);3.3 嗅探的过程通过DbDataSource推导出CommandBuilder其中CreateConnection、CreateCommand和CreateParameter等方法并不实际执行数据库IO操作,只是用来嗅探类型信息var command dataSource.CreateConnection().CreateCommand();var commandType command.GetType();var parametersProperty commandType.GetProperty(Parameters, BindingFlags.Instance | BindingFlags.DeclaredOnly);var parametersType parametersProperty.PropertyType;var parameterType command.CreateParameter().GetType();var addParameterMethod parametersType.GetMethod(Add, [parameterType]);4. ShadowExecutor变化比较小只是把类ShadowBuilder改为IShadowBuilder接口实际还是ShadowBuilder变化4.1 ShadowExecutor以前的代码class ShadowExecutor(ShadowBuilder builder, SqlSource source);4.2 ShadowExecutor现在的代码class ShadowExecutor(IShadowBuilder builder, SqlSource source);5. ShadowBuilder和ShadowCachedBuilder的关系5.1 ShadowCachedBuilder以前是ShadowBuilder的子类class ShadowCachedBuilder : ShadowBuilder;5.2 ShadowCachedBuilder现在是ShadowBuilder的包装类通过接口IShadowBuilder来实现通过original成员调用原有的ShadowBuilder功能这样避免ShadowBuilder的复杂度增加影响到ShadowCachedBuilderShadowCachedBuilder只负责缓存编译好的对象class ShadowCachedBuilder(ShadowBuilder original): IShadowBuilder;三、复杂的现实世界1. SqliteParameter不支持复制SqliteParameter没有实现ICloneable接口SqliteParameter也没有Clone方法2. SqlParameter可以复制,但性能不佳SqlParameter(Mssql)实现了ICloneable接口2.1 测试代码如下private readonly SqlCommand _command new();private ICloneable _idParameter;[Benchmark(Baseline true)]public DbParameter CreateParameter(){var id _command.CreateParameter();id.ParameterName Id;id.DbType System.Data.DbType.Int64;id.Value 1L;return id;}[Benchmark]public DbParameter Clone(){var id (SqlParameter)_idParameter.Clone();id.Value 1L;return id;}[GlobalSetup]public void Setup(){var idParameter _command.CreateParameter();idParameter.ParameterName Id;idParameter.DbType System.Data.DbType.Int64;_idParameter idParameter;}2.2 测试结果如下Clone方式比直接CreateParameter方式还慢这就是为什么CommandBuilder可以推导出来还要作为ShadowBuilder参数的原因现在只能把选择权交给用户,让用户决定是否启用Clone方式Method Mean Error StdDev Median Ratio RatioSD Gen0 Allocated Alloc RatioCreateParameter 15.38 ns 0.942 ns 0.967 ns 14.53 ns 1.00 0.09 0.0106 184 B 1.00Clone 24.84 ns 0.169 ns 0.194 ns 24.81 ns 1.62 0.10 0.0106 184 B 1.00四、ParameterBuilderParameterBuilder负责反射参数实际类型和方法,用于生成更高效的代码ParameterBuilder是抽象类,有3个具体实现1. BuildByNamedConstructor通过含参数名构造函数创建参数2. BuildByDefaultConstructor通过默认构造函数创建参数,然后设置参数名3. BuildByMethod通过command的CreateParameter方法创建参数,然后设置参数名4. ParameterBuilder的成员New方法用于创建参数实例SetParameterName方法用于设置参数的ParameterName属性SetDbType方法用于设置参数的DbType属性SetValue方法用于设置参数的Value属性GetParameterName方法用于生成集合参数的子参数名5. 集合参数大部分数据库并不支持集合参数因此需要生成多个单独的子参数来实际执行eg: IN Ids 需要转化为 IN (Ids0, Ids1, Ids2),Ids为集合参数集合参数很特殊,需要实际执行的时候才确定实际子参数的个数通过预编译可以生成遍历实参集合生成子参数的代码五、IParameterFactoryIParameterFactory就是参数处理的抽象ParameterFactory是默认实现CloneParameterFactory是Clone方式实现而IParameterFactory作为CommandBuilder的成员,处理参数部分的逻辑1. ParameterFactory默认的参数处理实现ParameterFactory包含ParameterBuilder,用于生成更高效的参数处理代码CloneParameterFactory也需要调用ParameterFactory的功能1.1 数据库类型处理可以通过重写CheckDbTypeCore方法来处理特殊的数据库类型映射需求////// 处理数据库类型(预留扩展处理特殊需求)//////protected virtual DbType CheckDbTypeCore(Type valueType) CheckDbType(valueType);////// 默认数据库类型/////////public static DbType CheckDbType(Type valueType){if (valueType.IsArray)return DbType.Binary;var typeCode Type.GetTypeCode(valueType);return typeCode switch{TypeCode.Byte DbType.Byte,TypeCode.SByte DbType.SByte,TypeCode.Int16 DbType.Int16,TypeCode.UInt16 DbType.UInt16,TypeCode.Int32 DbType.Int32,TypeCode.UInt32 DbType.UInt32,TypeCode.Int64 DbType.Int64,TypeCode.UInt64 DbType.UInt64,TypeCode.Single DbType.Single,TypeCode.Double DbType.Double,TypeCode.Decimal DbType.Decimal,TypeCode.Boolean DbType.Boolean,TypeCode.String DbType.String,TypeCode.Char DbType.StringFixedLength,TypeCode.DateTime DbType.DateTime,_ DbType.Object,};}1.2 CreateParameter方法实际CreateParameter专用于CloneParameterFactory用于创建参数原型,以便后续Clone使用////// 构造参数/////////DbParameter CreateParameter(Type valueType);////// 构造参数////////////DbParameter CreateParameter(string name, Type valueType);1.3 实现接口IParameterBuilder以下Create方法实际用于构造参数表达式其中_parameterBuilder就是ParameterBuilder实例,前面有介绍public Expression Create(IEmitBuilder builder, string name, Expression value) Create(builder, Expression.Constant(name), value);////// 构造参数///////////////public Expression Create(IEmitBuilder builder, Expression parameterName, Expression value){// var parameter command.CreateParameter();// parameter.ParameterName parameterName;var parameter _parameterBuilder.New(builder, parameterName);// parameter.DbType dbType;_parameterBuilder.SetDbType(builder, parameter, CheckDbTypeCore(value.Type));// parameter.Value value;_parameterBuilder.SetValue(builder, parameter, value);return parameter;}1.4 实现接口ICollectParameterBuilderICollectParameterBuilder用于处理集合参数的子参数public Expression CreateIndex(IEmitBuilder builder, Expression prefix, Expression index, Expression value) Create(builder, ParameterBuilder.GetParameterName(prefix, index), value);2. CloneParameterFactoryCloneParameterFactory由CloneParameterBuilder和CloneCollectParameterBuilder组成CloneParameterBuilder通过Clone方式创建参数CloneCollectParameterBuilder通过Clone方式创建集合参数3. CloneParameterBuilder3.1 CloneParameterBuilder包含ParameterBuilder、ParameterFactory和IEmitConverter成员ParameterBuilder用于处理参数值ParameterFactory用于生成参数原型,以便Clone使用IEmitConverter用于复制参数class CloneParameterBuilder(ParameterBuilder original, ParameterFactory factory, IEmitConverter cloneConverter);3.2 GetProtoType方法用于生成参数原型并缓存调用ParameterFactory的CreateParameter方法生成参数原型按参数名和类型缓存////// 获取原型缓存////////////public DbParameter GetProtoType(string name, Type valueType){var key new NameTypedCacheKey(name, valueType);if (_protoTypes.TryGetValue(key, out var cached))return cached;#if NET9_0_OR_GREATERlock (_lock)#elselock (_protoTypes)#endif{if (_protoTypes.TryGetValue(key, out cached))return cached;return _protoTypes[key] _factory.CreateParameter(name, valueType);}}3.3 Create方法Create用于构造参数表达式先调用GetProtoType方法获取参数原型该过程发生在预编译阶段,不会影响运行时性能通过缓存避免不同方法相同参数原型的重复创建通过cloneConverter生成Clone调用表达式最后调用ParameterBuilder设置参数值4. CloneCollectParameterBuilderCloneCollectParameterBuilder用于处理集合参数3.1 CloneCollectParameterBuilder也是包含ParameterBuilder、ParameterFactory和IEmitConverter成员ParameterBuilder用于处理参数值ParameterFactory用于生成参数原型,以便Clone使用IEmitConverter用于复制参数class CloneCollectParameterBuilder(ParameterBuilder original, ParameterFactory factory, IEmitConverter cloneConverter);3.2 GetProtoType方法用于生成集合参数原型并缓存调用ParameterFactory的CreateParameter方法生成参数原型按类型缓存CollectParameterPrototype(集合参数原型)用于实际处理集合参数的子参数////// 获取原型缓存/////////public CollectParameterPrototype GetProtoType(Type valueType){if (_protoTypes.TryGetValue(valueType, out var protoType))return protoType;#if NET9_0_OR_GREATERlock (_lock)#elselock (_protoTypes)#endif{if (_protoTypes.TryGetValue(valueType, out protoType))return protoType;var parameter _factory.CreateParameter(valueType);return _protoTypes[valueType] new(_original, parameter, _cloneConverter);}}4. CollectParameterPrototype集合参数原型4.1 包含ParameterBuilder、IEmitConverter和原型缓存class CollectParameterPrototype(ParameterBuilder original, ConstantExpression cached, IEmitConverter cloneConverter);4.2 CreateIndex方法CreateIndex用于构造集合参数的子参数通过cloneConverter生成Clone调用表达式最后调用ParameterBuilder设置参数名和参数值六、ToExecutor由于ShadowBuilder包含数据源可以很方便转化为ShadowExecutor增加ToExecutor方法简化操作ToExecutor是IShadowBuilder的方法,所以ShadowCachedBuilder也支持////// 转化为执行器/////////ShadowExecutor ToExecutor(int? commandTimeout null);七、总结通过嗅探实际类型生成更高效的参数处理代码不同数据库差异为此带来了复杂性部分数据库可以通过Clone方式创建参数提高性能1. 一般使用示例一般使用CreateCache方法创建ShadowBuilder如果是一次性使用无需缓存可以使用Create方法代替var engine new MySqlEngine();var dataSource new MySqlDataSource(ConnectionString);var builder ShadowBuilder.CreateCache(Mapper.Default, engine, dataSource);var select table.ToQuery().And(table.Id.Equal()).ToSelect().SelectSelfColumns();var compiled select.BuildQuery(builder);1.1 参数处理生成如下代码(DbCommand command, Todo param) {var parameters ((MySqlCommand)command).Parameters;var t new MySqlParameter();t.ParameterName Id;t.DbType DbType.Int64;t.Value param.Id;return parameters.Add(t);}2. 启用Clone方式示例启用Clone要复杂一点需要通过CloneParameter生成一个ParameterFactory然后传入ShadowBuilder的CreateCache方法中var engine new MySqlEngine();var dataSource new MySqlDataSource(ConnectionString);var parameterFactory ParameterFactory.CloneParameter(new MySqlCommand())var builder ShadowBuilder.CreateCache(Mapper.Default, engine, dataSource, parameterFactory);var select table.ToQuery().And(table.Id.Equal()).ToSelect().SelectSelfColumns();var compiled select.BuildQuery(builder);2.1 参数处理生成如下代码cached是预先构造好作为常量的参数缓存很明显如果Clone比new更快的话,这个代码会更高效Mysql下此方法耗时为原来的20%(也就是性能提高4倍)(DbCommand command, Todo param) {var parameters ((MySqlCommand)command).Parameters;var t cached.Clone();t.Value param.Id;return parameters.Add(t);}吮押交号

相关新闻

最新新闻

日新闻

周新闻

月新闻