运用AOP思想更优雅地进行性能调优

在软件测试中,如果想在一个耗时严重的操作中找出其耗时的瓶颈时,一般采用的方法是在每个被调用的函数中写进测试代码,在运行时打出日志。如果该操作涉及到的业务逻辑特别复杂时,插入这些测试代码不仅工作量十分巨大,而且难以维护。如果后期剔除不干净,不仅增加了无关的代码量,还会在执行时造成不必要的资源浪费。

像在手机管家的清理加速模块中,垃圾扫描这个功能的耗时是性能优化的重点,如何快速测试和分析扫描过程中的函数耗时一直是性能测试想克服的难题。但是在数以千计的函数中插入测试代码简直是一场恶梦,所以优化过程一直是不知道从何开始从何结束。这时候好想有个脚本可以跟踪调用关系,并且可以根据规则自动插入测试代码。

在继续进一步学习时,发现很多开发大牛也遇到同样的问题,他们代码中有一些公共的代码需要统一在一系列的函数运行时被调用,在经历了无数次重复的代码修改后,终于忍无可忍的他们提出了一种新的编程思想——面向切面编程(Aspect-Oriented Programming),这是一种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想。

它是一种可以通过预编译方式和运行期间的动态代理实现的编程技术,在不修改源代码的情况下给程序统一添加某种功能,共享一个行为,主要可用来作为日志记录,性能统计,安全控制和事务处理等等。而且最重要的是,打包时可以通过不同的编译选项选择插入或者剔除测试代码,真正做到无损插桩!啊!果然很适合这种性能调优场景,赶紧学起来!

开始了解AOP

面向切面编程思想中有两个核心的步骤:

1. 在软件中挑选出来需要关注的切面关注点 (pointcut),即程序中我们选择出来的执行点的集合,选择出来之后就可以对其进行后续操作,比如性能调优这里选择了手机管家中的sdcard卡扫描这个关注点,这里可以通过通配符组合各种规则来选取一系列的函数;

2. 在切面上增加一些需要统一执行的操作(advice),比如统一给切面选取到的函数添加统计耗时的代码, 这里主要有三种类型,分别是:

(1)before : 在执行点的代码执行之前进行操作

(2)after : 在执行点的代码执行之后进行操作

(3)around :将before和after拼接在同一个执行点上,就是around操作

具体示意图如下,看不懂也没关系,下面还会有具体代码的演示。

所以怎么应用到具体工程上

AOP这么厉害,所以到底怎么应用到具体工程上。对于java、C++都已经有了对应的AOP支持版本,aspectj就是基于java易用的、功能强大的aop编程语言。在eclipse中安装AJDT插件就可以快速对工程进行插桩。

aspectJ插桩实战

在刚刚简介的基础上了解AOP的基本思想,接下来通过aspectJ实战脚本的例子深入了解下如何对你的程序进行自动插桩吧。

(1)查看运行函数:

我的程序功能很复杂,在测试CPU使用率时总发现一个现象,灭屏时进程的CPU使用率会突然升高,现有的log无法发现问题,想看看这是灭屏后程序到底有没有做什么异常操作,有没有异常调用其他不相关函数,这时候该怎么办?

用法详解:

  • a. pointcut TestPoint()表示定义了TestPoint这个切点,在切点表达式中使用execution(*deepclean..*(..))匹配所有函数名中含有deepclean字符的函数,execution代表在该函数的执行处进行操作;
  • b. before():TestPoint() 操作代表在TestPoint这个切点的执行前插入打印函数签名的代码。

用法:打完插桩包后,安装后,打开被插桩过的软件,logcat中自动输出每个清理相关函数的函数名,此时便可查看是否有异常调用

(2)定位耗时操作的性能瓶颈:

业务中有个扫描的函数,想查看在扫描执行过程中该函数调用到的每个函数运行耗时,查看耗时瓶颈,以定位最应优化的函数

pointcut 定义详解:

  • a. cflow表示跟踪scanAll()函数被调用的工作流,所以在scanAll()中调用的函数都会被我们选取到;
  • b. !within(CPUTimeTest)表示不要跟踪aspectj脚本测试类中的代码,避免插桩后代码的自循环,其中CPUTimeTest是工程中自定义的测试类名(这个是使用cflow关键字对函数进行跟踪时的必加项!);
  • c. && TestPoint() 保证跟踪的函数都是clean相关的函数,避免引入不必要测试日志,TestPoint()定义详情参照上个例子。

