perf基本操作:
● perf top:查看当前全部进程及其性能消耗
● sudo perf record:收集性能数据记录到文件,默认为perf.data,也会保留上一次的记录文件perf.data.old
● sudo perf report:对记录文件进行分析,选中指定函数enter可看到调用栈,到最低层调用栈再enter可进入机器码查看对应行数的性能消耗,都采用-g则可找到耗时的对应行。
○ -i 指定分析的.data文件
● sudo perf script:保存裸trace数据,直接观察采样点的数据分析
● sudo perf annotate:在gcc编译时加入-g,在record后进行annotate,则可看到对应代码行和相关机器码,以及他们的性能消耗
● -p 指明检测进程的pid,不指明则检测整个系统
● -t 指明线程pid
● -F 设置采样频率,默认频率为1000。但一般推荐设置为-F 99或-F 999,因为要避免引起lockstep采样:
○ 锁步采样是指分析样本以与应用程序中的循环相同的频率出现。 这样做的结果是样本经常出现在循环中的同一位置,因此它会认为操作是最常见的操作,并且可能是瓶颈。
● -a 显示在所有CPU上的性能统计信息
● -c 显示在指定CPU上的性能统计信息
● -g 开启调用栈分析,记录函数间的调用关系
● sleep 等待指定时间后停止,常用于record
● -e 对指定的性能事件event进行分析,事件表见下,默认事件为cpu-cycles。
如:分析pid为123456的cpu-clock情况,延时5秒,并查看函数调用
sudo perf record -p 123456 -g -e cpu-clock sleep 5 (生成perf.data)
sudo perf report -g (进入分析界面)
如:分析可执行文件test
sudo perf record ./test
sudo perf report
perf event详解:
事件分为三种:
[Hardware event] :
● cpu-cycles:统计cpu周期数,用于性能的耗时分析。
cpu周期:指一条指令的操作时间。(不同于cpu-clock,是硬件事件)
● instructions: 机器指令数目,一条指令会包含多个cycles
● cache-references: cache命中次数
● cache-misses: cache失效次数,过多的实效会增加IO时间,增加耗时。
○ 当运算器需要从存储器中提取数据时,它首先在最高级的cache中寻找然后在次高级的cache中寻找。如果在cache中找到,则称为命中hit;反之,则称为不命中miss。
○ 为L1L2L3失效的统计和,具体哪个寄存器的可在Hardware cache event中检测。
○ 优化:程序在一段时间内访问的数据通常具有局部性,比如对一维数组来说,访问了地址x上的元素,那么以后访问地址x+1、x+2上元素的可能性就比较高;现在访问的数据,在不久之后再次被访问的可能性也比较高。局部性分为“时间局部性”和“空间局部性”,时间局部性是指当前被访问的数据随后有可能访问到;空间局部性是指当前访问地址附近的地址可能随后被访问。处理器通过在内存和核心之间增加缓存以利用局部性增强程序性能,这样可以用远低于缓存的价格换取接近缓存的速度。
○ 从cache指令上做优化:简化调用关系,减少冗余代码(即不是必须存在的的代码),减小代码量,减少不必要的调用;如:
for i=1…n
for j=1…n
for k=1…n
c[i,j] += a[i,k]*b[k,j]
for i=1…n
for k=1…n
for j=1…n
c[i,j] += a[i,k]*b[k,j]
○ 在cache层面上,代码二要比代码一效率高很多,因为代码2的b[k,j]是按行访问的,所以存在良好的空间局部性,cache line被充分利用。代码1中,b [k,j]由列访问。 由于行的存储矩阵,因此对于每个缓存行加载,只有一个元素用于遍历。
○ 相关优化可参考:
■ =&request_id=&biz_id=102&utm_term=cache%20misses&utm_medium=distribute.pc_-task-blog-2allsobaiduweb~default-6-79047302.142v7pc_search_result_control_group,157v4control&spm=1018.2226.3001.4187
■
■ .html
● branch-instructions: 分支预测成功次数
● branch-misses: 分支预测失败次数
○ 分支预测器是一种数字电路,在分支指令执行前,猜测哪一个分支会被执行,能显著提高pipelines的性能。
○ 条件分支通常有两路后续执行分支,not token时,跳过接下来的JMP指令,继续执行, token时,执行JMP指令,跳转到另一块程序内存去执行。
○
○ 加入分支预测器后,为避免pipeline停顿(stream stalled),其会猜测两路分支哪一路最有可能执行,然后投机执行,如果猜错,则流水线中投机执行中间结果全部抛弃,重新获取正确分支路线上的指令执行。可见,错误的预测会导致程序执行的延迟。
○ 优化:为保证分支预测有效,应让具有相同逻条件和逻辑的运算处于同一程序块中,如:
■ 1、调整循环层数和规模,使底层紧凑
public class BranchPrediction {
public static void main(String args[]) {
long start = System.currentTimeMillis();
for (int i = 0; i < 100; i++) {
for (int j = 0; j <1000; j ++) {
for (int k = 0; k < 10000; k++) {
}
}
}
long end = System.currentTimeMillis();
System.out.println("Time spent is " + (end - start));
start = System.currentTimeMillis();for (int i = 0; i < 10000; i++) {for (int j = 0; j <1000; j ++) {for (int k = 0; k < 100; k++) {}}}end = System.currentTimeMillis();System.out.println("Time spent is " + (end - start) + "ms");
}
}
//output
Time spent in first loop is 5ms
Time spent in second loop is 15ms
■ 这个差异就是因为分支预测。循环本质也是利用cmp和jle这样的先比较后挑战的指令来实现的,每一次循环都有一个 cmp 和 jle 指令。每一个 jle 就意味着,要比较条件码寄存器的状态,决定是顺序执行代码,还是要跳转到另外一个地址。也就是说,在每一次循环的时候,都会有一次“分支”。
■ 2、调整循环逻辑顺序,使之有一定的有序性
#include
#include
#include
int main()
{
// 随机产生整数,用分区函数填充,以避免出现分桶不均
const unsigned arraySize = 32768;
int data[arraySize];
for (unsigned c = 0; c < arraySize; ++c)data[c] = std::rand() % 256;// !!! 排序后下面的Loop运行将更快
std::sort(data, data + arraySize);// 测试部分
clock_t start = clock();
long long sum = 0;for (unsigned i = 0; i < 100000; ++i)
{// 主要计算部分,选一半元素参与计算for (unsigned c = 0; c < arraySize; ++c){if (data[c] >= 128)sum += data[c];}
}double elapsedTime = static_cast<double>(clock() - start) / CLOCKS_PER_SEC;std::cout << elapsedTime << std::endl;
std::cout << "sum = " << sum << std::endl;
}
//output
10.218
sum = 312426300000
29.6809
sum = 312426300000
■ 简单地分析一下:
有序数组的分支预测流程:
T = 分支命中
N = 分支没有命中
data[] = 0, 1, 2, 3, 4, … 126, 127, 128, 129, 130, … 250, 251, 252, …
branch = N N N N N … N N T T T … T T T …
= NNNNNNNNNNNN ... NNNNNNNTTTTTTTTT ... TTTTTTTTTT (非常容易预测)
无序数组的分支预测流程:
data[] = 226, 185, 125, 158, 198, 144, 217, 79, 202, 118, 14, 150, 177, 182, 133, …
branch = T, T, N, T, T, T, T, N, T, N, N, T, T, T, N …
= TTNTTTTNTNNTTTN ... (完全随机--无法预测)
在本例中,由于data数组元素填充的特殊性,决定了分支预测器在未排序数组迭代过程中将有50%的错误命中率,因而执行完整个sum操作将会耗时更多。
■ 优化:利用位运算取消分支跳转,是条件判断情况一致可预测,不会回退
|x| >> 31 = 0 # 非负数右移31为一定为0
~(|x| >> 31) = -1 # 0取反为-1
-|x| >> 31 = -1 # 负数右移31为一定为0xffff = -1
~(-|x| >> 31) = 0 # -1取反为0
-1 = 0xffff
-1 & x = x # 以-1为mask和任何数求与,值不变
故分支可优化为:
int t = (data[c] - 128) >> 31; # statement 1
sum += ~t & data[c]; # statement 2
避免移位操作版:
int t=-((data[c]>=128)); # generate the mask
sum += ~t & data[c]; # bitwise AND
■ 分析:
1. data[c] < 128, 则statement 1值为: 0xffff = -1, statement 2等号右侧值为: 0 & data[c] == 0;
2. data[c] >= 128, 则statement 1值为: 0, statement 2等号右侧值为: ~0 & data[c] == -1 & data[c] == 0xffff & data[c] == data[c];
故上述位运算实现的sum逻辑完全等价于if-statement, 更多的位运算hack操作请参见bithacks.
若想避免移位操作,可以使用如下方式:
○ 相关优化可参考:
■ =&request_id=&biz_id=102&utm_term=%E5%88%86%E6%94%AF%E9%A2%84%E6%B5%8B&utm_medium=distribute.pc_-task-blog-2allsobaiduweb~default-0-82893317.142v7pc_search_result_control_group,157v4control&spm=1018.2226.3001.4187
■
■
● bus-cycles
● stalled-cycles-frontend OR idle-cycles-frontend
● stalled-cycles-backend OR idle-cycles-backend
● ref-cycles
[Software event] :
● cpu-clock: cpu clock的统计,每个cpu都有一个高精度定时器,单位为ms。
● task-clock :cpu clock中有task运行的统计
● cpu-migrations:进程运行过程中从一个cpu迁移到另一cpu的次数
● context-switches: 上下文切换次数,
● page-faults: 页错误的统计
● major-faults:页错误,内存页已经被swap到硬盘上,需要I/O换回
● minor-faults :页错误,内存页在物理内存中,只是没有和逻辑页进行映射
● alignment-faults: 统计内存对齐错误发生的次数, 当访问的非对齐的内存地址时,内核会进行处理,已保存不会发生问题,但会降低性能
● emulation-faults
[Hardware cache event] :
● L1-dcache-loads
● L1-dcache-load-misses
● L1-dcache-stores
● […] rNNN
[Tracepoint event] :
● probe:tcp_sendmsg
● sched:sched_process_exec
● sched:sched_process_fork
● sched:sched_process_wait
● sched:sched_wait_task
● sched:sched_process_exit
优化参考:
.html
本文发布于:2024-01-28 07:34:47,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/17063984955827.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |