操作系统android之性能优化学习

《嵌入式Linux内存与性能详解》笔记4——性能优化

2020-04-05  本文已影响0人  wipping的技术小栈

一、前言

前面讲了关于 内存 方面的优化,那么接下来的文章我们主要聚焦于 性能 的优化,那么主要体现在优化程序 速度 上。程序的 速度 很大程度上会影响用户体验或者程序的实际效用,所以优化 性能速度 也是程序员需要关注的一个方面,从本文起接下来的几篇文章将讲述如何对 程序性能 进行优化。

二、优化思想

在讲解优化之前,我们先看一下 优化的思想,它可以保证我们在学习或者优化的过程中保持对问题的关注,让我们知道是在学习什么跟如何优化。

2.1 性能分析瓶颈分类

说到优化,一个程序运行慢可能是有多种原因构成的,那么前辈们就将这多种原因归结为以下 3类

2.2 优化基本原则

另外一般有 5 个基本纲领为我们提供优化思路:

2.3 优化思路

在程序优化上,往往 20%的代码 占用了 80%的运行时间,所以我们需要找出这 20% 的代码。所以 如何找出需要优化的代码 是一个我们需要关注的问题,而这个问题我们可以有一些工具来解决,在下面的章节会提到。

第一步:找出 性能瓶颈的代码,在笔者看来,分析程序问题,找出性能瓶颈是重中之重。
第二步:进行 代码优化

2.4 优化经验总结:

下面是书中提到的一些 优化经验,在了解这些经验之前,我们需要了解几个概念:

书中经验总结如下:

2.5 优化的层次

在我们了解到了 程序瓶颈 之后,接下来我们就要考虑对代码进行优化:

程序的优化,主要包括四个层次:

  1. 算法和数据结构的优化
  2. 编译器优化
  3. 代码优化
  4. 硬加速

优化新手容易犯的错误就是:找到 程序热点 时,过于兴奋,急匆匆的就开始 局部代码 的优化。但我们需要在 更大的范围内分析,是什么导致这个 程序瓶颈 的存在,有时可能根本就不需要改动你的程序。

三、性能评估

一般情况下,我们可以把 性能优化 分为 2 个部分

3.1 系统及进程性能评估

据上面所讲,性能评测 是至关重要的一个环节,如果不知道哪里出了问题,那么优化就无从下手。程序性能的问题是有很多原因的,我们先看看一般的问题分析:

3.1.1 proc系统相关

系统相关属性 我们可以通过下面 2 个文件来了解。

3.1.1.1. /proc/stat

我们使用 cat /proc/stat 来获取 系统相关 的信息,这样可以知道我们 系统效率 的具体细节

运行结果.png

下面 按行 说明各行数据的意义:

3.1.1.2. CPU利用率

CPU利用率是指CPU工作时间占总时间的比重, 简单地理解为 单位时间内 CPU处于忙状态的时间占比。

摘抄附录《CPU利用率与Load Average的区别?》中的说法:
CPU利用率 可以看出在某一个时间段内CPU被占用的情况,如果CPU被占用时间很高,那么就需要考虑CPU是否已经处于超负荷运作,长期超负荷运作对于机器本身来说是一种损害,因此必须将CPU的利用率控制在一定的比例下,以保证机器的正常运作。

有一种情况需要注意的是:使用 自旋锁忙等处理的锁会导致 CPU利用率的上升,此时 CPU利用率 并不能很好的反映 CPU的使用情况

3.1.1.3. /proc/loadavg

运行结果.png
loadavg 主要检查当前系统的 负载情况:下面 按从左到右 逐个说明:
  1. 1分钟 的平均负载
  2. 5分钟 的平均负载
  3. 15分钟 的平均负载
  4. 在采样时刻,运行队列的任务的数目,与 /proc/statprocs_running 表示相同意思
  5. 在采样时刻,系统中 活跃的任务个数(不包括运行已经结束的任务)
  6. 最大的 pid值,包括 轻量级进程(即线程)

更多关于 CPU负载 的讲述可以参考 3.2.1.2小节

3.1.2 proc进程相关

3.1.2.1 proc进程相关属性

要查看 进程状态 ,先需要进入 进程proc目录 下的文件夹,然后使用 cat stat 查看其状态,
如图:

进程stat
下面按顺序逐个介绍各个数据的意义:

3.1.2.2 使用proc进程相关属性计算CPU占有率

先看看下面的公式:

想要计算 CPU占有率 还对 进程的运行采样,一般需要 2个采样点

经过计算和采样后可以按照下面的公式来计算:

3.2 系统性能评估工具

3.2.1 top

top 是一个常用的 性能分析 软件,运行结果如下图所示:

运行结果
下面将 top 分 2 点来讲述:

3.2.1.1 内存分析

运行结果如下图所示:

内存
可以看到它统计的内存有:usedfreeshrdbuffcached
各个内存的含义在前面的 内存分析 文章中已经说过,这里不再赘述

3.2.1.2 CPU及负载分析

