由于使用Lambda表达式生成URL的方式性能较差,因此我使用Fluent Interface来代替原有的Lambda表达式构建方式。Fluent Interface主要对生成URL的前两个阶段(创建对象及分析对象)进行了优化,分别带来了超过2/3和1/2的性能优化,但因为最后一步,也就是使用Route对象的GetVirtualPath方法构造URL的性能没有提高,因此总体性能只提高了30%。于是我打算重新实现GetVirtualPath方法,希望得到更好的性能。
我的实现放在MvcPatch中MvcPatch.Routing项目里的FastRoute类。FastRoute的使用与ASP.NET Routing自带的Route比较相似,可以通过URL Pattern,Defaults及Constraints构造一个对象,而实际使用过程中可以通过MvcPatch.Extensions项目中定义的MapFast扩展方法进行注册。FastRoute将GetRouteData方法直接委托给内部的Route对象,但是重新实现了一个性能较高的GetVirtualPath方法。
说出来有些好笑,事实上我并没有完全理解GetVirtualPath的行为,我只是让GetVirtualPath方法通过了一些Test Case而已,这些Case的来源是我的“期望”以及微软官方,及我实际项目中所总结出来的使用方式。从目前看来,应该没有太大问题,最有可能出现问题的原因是我对Route对象本身的“设想”有所偏差,但它的确满足我目前的实际需求。
此外,在原版的Route对象中,Constraints集合支持两种约束方式,即“正则表达式”及“IRouteConstraint对象”。前者为开发人员提供的一个字符串,效果自不必说,而后者则是一个实现IRouteConstraint接口的对象。这个接口有一个Match方法,会将用于构造数据的RouteValueDictionary集合传入该方法,并返回一个布尔值表明是否满足该约束。它对性能的伤害之处需要完整复制一份的RouteValueDictionary(因为我们无法保证Match方法不会修改这个集合)。由于实际情况下我还从来没有真正使用过这个功能,因此将其抛弃。不过,如果真实现这个功能的话性能应该也不会差太多,因为假如开发人员不提供此类约束方式,我们不去复制该集合即可。
FastRoute的GetVirtualPath方法性能提升主要在三个方面:
- 验证及字符串拼接同时进行:在Route类中,每次Match方法都会先验证一遍传入的数据是否满足规则,如果不满足则直接返回null,然后再根据默认值和用户提供的值进行合并得到最终使用的集合,再进行拼接。这种方式较为“美观”但由于需要多次遍历,并构造并填充一些额外的RouteValueDictionary,因此虽然时间复杂度没有提高,但是影响性能的常数较大。由于项目中基本上都是通过名称直接找到RouteBase对象,因此不会失败,一边验证一边拼接的做法性能较高。
- 使用快速的索引方式:Route类中的索引使用了字典,而FastRoute使用了较为丑陋的方式,将URL Pattern拆分为多个Token,并使用多个数组保存Token的信息,例如当前Token是个常量还是个占位符。字典本身性能已经很高了,但是使用数组保存数据后,可以直接通过下标来获取Token的信息,一边读取,一边填充目标数组(见下一条)。
- 使用高效的字符串拼接方式:Route类使用了StringBuilder进行字符串拼接,而FastRoute使用了性能更高的String.Concat方法。由于URL Pattern的Token数量以及需要多少Query String都是在拼接前可以分析出来的,因此FastRoute在生成字符串之前便已经确定了整个字符串数组的长度。然后便是从后向前逆序填充数组内容,这是因为URL靠后的部分可能需要省略,这样便在数组的那个位置填入空字符串即可。
总之,FastRoute的性能优势在于精打细算每步操作,并根据数据特征选择相对高效的做法。不过目前的实现非常丑陋,我会继续进行一些调整和改进。使用FastRoute后,四种生成方式的耗时如下:
您可以将它和上次的结果进行对比,如果将Raw的性能视为100,则使用Route和FastRoute时其他3种方法的性能得分便是:
Route | FastRoute | |
---|---|---|
Route | 19.47167118 | 47.14237983 |
Lambda | 8.208318357 | 11.82049872 |
Fluent | 12.37249537 | 21.76159473 |
绘制成图表似乎更加一目了然:
可见,FastRoute让纯粹通过Route构造URL的做法性能提高了1倍多,而使用Lambda表达式和Fluent的做法也有较为明显的性能提升。值得一提的是,Fluent + FastRoute的性能已经超过了原有Route的做法,这意味着如果之前Route构造方式的性能能够令人接受的话,则目前Fluent方式的性能也已够用了。
相关文章
- 各种URL生成方式的性能对比
- 各种URL生成方式的性能对比(结论及分析)
- 为URL生成设计流畅接口(Fluent Interface)
- URL生成方式性能优化结果
- Route组件GetVirtualPath方法性能优化结果