早就听过CPU火焰图的强大功能,也听过几个火焰图工具,今天终于开始尝试使用CPU火焰图生成工具。
奈何由于各种原因,Intellij自带的火焰图插件并不能用,着实让人不快。故而找到一个async-profiler分析工具作为替代品。
当时正在测试随机数性能的,所以就用了一个动态QPS模型的Case,学习了async-profiler的使用。很意外地发现了一个性能可以优化的地方。经过尝试,CPU使用率降低了0.24%,也算是第一个成果了。
async-profiler
这个工具安装和使用教程,可以网上搜一下,建议去Github仓库看看Wiki,这里我就不多说。
Case code
下面是Case的代码,用了动态QPS模型。
class T extends SourceCode {
static void main(String[] args) {
def total = 1000_0000
def index = new AtomicInteger()
int i = 0
def test = {
i++ % total
// index.getAndIncrement() % total
getRandomInt(total)
sleep(0.01)
}
new FunQpsConcurrent(test, "测试随机性能").start()
}
}
下面是执行任务的方法com.okcoin.hickwall.presses.funtester.frame.execute.FunQpsConcurrent#start代码:
void start() {
if (executor == null) executor = ThreadPoolUtil.createCachePool(Constant.THREADPOOL_MAX, "Q")
if (Common.PERF_PLATFORM) controller = new RedisController(this)
if (controller == null) controller = new FunTester();
new Thread(controller, "receiver").start();
while (key) {
ThreadPoolUtil.executeTask(executor, qps, produce, total, name)
}
stop()
}
优化过程
整个main线程差不多都在上面那个while循环中。我先生成了火焰图,看了main的火焰图,如下:
CPU火焰图(优化前)
可以看出com.okcoin.hickwall.presses.funtester.frame.execute.ThreadPoolUtil#executeTask方法使用了0.53%的CPU,这里看到一个getSecond方法里面使用CPU最多,而且创建了Calendar对象,代码如下:
if (Time.getSecond() % COUNT_INTERVAL == 0) {
int real = total.sumThenReset() / COUNT_INTERVAL as int
def active = executor.getActiveCount()
def count = active == 0 ? 1 : active
log.info("{} design QPS:{},actual QPS:{} active thread:{} per thread efficiency:{}", name, qps, real, active, real / count as int)
}
这里本意是为了间隔几秒输出当前的设计QPS、实际QPS、活跃线程数等信息。这里我想是不是可以直接用时间戳实现这个需求,应该会更快。所以修改后的代码如下:
if (SourceCode.getMark() % COUNT_INTERVAL == 0) {
int real = total.sumThenReset() / COUNT_INTERVAL as int
def active = executor.getActiveCount()
def count = active == 0 ? 1 : active
log.info("{} design QPS:{},actual QPS:{} active thread:{} per thread efficiency:{}", name, qps, real, active, real / count as int)
}
改完之后重新跑了一次,抓取火焰图,main线程部分如下:
CPU火焰图(优化后)
com.okcoin.hickwall.presses.funtester.frame.execute.ThreadPoolUtil#executeTask方法CPU使用率降低到0.29%,但是多了一部分使用使用,整体com.okcoin.hickwall.presses.funtester.frame.execute.FunQpsConcurrent#start方法的CPU使用率是0.44%,相比较之前的0.53%降低了0.09%。
四舍五入也就优化了0.1%,也算是优化效果,有所收获。然后我突然发现整个火焰图的中CPU占用的大多数都是那个sleep方法,可能这个之前性能测试中的随机数性能问题探索中的结论可能不够明显,甚至忽略了三个方式的差异。后续我重新设计用例测一遍。