1. CPU负载

Load AverageCPU负载 ,该数据是 每隔5秒钟 检查一次活跃的进程数,然后按特定算法计算出的数值。

Load Average 可以理解为 CPU 的带载情况,即有多少进程需要 CPU 来处理。它并不是描述 CPU的使用情况,其本质应该是 在单位时间内,CPU正在处理的进程数以及等待CPU处理的进程数之间的和,也就是CPU进程队列的统计信息

Load Average 越高说明越多的进程在抢占CPU,因而会导致 CPU资源的竞争越来越激烈,对于CPU资源的申请和维护的成本也会加大

理想情况下是一个CPU带一个进程,这样就不会发生CPU资源抢占。所以在一般情况下,如果这个数除以逻辑CPU的数量,结果高于5的时候就表明系统在超负荷运转了。

CPU利用率和CPU负载的区别

两者之间并没有太大的关联。比如一个进程一直在使用CPU进行运算,那么此时CPU利用率会达到100%,但平均负载则趋近于 1。反过来说,当CPU的工作负载越大,代表CPU必须要在不同的工作之间进行频繁的工作切换。

按照笔者的理解:

2. CPU利用率及负载
CPU及负载

2.2.1.3 进程分析

image.png

3.2.2 vmstat

在嵌入式设备上,busybox 是不带有该工具,而该工具的源码也不是独立的,而是在 procps 这个中聚集中。关于 procps 的交叉编译请参考附录中的:

编译完成后将相应的文件拷贝到设备端上就可以使用 vmstat 工具了,运行结果如下:

运行结果
该命令的意思是:每隔1秒运行1次vmstat,一共运行5次。

数据含义如下:

注意: 内存够用的时候,这2个值都是 0。如果这2个值长期大于0时,系统性能会受到影响,磁盘IOCPU资源 都会被消耗。不能因为观察到 空闲内存(free) 很少或接近于0时,就认为内存不够用了。还需要结合 siso。如果 闲内存(free)很少,同时siso 也很少(大多时候是0),那么此时说明内存刚好够用,系统性能暂时不会受到影响的。

注意:随机磁盘读写的时候,bi** 和 bo 值越大,能看到 CPUIO等待(wa) 也会越大。**

注意: 随机磁盘读写的时候,incs 值越大,能看到 CPU内核消耗的CPU时间(sy) 也会越大。

CPUCPU 的使用情况,以 百分比表示

四、查找性能瓶颈

在分析程序的时候,往往需要找个程序的 性能瓶颈。从而对 性能瓶颈 进行优化,这样可以取得比较大的收益。我们一般都使用功能来查找 性能瓶颈,一般有 gprofOProfile 或者 perf 等。本文着重说明一下 perf 的使用方法,及如何将 perf 获取到的数据转换为 火焰图 来分析程序性能。

4.1 perf

4.1.1 perf编译

perf 依赖于几种库,分别如下:

1. zlib交叉编译

  1. 点击 zlib下载地址 下载 zlib 源码。
  2. 解压并进入 zlib的目录
  3. 使用命令导出 交叉编译工具,命令举例如下:

export CC=arm-linux-gnueabihf-gcc

  1. 使用命令进行配置,该命令会直接将编译出来的库自动加入到我们的 库路径,我们就不再需要去指定 库路径 了。如下:

./configure --host=arm-linux-gnueabihf --prefix=/your_compiler_path/arm-linux-gnueabihf/libc/usr

附录

  1. 编译安装

make -j12 && make install

2. elfutils交叉编译

我们编译 elfutils 是需要其里面的库 libelf。编译过程依赖 2 个工具 m4bison。在编译前请检查编译机是否有安装该工具。编译步骤如下:

  1. 点击 elfutils下载地址 下载 elfutils 源码。
  2. 解压并进入目录
  3. 使用命令进行配置:

./configure --host=arm-linux-gnueabihf --prefix=/your_compiler_path/arm-linux-gnueabihf/libc/usr/ --disable-debuginfod

  1. 编译安装

make -j12 && make install

3. perf交叉编译

perf源码 在我们开发板上 linux 的源码中,其路径一般是 /your_linux_path/tool/perf
编译步骤如下:

  1. 进入 perf 源码目录
  2. 使用命令进行编译

make LDFLAGS=-static ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- DEBUG=1 HAVE_CPLUS_DEMANGLE=1

  1. 编译完成后在 perf目录下 有个叫 perf 的执行文件。将 perflibzlibelf 拷贝到开发板上对应的位置,就是 库路径和执行文件路径

4.1.2 perf概念

perf 的原理是通过对 系统事件 进行 统计采样 得到 统计数据,再通过分析 统计数据 得出性能瓶颈的结果。

perf 中能采样 系统事件 分为以下 3 类:

4.1.2 perf使用方法

perf 是一个工具集,一共包含 20多种 工具,下面看看常用的 5种工具

1. perf list

