性能模式(下):如何解决核心的性能问题?

如今我们已然明晰,性能模式乃是用于提升性能指标的一种针对软件设计与实现的调整方式及手段。当我们理解了这些性能模式,便能在优化系统性能之时,迅速寻得调整设计实现的起始点和思考方向。

其一,和设计模式相同,每一种性能模式仅仅是为了解决特定业务场景之中的性能问题,倘若使用不当,极有可能会产生相反的效果。所以,您切不可局限于这几种性能模式,而是要掌握此类解决性能问题的思维。

其二,基于性能模式对软件设计实现所进行的调整,其所带来的性能收益实际上并非是确定的。因而,在做出调整优化之前,您需要通过测试获取性能提升收益的精确数据,然后再去权衡思考是否确实需要进行修改,这样也有助于节约成本。


好了,接下来我将从预计算模式着手,带您去了解它的设计原理和工作机制。

预计算模式

我们不妨先来设想这样一种场景:小明喜爱在家享用早餐,然而却不想过早起床,于是他决定在前天晚上就将菜洗净切好,如此一来,早上起来只需直接炒制便能食用,节省了早上所需花费的时间,能够多睡一会儿懒觉。那么在软件实现的业务逻辑当中,是否存在一些能够提前执行的计算逻辑呢?答案是肯定的。接下来我要介绍的预计算模式,便是通过挖掘出能够提前计算的业务逻辑,并在程序启动之前执行完毕,进而有效地提高了业务的处理速度。好,此刻我们来看一下预计算性能模式的具体工作流程:

在图上左侧优化前的代码里,第一个矩形代码块的执行所需成本为 3 。经由对重复的计算逻辑加以分析,察觉到其中有一部分的计算逻辑能够提前执行。针对这样的情形,我们能够把这部分计算从业务当中分离出来,运用预计算模式提前至程序启动时进行执行。此刻我们来看一个具体的实例。如下所展示的代码示例当中,所实现的功能是依据员工请假天数来计算当月薪水,其中 calcSalaryBeforeOptimize 表示优化前的实现,calcSalaryAfterOptimize 代表优化后的实现:

