作者|金胜杰(司旭)一、背景1.1业务背景支付宝卡包存放用户的会员卡和优惠券。优惠券单元格和优惠券详细信息均通过静态模板配置和动态可变数据呈现给最终用户。下面【图1】优惠券数据在C端用户展示形式,【图2】展示C端数据组装流程。【图1】C端优惠券数据展示形式【图2]C端数据组装流程以[图2]为例,模板中有两个变量availableAmount和voucherName,这两个变量在动态变量Data中有对应的值。将模板中对应的两个变量替换为动态值,最后拼装成“100元红包名”。当这个红包使用一次,消费满30元时,动态数据中availableAmount的值会变成70,当用户再次进入红包详情页面时,重新组装后显示数据会变成“70元红包名称”.1.2问题发现最近在做项目的过程中,仔细梳理了优惠券的组装和渲染逻辑,仔细研究了【图3】中的模板变量替换逻辑。这是一段从卡包产品诞生之日起就存在的老代码,差不多十年前了。它的作用是用动态数据替换模板中的变量。乍一看,这段代码的逻辑没有问题。就是用动态数据替换模板中两个$之间(included)之间的变量。考虑到这是一个极其核心和高频的调用逻辑,看看有没有性能优化的空间。【图3】模板变量替换代码实现理清了替换逻辑后,第一感觉就是这段代码还有性能提升的空间。主要有两点:每个while循环进行两次indexOf操作,每个while循环进行子串操作。因此,有以下两个问题:indexOf和substring操作是否可以减少?您真的必须每次都进行模板变量查找吗?2.性能优化带着以上两个问题,一步步进行性能优化和测试。整个优化过程迭代了5个版本,最终实现了10倍以上的性能提升。下面分别介绍不同版本的实现和性能对比。2.1性能优化V1该版本去掉了indexOf和substring操作,使用了另一种替换方式。之前的替换逻辑是从头到尾循环模板内容字符串,替换$之间的变量,需要常量indexOf和substring操作。新的实现方式是在进行变量替换之前,通过循环模板内容字符串,使用双指针提取模板中的所有变量,然后循环遍历变量集依次替换模板内容中的变量。【图4】性能优化V1代码实现2.2性能优化V2静态模板配置一般情况下不会发生变化。也就是说,同一个模板对应的变量都是固定的。模板id和模板变量集可以一对一缓存,减少每次替换前的变量抽取。在决定使用缓存之前,先想好如何实现缓存。有两点需要注意:使用本地缓存而不是TBase,以减轻大流量场景下TBase的压力。控制本地缓存的有效数量,以有限的内存使用率最大化缓存效率。可以使用GoogleGuava库的缓存类来实现缓存逻辑,参见【图5】【图5】缓存实现示例代码【图6】性能优化V2代码实现2.3性能对比(1)完成以上内容后两步,进行了性能测试,性能对比如图7所示。【图7】V1和V2版本性能对比通过性能对比发现,V1版本相比原版有性能提升,带缓存的V2版本相比无缓存的V1版本也有性能提升.但随着流量的增加,性能优化效果逐渐减弱。说明V1和V2版本的耗时优化点在模板变量替换耗时中所占比例并不高。也说明了整个模板变量替换逻辑中还有其他比较耗时的地方。仔细回头看变量替换逻辑,突然发现少了一个“大问题”。这个就是String.replace方法,它有两个耗时点:每次替换replace都会进行模板编译,并创建一个新的对象返回,每次replace后都要重新赋值变量。【图8】String.replace代码实现2.4性能优化V3在V2版本的基础上,去掉了replace方法,用StringBuilder实现。【图9】实现过程中有一点需要注意性能优化V3代码中的StringBuilder。在V2版本中,提取变量返回一个Set集合。变量在返回集合中出现的顺序将与模板中变量的顺序不一致。如果模板中有多个相同的变量,则只会替换出现的第一个变量。因此,需要将变量抽取返回的结果替换为有序、可重复的List,以保证逻辑的正确性。2.5性能优化V4V3版本优化后,性能有明显提升,证明String.replace方法是整个模板变量替换逻辑中最耗时的点。所以原方法中只用StringBuilder替换String.replace,得到V4版本。【图10】性能优化V4代码实现2.6性能对比(二)【图11】V1、V2、V3、V4版本通过【图11】可以清楚的发现,StringBuilder实现后,性能提升了10倍以上,效果非常明显。V4版本的耗时其实比带缓存的V3版本要少,也就是说V3版本中先抽取变量再组装StringBuilder的过程相对来说更耗时。但是V4版本的代码可读性不如V3版本。您可以组合V3和V4版本以消除缓存依赖性并生成具有最佳代码可读性和性能的V5版本。2.7性能优化V5首先抽取变量,去除缓存依赖,将String.replace替换为StringBuilder,增加代码可读性。【图12】V5版本代码实现&100万次循环耗时对比三、总结通过性能优化以上5个版本,性能提升了10倍以上。性能从高到低的顺序为V4>V3>V5>V2>V1>未优化原版。其中,V3、V4、V5版本的性能明显优于V1、V2版本。证明这个模板替换逻辑最耗时的点是String.replace。V3>V5和V2>V1说明缓存的引入还是有一定的性能提升的。有帮助。在代码可读性方面,V4不如V3和V5。整个优化主要有两点:1.String.replace方法涉及到模板编译和新字符串的生成,比较耗资源。2.StringBuilder替换String.replace。除了缩短通话时间,它还可以在空间方面减少资源。占据。因为StringBuilder.append相对于String.replace,可以减少中间大量String对象的创建和销毁,可以减轻GC的压力,从而减轻CPU的负载。性能优化的明显好处是能够节省机器资源。如果2000台服务器的应用,整体性能提升10%,理论上相当于节省了200台机器。除了节省机器资源外,与性能较差的应用程序相比,性能较好的应用程序在应对流量激增时更不容易达到机器的性能瓶颈。在相同流量场景下扩容机器,只需要更少的机器。这样,可以更快地完成扩容和应急操作。因此,性能好的应用比性能差的应用更稳定。最后回到本文的主题:是什么让20行代码性能提升10倍?我的回答是:StringBuilderyyds!