perf 的运行原理就是对 系统事件 进行采样,从而得到相关数据来分析。我们可以使用perf list 来查看编译出来的 perf 支持哪些 系统事件。每个 linux版本perf 可能有所不同。笔者的运行结果如下:

运行结果
可以看到一共有 10种软件事件3种硬件事件。笔者编译出来的 peff 比较简陋,所以本文主要讲述 软件事件 相关的分析。

2. perf stat

perf stat 可以统计 程序整体情况,它能够显示出 程序系统事件 统计数据。一般用于 分析程序的整体性能

常用选项如下

运行结果如下:


image.png

perf stat 显示的默认 统计信息如下:

3. perf top

perf toptop 不同的是。top 常用于分析系统的整体性能。而 perf top 可以精确到系统或程序中 函数级的运行情况

常用选项如下

运行结果如下:


image.png

4. perf record

perf record 可以 用于记录perf统计的数据 并输出到 文件perf.data,并使用相关工具生成 可视化 图像来分析程序。配合 perf report 可以精确的分析程序

常用选项:

这里不展开说 perf report,我们使用另一个强有力的工具 火焰图FlameGraph,可以在 github 上找到火焰图的项目,点击下载火焰图生成软件

下面我们通过一个例程来说明:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>

void* func_test1(void* arg)
{
    int j = 0;
    for(int i = 0; i < 90000000; i++) 
        j=i; 
    return NULL;
}

void* func_test2(void* arg)
{
    int j = 0;
    for(int i = 0; i < 120000000; i++) 
        j=i; 
    return NULL;        
}

int main()
{
    pthread_t test1 = 0;
    pthread_t test2 = 0;

    pthread_create(&test1, NULL, func_test1, NULL);
    pthread_create(&test2, NULL, func_test2, NULL);
    pthread_join(test1,NULL);
    pthread_join(test2,NULL);

    return 0;
}

注意,在使用时需要注意要加参数 -g。
步骤如下:

  1. 在设备上运行 perf record -a -g your_program
  2. 使用命令 perf script -i perf.data > perf.unfold 将生成的 perf.data 转换为 perf.unfold
  3. 将生成的 perf.unfold 拷贝到宿主机上
  4. 使用 FlameGraph 中的工具执行命令 ./stackcollapse-perf.pl perf.unfold > perf.fold
  5. 使用命令 ./flamegraph.pl perf.fold > perf.svg 生成 svg文件
  6. 使用 浏览器 打开 svg文件 (笔者使用的是chrom)

结果如下:


运行结果

可以看到 func_test1func_test2 占据了 执行时间 的大头,在旁边还有 2个 函数在执行。
点击 左侧红框 查看如下:

左侧
从结果中可以看到是 perf 自身的调用情况。

再点击 右侧红框 查看如下:

右侧
可以看到是一个名为 swapper 的函数在执行,而这个函数是干什么用的呢?
其实在 CPU 执行程序时,空闲的时候就会调用 swapper 函数,此时 CPU 处于 idle状态

五、程序优化

前面我们讲了 程序优化 的几个层次,分别是:

  1. 算法和数据结构的优化
  2. 编译器优化
  3. 代码优化
  4. 硬加速

其中 算法和数据结构的优化 不在本文内容计划之内。使用 SIMD指令硬加速 已经在笔者的其他博客 neon指令 系列一将说到。 下面主要简单地讲述一下 编译器优化代码优化

5.1 编译器优化

编译器按照 arm-linux-gcc 系列来说明。下面将按书中所写,列举出 gcc 的相关编译优化选项。如果你想 关掉 某一个优化选项, 你可以在 -f优化选项 之间增加 no
在编译时,我们一般回加入 -O1、-O2和-O3 等选项,其实它们的作用就是让编译器进行不同程度的优化,那么每种优化打开的编译选项都不同,下面看看每种优化都打开了哪些选项。

5.1.1 -O1优化

const int i = 2*2;

编译器确实将 2*2 算成 4 了,以后碰到 i 就用 4 替换,这个计算 2*2 的过程据说叫 常量折叠(const folding),而用 4 替换 i 的过程叫做 复写传播(copy propagation)

5.1.2 -O2优化

5.1.3 -O3优化

5.2 代码优化

按照笔者理解,代码优化 更加考验一个人的经验积累。本节不会详细介绍书中每种 代码优化技巧 的原理,主要是总结 代码优化技巧结论。可以快速应用于 开发 中。

六、参考链接

/proc/stat 详解
Linux top命令详解
Linux CPU性能优化方法
json-c 交叉编译(undefined reference to rpl_malloc )
交叉编译Procps-ng-3.3.11【转】
CPU 利用率背后的真相,你知道吗?
CPU利用率与Load Average的区别?
死锁一定会造成cpu使用率飙升吗?
深入理解perf报告中的swapper进程
系统级性能分析工具perf的介绍与使用
perf工具+ 火焰图分析性能
perf 工具原理与使用
GNU下优化代码
gcc -o 优化选项

上一篇下一篇

猜你喜欢

热点阅读