advice 定义详解:

  • a. 使用around()函数将测试函数的函数体包围在测试代码中,被测函数的函数体为Object obj= proceed();
  • b. 使用安卓自带的debug函数获取线程运行耗时:Debug.threadCpuTimeNanos()(debug类中有许多性能监控获取接口,如内存使用率、CPU使用率等,可以按照自身需要切换不同监控数据)
  • c. 插桩后代码实际执行顺序为:记录进入函数时的startTime,运行函数本体,记录函数执行完的endTime,输出两者之间的时间差

用法:安装插桩包后,通过logcat收集日志中各个函数耗时,得到扫描过程中每个函数过程中的耗时和被调用次数,查看扫描瓶颈。如果输出函数过多,还可以使用HashMap对每个函数的调用次数和总耗时进行统计,最后输出最终结果。

(3)输出异常情况的调试信息:

测试过程中想监控一个函数,如果其耗时(或者其他指标)超过预定的最大值,则为异常情况,想输出异常情况下函数的调用行数和调用堆栈

用法详解:

  • a. 在上一个例子的基础上,使用if逻辑判断耗时是否是异常数值,减少输出日志的数量,更容易定位问题
  • b. 使用thisJoinPointStaticPart获取该切点的静态信息,包括被调用的文件名,行数和函数签名,方便进行定位和跟踪

用法:安装插桩包,触发业务逻辑,查看日志输出,定位异常路径

获取AOP的更多信息

由于篇幅的局限,这里只是通过一些例子简单地讲解了AOP的相关应用。作为一种新的程序设计模式,AOP还蕴含着更多的能力,不仅仅适用于性能调优上,还能应用在开发上,它能将逻辑代码和处理琐碎事务的代码分离开,减少代码实现和维护的复杂度。

更多的AOP和aspectJ的学习资料可以参考下面链接:

(1)https://eclipse.org/aspectj/doc/released/adk15notebook/index.html 官方aspectJ开发者文档

(2)http://blog.csdn.net/zl3450341/article/details/7673938 详细的aspectJ语法中文讲解

原文发布于微信公众号 - 腾讯移动品质中心TMQ(gh_2052d3e8c27d)

原文发表时间:2016-09-27

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Python

python select模块详解

要理解select.select模块其实主要就是要理解它的参数, 以及其三个返回值。 select()方法接收并监控3个通信列表, 第一个是所有的输入的data...

4886
来自专栏coolblog.xyz技术专栏

AbstractQueuedSynchronizer 原理分析 - Condition 实现原理

Condition是一个接口,AbstractQueuedSynchronizer 中的ConditionObject内部类实现了这个接口。Condition声...

46310
来自专栏向治洪

责任链模式

概述 概念:责任链模式是一种对象的行为模式。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定...

1965
来自专栏屈定‘s Blog

工作--如何封装第三方服务?

业务开发中经常会对接某某第三方服务,因此会经常写一些SDK供服务使用,一种比较好的做法就是使用命令模式封装第三方服务,命令模式对于调用方来说简洁明了,也正是封装...

2172
来自专栏北京马哥教育

Python imports指南

来源:Python程序员 ID:pythonbuluo 声明:如果你每天写Python,你会发现这篇文章中没有新东西。 这是专为那些像运维人员等偶尔使用Pyt...

2575
来自专栏不想当开发的产品不是好测试

#测试框架推荐# test4j,数据库测试

# 背景 后端都是操作DB的,这块的自动化测试校验的话,是需要数据库操作的,当然可以直接封装方法来操作数据,那么有没有开源框架支持数据操作,让我们关注写sql语...

61212
来自专栏magicsoar

C++ socket网络爬虫(1)

C++写的socket网络爬虫,代码会在最后一次讲解中提供给大家,同时我也会在写的同时不断的对代码进行完善与修改 我首先向大家讲解如何将网页中的内容,文本,图片...

5105
来自专栏风中追风

java类的加载过程和类加载器的分析

我们知道,我们写的java代码保存的格式是 .java, java文件被编译后会转换为字节码,字节码可以在任何平台通过java虚拟机来运行,这也是java能够跨...

5478
来自专栏owent

C++ 新特性学习(八) — 原子操作和多线程库[多工内存模型]

分别对于两个进程而言,可观察行为确实没有变化。而这种优化在某些时候确实会有比较明显的效果。但是很显然,语义变化了。在原来的结果里不可能发生 x和y都为0的情况,...

661
来自专栏Fundebug

Source Map入门教程

部署前端之前,开发者通常会对代码进行打包压缩,这样可以减少代码大小,从而有效提高访问速度。然而,压缩代码的报错信息是很难Debug的,因为它的行号和列号已经失真...

2226

扫码关注云+社区

领取腾讯云代金券