public class ClacOptimze {    public static int calcSalaryBeforeOptimize(int leaveDay) {int fullSalary = 12000;if (leaveDay <= 1) {            //使用条件判断,潜在分支预测失败return fullSalary;        }if (leaveDay < 5) {return fullSalary - leaveDay * 400; //使用了乘法运算逻辑        }
return fullSalary - leaveDay * 800;
}
final static int[] salarys = { 12000, 12000, 11800, 11600, 11400, 11200, 10800 };public static int calcSalaryAfterOptimize(int leaveDay) {return salarys[leaveDay]; //这里使用查表 }}


可以发现,在优化前的代码实现当中,需要进行多次的 if 判断,并且还需要进行乘法运算。然而在优化后的代码中,每次运行只需查表就能返回,执行速度会快许多。

实际上,以上代码示例所运用的预计算模式,所采用的策略是通过空间来换取时间,这在预计算性能模式的实现过程中是较为常见的一种方式。

通常而言,针对预计算工作量相对较小的情况,我们完全能够进行手工计算。但当计算量较大时,我们或许还需要开发单独针对预计算的程序。

当然,预计算模式并非仅有空间换时间这一种实现方式,还有众多实现并不会带来额外的内存开销,例如业务中内存的预申请、业务数据的预初始化等等。

另外,有些编程语言具备编译期计算的能力,针对此类场景,我们也能够将计算逻辑提前至编译期执行,以减少运行期的时间开销。比如 C++ 的常量表达式、模板泛型编程等,都提供了颇为强大的编译期计算能力。

这里我为您举个真实的例子。我曾经参与过一个 SaaS 服务时延的优化项目,就多次使用在数据库中添加冗余数据来记录预计算结果的方式,从而减少了业务处理运行期的开销,达成了降低时延的效果。此外,在嵌入式实时性的优化中,我们还通过挖掘业务中所有的预计算逻辑,多次大幅提升了产品性能。

不过,在使用预计算模式的时候,您还需要留意一点,那就是当需要对计算逻辑进行较大的调整时,您需要进行完备的测试,以免引入新的故障。

耦合模式

这种性能模式的原理实则十分简单,我就拿出行服务来给您举个例子。

我们知道,出租车司机在开车运营期间,喜爱选择拼车模式同时接送多位乘客,因为当乘客路线重合较多的时候,他们能够获取更大的现金收益,而这便是运用了耦合模式的解决思路。

所以,耦合模式的含义就是当您做一件事情的时候,不要将目光仅仅停留在这一件事情上,您还能够思考一下是否能够顺带把其他事情也一并处理掉。

在这里您可能立刻就会想到,这与面向对象设计原则中的“单一职责原则”存在冲突啊?的确,耦合模式在一些场景下会与单一职责产生冲突(单一职责推荐一个方法只实现一个功能,而耦合模式需要一个方法内同时实现多个功能),所以我更建议您只在性能影响权重较大的关键场景中使用它。

现在,我们先来了解下耦合性能模式的优化过程:


可以看到,图中左侧粉色的两个代码块是相对独立的,执行开销分别为 2 。在优化过程中将两个代码块逻辑合并到一起后,执行总开销变为了 3 。

这样,在使用这种方式优化后,系统的总执行开销就从原来的 12 降低到了 11 ,处理时延也就降低了。


这里我们来看一个具体的例子。下面是一个 Java 使用 MyBatis 访问数据库场景的代码片段,其中 UserMapperBeforeOptimize 代表的是优化前访问数据库的接口,UserMapperAfterOptimize 则代表优化后访问数据库的接口。

public class User {private String name;private Integer age;
public String getName() {return name; }
public void setName(String name) {this.name = name; }
public Integer getAge() {return age; }
public void setAge(Integer age) {this.age = age; }
}
public interface UserMapperBeforeOptimize { public String findNameById(String Id); //单一职责接口public String findAgeById(String Id); //单一职责接口}
public interface UserMapperAfterOptimize { public String findNameById(String Id); //单一职责接口public String findAgeById(String Id); //单一职责接口public User FindUserById(String Id); //新增的获取多个字段的耦合接口}

可以发觉,在优化之前的接口里涵盖了两个方法:依据 ID 获取名字、依据 ID 获取年龄。当众多的客户代码同时需要获取名字和年龄时,便能够通过在接口中增添一次性返回姓名和年龄信息的方法,以降低业务两次访问数据库所带来的网络和查询的额外开销。

耦合模式的应用场景颇为多样,例如:在数据库设计的进程中,基于性能方面的考虑,我们能够将多个表中的字段信息融合记录至一个大表内,进而让原来需要多次的查询操作,转变为一次查询便能全部获取。

在微服务接口设计中,通常 REST 接口并非是正交的,其中会包含一些基本功能接口和一些复合功能接口。而在一些典型的性能优化场景之下,运用复合接口能够一次性实现原来多个基本功能接口请求的功能,从而能够通过减少 REST 接口调用的次数来优化性能。

在嵌入式场景中,子系统间交互所使用的 TLV(Tag、Length、Value)数据结构类型,也是典型的耦合模式的应用。

另外,耦合模式并非只局限于接口层面,您也能够在计算逻辑中加以运用。同时您需要留意,在实现包含复合功能的接口与业务逻辑时,不建议删除掉原来的单一功能实现,这是为了避免给只使用简单接口与功能的客户造成额外的开销。


搬移计算模式

不论在工作中还是生活里,您或许极为擅长依据时间来统筹规划每个时间段所要做的事情。那么在软件的业务计算进程当中,您同样能够基于性能和效率方面的考虑,去调整安排计算逻辑在运行时间与物理位置上的分布。

在实时高性能的系统里,软件工程师在设计时往往会将业务逻辑划分成关键路径和非关键路径。而且我们清楚,系统时延在更大程度上取决于关键业务逻辑的处理时延。所以,倘若您能够把计算逻辑从关键路径转移到非关键路径,就能够提升产品的性能。

在运用搬移计算模式来调整计算业务逻辑的过程中,您需要重点关注的是处理时延的性能提升情况。但您也要留意,在这样优化处理过后,实际上系统的总负荷通常并没有降低。所以,您能够在系统核心目标是追求用户侧的时延最小化的业务场景中,选用这种性能模式。

下面我们就来看一下搬移计算模式的具体实现流程:

在图中左侧第一个矩形执行代码块中,通过分析业务流程和度量数据,我们发现部分业务逻辑能够推后计算,于是将这部分业务拆分到另外一个任务中去执行,进而减少了客户关注的处理时延。

这里我为您举一个真实的使用案例。在互联网 SaaS 服务中,对于我们这样的普通用户来说,会对请求的响应时延十分关注,而并不在意服务器处理业务的全部处理时间,所以在这种时候,我们就能够使用搬移计算模式来进行优化。

因此,我曾经在这类项目的优化过程中,引入了延迟计算服务,并通过对业务逻辑的优化,剥离了非关键业务,交给延迟计算任务进行处理,从而实现了在短时间内,将时延性能指标提升到 40%以上的目标。

而除了与 SaaS 服务相关的业务场景,在嵌入式场景和云服务场景中,使用搬移计算模式优化性能也都能够取得很好的效果。

但是您需要认识到,搬移计算的设计与实现实际上容易导致整个系统的复杂度提高,进而容易引入额外的故障,所以您在引用这种性能模式时,务必要注意进行充分的功能验证与性能优化的提升分析。

丢弃模式

您可以先来设想这样一种场景:通常情况下,您每天早上起床准备去上班时,都会先洗脸、刷牙,然后再带上电脑出门;然而当某天起晚了,您或许就会省去洗脸、刷牙这一步,直接背着电脑出门。所以您发现了吗,我们所做的很多事情在特定的场景下实际上都是能够舍弃的。

那么回到软件业务处理的过程当中,道理也是相同的。在软件系统里,我们在一些特殊的场景下运用丢弃模式,便能够达成极佳的性能优化效果,这里我们先来了解一下它的具体工作流程:

在上图的左侧部分,能够看到第一个矩形代码块的执行开销为 3 。而我们通过对业务流程和度量数据进行分析,发觉这个功能的优先级较低。因此能够采用的优化策略为:将优先级较低的代码块放置到业务的最后,在极端的场景下,甚至可以通过直接丢弃不处理的方式来确保系统性能不恶化。

丢弃模式,在实时嵌入式的场景中运用得比较多,这种模式十分容易理解和操作。您在实际的业务场景中,要留意先识别出业务中的非关键部分逻辑,确认其支持可关闭,如此当系统处于超负荷运行时,便能够直接将这部分业务停止。

在实际的业务场景下,如何识别出业务中的非关键部分逻辑?

实时嵌入式场景中使用丢弃模式时,如何确保系统的安全性?

除了丢弃模式,还有哪些优化策略可以提高系统性能?


请使用浏览器的分享功能分享到微信等