plantegg

java tcp mysql performance network docker Linux

等额本息和等额本金以及提前还贷误区

1 等额本金和等额本息的差异

没有差异。等额本金=等额本息+每月提前还贷一点点()

2 为什么等额本金总利息少

因为每个月等额本金还款多,第一个月后欠本金少了,后面每个月都是这样,所还本金更多,最终总利息自然更少

其实可以用极限思维来分析他们的差异:假设等额本息和等额本金都借100万,周期一个月(没看错,一个月还清,极限假设),所以一个月后他们还钱一样多!所以这个时候没有任何区别;现在继续假设,假如还款周期是2个月,那么等额本金在第一个月还钱多,导致等额本金在第二个月的时候欠钱少了,到第二个月月底还清所有欠款的时候利息要少(本金少了)——这才是他们的差异,所以是没区别的。等额本金这两个月相当于欠了银行150万一个月(第一个月欠100万,第二个月欠50万) 应还利息就是150万 乘以 月利率;等额本息相当于欠了银行 151万(第一个月欠100万,第二个月51万,因为第一个月还钱的时候只还了49万本金),所以应还利息就是 151万 乘以 月利率;欠得多利息就多天经地义这里没有投机、没有人吃亏

再或者换个思路:第一个月借100万,月底都还清,此时利息一样多;然后再借出来99.X万,这时等额本金借得少这个X更小,所以从第二个月开始等额本金还的利息少,归根结底换利息少是因为借得少(即现实中的还本金多)

把上面的极限思维图形化就是下图中的灰色部分面积代表你的欠款(每个月的欠款累加),等额本息的方式欠得多,自然利息多:

image-20230704214346239

3 为什么总有等额本金划得来的说法

同样贷款金额下等额本金总还款额少,也就是总利息少,所以给了很多人划得来的感觉,或者给人感觉利息便宜。其实这都是错的,解释如上第二个问题

4 利息的计算方式

利息每个月计算一次,这个月所欠本金*月利率。所以利息只和你欠钱多少有关(我们假设所有人的利率都一样)。每个月的月供,都是先还掉这个月的利息,多余的再还本金

等额本金因为每个月还掉的本金多,所以计算下来每个月的利息更少

5 如何理解等额本金和等额本息

同样贷款额+利率的话等额本金开始还款一定比等额本息要多一些,那么你可以把等额本金分成两块,一块和等额本息一样,多出来的部分你把他看成这个月额外做了一次提前还款。你提前还款了后面的总利息自然也会更少一些,然后每个月的等额本息也会减少,以此类推每个月都是这样额外做一次提前还款直到最后一个月。

总结:等额本金=等额本息+提前还贷

额本金开始还款一定比等额本息要多一些,可以把等额本金分成两块,一块和等额本息一样,多出来的部分把他看成这个月额外做了一次提前还款。提前还款后总利息也会更少一些,然后每个月的等额本息也会减少,以此类推每个月都是这样额外做一次提前还款直到最后一个月。

6 提前还款划不划得来

钱在你手里没法赚到比利息更高的收益的话(99%的人属于这种)提前还贷划得来,之所以以前不建议大家提前还贷,是因为以前普遍涨薪快、通胀厉害、房价涨得块,把钱留出来继续买二套、三套更赚钱。另外钱留在手里会有主动权和应急方便

7 提前还贷会多付利息吗?

不会,见第四条利息的计算方式。担心提前还贷的时候这比贷款把后面10年的利息收走了的是脑子不好使的人。但有些银行提前还贷会有违约金

8 为什么等额本金和等额本息给了这么多人错觉

从知识的第一性出发,他们都没理解第4条,受社会普遍意识影响都预先留下了错误经验。本质就是利息只和贷款额、利率有关。

9 贷款30年和10年利率有差异吗?

没有

10贷款30年,还了6年了,然后一次还清所有欠款会多还利息吗?

不会,你前6年只是还了前6年的利息,没为之后的6年多付一分利息

11 那为什么利率不变我每个月利息越来越少

因为你欠的钱越来越少了,不是你提前还了利息,是你一直在还本金

12 网购分期合适吗?

大部分小贷公司的套路就是利用你每个月已经在还本金的差异,利息自然越来越少,然后计算一个大概5%的年息误导你,实际这种网购分期的实际利息要是他计算的2倍……好好想想

等额本金、本息都搞不清楚的人就不要去搞分期了,你的脑瓜子在这方面不好使。

聪明人只看第4条就能在大脑里得出所有结论,普通人除了要有第4条还需要去看每个月的还款额、还款额里面的本金、还款额里的利息等实践输入才能理解这个问题,这就是差异

房贷

https://zhuanlan.zhihu.com/p/161405128

提前还贷

要不要提前还贷

提前还贷划得来吗?

划不划得来要看这笔钱在你手里能否获得比房贷利息更高的收入以及你对流动资金的需要。其实万一有个啥事也可以走消费贷啥的,现在利息都很低

等额本息比等额本金提前还贷更划得来?

一样的,你这个问题等价于我欠100万,老婆欠50万,所以我提前还贷比朋友合算吗?显然你两谁提前还贷合算只和你两的贷款利率是否有差别

等额本息和等额本金利率一样的,所以没有差别

20年的房贷我已经还了15年了是不是不值得提前还贷了?

错误!利率还是那个利率,跟你还剩10年和还剩5年没关系,提前还贷都是等价的。记住有闲钱就提前还

问题的本质

搞清楚每个月的利息怎么计算出来的 利息=欠款额*月利率,月供都是把本月利息还掉多出来的还本金,也就是每个月欠款额会变少

总结

每个领域都有一些核心知识点代表着这些问题的本质,你只有把核心知识点或者说问题本质搞懂了才能化繁为简、不被忽悠!

那贷款这里的本质是什么?就是利息只和你每个月欠款以及利率有关!这简直是屁话,太简单了,但你不能理解他,就容易被套上等额本金、等额本息、提前还贷的外壳给忽悠了。

再或者说这里的本质就是:你去搞清楚每个月的还款是怎么计算的。月供=本月所欠X利率+本月还掉的本金 这是个核心公式,差别在每个月还掉的本金不一样!

就这样吧该懂的也该看懂了,看不懂的大概怎么样也看不懂!只能说是蠢,这些人肯定理科不好、逻辑不行,必定做不了程序员。

比如网上流传的如图总结的所有结论都是错的:

image-20230704215506179

实战瓶颈定位-我的MySQL为什么压不上去

背景

环境两台云上相同 128C的EC2(有点豪),一台当压力机一台当服务器,用Sysbench测试MySQL纯读场景,不存在任何修改,也就几乎没有锁

1
2
3
4
5
#uname -r
5.10.84.aarch64

Server: MySQL
Server version: 8.0.18 Source distribution

EC2机器128核,故意只给MySQLD绑定了其中的24Core,网卡32队列

1
2
3
4
5
6
7
8
9
10
11
12
#ethtool -l eth0
Channel parameters for eth0:
Pre-set maximums:
RX: 0
TX: 0
Other: 0
Combined: 32
Current hardware settings:
RX: 0
TX: 0
Other: 0
Combined: 32

img

压测过程

走同一交换机内网IP压MySQL跑不满CPU,跑压力和不跑压力时ping rtt 分别是 0.859/0.053(RTT 有增加–注意点), 此时TPS:119956.67 1000并发 RT 8.33

下图是压测时 htop 看到的MySQLD 所在EC2的 CPU使用情况,右边65-88是MySQLD进程(绿色表示us, 红色表示sys+si CPU)

image-20230511125934259

用top查看详细的每个 core 使用(只展示MySQLD使用的24core ,top 然后按1–还可以试试2/3,有惊喜)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
top - 13:49:55 up 160 days, 18:10,  3 users,  load average: 555.26, 720.12, 462.21
Tasks: 1065 total, 1 running, 499 sleeping, 0 stopped, 0 zombie
%Node1 : 10.1 us, 5.3 sy, 0.0 ni, 83.3 id, 0.0 wa, 0.0 hi, 1.3 si, 0.0 st
%Cpu64 : 29.3 us, 16.5 sy, 0.0 ni, 54.2 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu65 : 37.0 us, 18.5 sy, 0.0 ni, 26.9 id, 0.0 wa, 0.0 hi, 17.5 si, 0.0 st
%Cpu66 : 34.2 us, 17.8 sy, 0.0 ni, 47.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu67 : 26.0 us, 15.1 sy, 0.0 ni, 58.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu68 : 26.1 us, 14.8 sy, 0.0 ni, 59.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu69 : 27.2 us, 13.8 sy, 0.0 ni, 59.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu70 : 25.7 us, 11.8 sy, 0.0 ni, 62.5 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu71 : 18.3 us, 10.6 sy, 0.0 ni, 71.1 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu72 : 29.7 us, 12.6 sy, 0.0 ni, 57.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu73 : 21.2 us, 13.0 sy, 0.0 ni, 65.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu74 : 18.9 us, 10.8 sy, 0.0 ni, 70.4 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu75 : 28.9 us, 15.1 sy, 0.0 ni, 36.1 id, 0.0 wa, 0.0 hi, 19.9 si, 0.0 st
%Cpu76 : 30.3 us, 15.5 sy, 0.0 ni, 54.1 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu77 : 25.1 us, 13.2 sy, 0.0 ni, 61.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu78 : 18.2 us, 10.3 sy, 0.0 ni, 71.5 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu79 : 14.9 us, 8.8 sy, 0.0 ni, 76.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu80 : 23.4 us, 12.2 sy, 0.0 ni, 64.4 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu81 : 35.3 us, 17.6 sy, 0.0 ni, 30.2 id, 0.0 wa, 0.0 hi, 16.9 si, 0.0 st
%Cpu82 : 28.2 us, 16.1 sy, 0.0 ni, 55.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu83 : 37.5 us, 16.9 sy, 0.0 ni, 27.0 id, 0.0 wa, 0.0 hi, 18.6 si, 0.0 st
%Cpu84 : 35.4 us, 18.5 sy, 0.0 ni, 46.1 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu85 : 27.9 us, 16.8 sy, 0.0 ni, 55.2 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu86 : 28.2 us, 13.7 sy, 0.0 ni, 58.1 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu87 : 27.2 us, 11.0 sy, 0.0 ni, 61.8 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu88 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st

继续尝试用2000并发,TPS、CPU、ping rtt都和1000并发没有区别,当然按照我们以前QPS、RT理论2000并发的时候RT应该翻倍,实际确实是16.66,所以这里的问题就是翻倍的 RT哪里来的瓶颈就在哪里

也试过用两个压力机每个压力机分别用1000并发同时压,QPS一样稳定——目的快速排除压力端、链路上有瓶颈。

写到这里RT 刚好翻倍16.66=8.33*2 数字精准得好像编故事一样,不得不贴一下原始数据证实一下:

image-20230511130851332

1000 并发和2000并发时的ping RTT对比(ttl 64说明内网直达)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#ping mysqld27
PING yt27 (mysqld217) 56(84) bytes of data.
---以下是2000并发
64 bytes from mysqld27 (mysqld217): icmp_seq=1 ttl=64 time=0.867 ms
64 bytes from mysqld27 (mysqld217): icmp_seq=2 ttl=64 time=0.952 ms
64 bytes from mysqld27 (mysqld217): icmp_seq=3 ttl=64 time=0.849 ms
64 bytes from mysqld27 (mysqld217): icmp_seq=4 ttl=64 time=0.857 ms
64 bytes from mysqld27 (mysqld217): icmp_seq=5 ttl=64 time=0.987 ms
64 bytes from mysqld27 (mysqld217): icmp_seq=6 ttl=64 time=0.860 ms
64 bytes from mysqld27 (mysqld217): icmp_seq=7 ttl=64 time=0.909 ms
64 bytes from mysqld27 (mysqld217): icmp_seq=8 ttl=64 time=0.875 ms
64 bytes from mysqld27 (mysqld217): icmp_seq=9 ttl=64 time=0.979 ms
---终止压测,无无压力的rtt
64 bytes from mysqld27 (mysqld217): icmp_seq=10 ttl=64 time=0.104 ms
64 bytes from mysqld27 (mysqld217): icmp_seq=11 ttl=64 time=0.079 ms
64 bytes from mysqld27 (mysqld217): icmp_seq=12 ttl=64 time=0.075 ms
64 bytes from mysqld27 (mysqld217): icmp_seq=13 ttl=64 time=0.075 ms
64 bytes from mysqld27 (mysqld217): icmp_seq=14 ttl=64 time=0.074 ms
64 bytes from mysqld27 (mysqld217): icmp_seq=15 ttl=64 time=0.078 ms
64 bytes from mysqld27 (mysqld217): icmp_seq=16 ttl=64 time=0.075 ms
---开启1000并发时的rtt
64 bytes from mysqld27 (mysqld217): icmp_seq=17 ttl=64 time=0.872 ms
64 bytes from mysqld27 (mysqld217): icmp_seq=18 ttl=64 time=0.969 ms
64 bytes from mysqld27 (mysqld217): icmp_seq=19 ttl=64 time=0.862 ms
64 bytes from mysqld27 (mysqld217): icmp_seq=20 ttl=64 time=0.877 ms
64 bytes from mysqld27 (mysqld217): icmp_seq=21 ttl=64 time=0.961 ms
64 bytes from mysqld27 (mysqld217): icmp_seq=22 ttl=64 time=0.828 ms
64 bytes from mysqld27 (mysqld217): icmp_seq=23 ttl=64 time=0.098 ms
64 bytes from mysqld27 (mysqld217): icmp_seq=24 ttl=64 time=0.083 ms

抓包证明

在抓保证明前推荐一个工具快速绕过抓包(原理也是通过pcap lib去分析网络包,tcpdump也会调用pcap lib)

监控tcprstat,从网络层抓包来对比两个并发下的RT:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//./tcprstat -p 3307 -t 1 -n 0 -l 10.9.10.22 -f "%T\t\t%n\t\t%a\t%h\t%S\t%C\t%95M\t%95a\t%95S\t%99M\t%99a\t%99S\n"
#tcprstat -p 14822 -t 1 -n 0 -l mysqld217 -f "%T\t\t%n\t\t%a\n"
timestamp count avg
1683785023 50743 626
1683785024 120004 100
1683785025 120051 103
1683785026 120042 102
1683785027 120031 103
1683785028 120034 104
1683785029 120034 104
1683785030 55209 103 ---以上是2000并发
1683785038 0 0
1683785039 0 0
1683785040 55224 614 ---以下是1000并发
1683785041 119998 104
1683785042 120039 105
1683785043 120039 105
1683785044 120026 107
1683785045 120039 108
1683785046 120047 108
1683785047 120037 108
1683785048 120032 108
1683785049 120041 108

也就是网卡层面确认了压不上去瓶颈不在MySQL 上,加并发后网卡的RT没变(网卡RT包含MySQLD RT),因为ping RTT 在1000和2000并发也没有差异,推测交换机不是瓶颈,大概率出网卡的虚拟层面

在客户端的机器上抓包,上面我们说过了1000并发的RT是8.33毫秒:

image-20230511141508811

注意上图,我把RT排序了,明显看到5ms到17ms 中间没有这个RT范围的包,但是有很多25ms的RT,平均下来确实是8.33毫秒,留下一个疑问:RT分布不符合正态,而且中间有很大一段范围镂空了!这是不应该的。

同样我们再到MySQLD 所在机器抓包分析(注:正常路径先抓MySQLD上的包就行了):

image-20230511141925557

同样是对RT 排序了,但是慢的RT都是对端发慢了(注意最右边的select, MySQL相应是 response),同样对这个抓包求平均时间就是tcprstat 看到的103微秒,也就是0.1毫秒。如下图红框是请求,请求的间隔是11毫米,绿框是响应,响应的间隔都是0.2ms不到

image-20230513084610300

同样在2000并发时也对MySQLD所在网卡抓包对比,response 的RT 没有变化,从这里可以看出瓶颈点在sysbench 和 MySQLD 的网卡之间的链路上,似乎有限流、管控

image-20230512084446715

快速验证

到这里我们已经找到了有力的证据,RT是在离开MySQLD网卡后增加上去的,先验证下走走本机127.0.0.1快速压一把,让sysbench 跑在0-7 core上,这时可以看到MySQL跑满了CPU,下图左边1-8核是压力进程,右边65-88是业务进程,TPS:239969.91 1000并发 RT 4.16

htop状态:

image-20230511125346066

各CPU 详细分析:

  • us MySQL解析SQL、处理查询
  • si 网络软中断
  • sy OS 的sys API 消耗,一般用户进程会调用系统 API, 比如读写文件、分配内存、网络访问等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//sysbench
top - 13:44:27 up 160 days, 18:04, 3 users, load average: 792.17, 619.09, 311.58
Tasks: 1073 total, 1 running, 500 sleeping, 0 stopped, 0 zombie
%Cpu0 : 14.0 us, 29.1 sy, 0.0 ni, 33.3 id, 0.0 wa, 0.0 hi, 23.5 si, 0.0 st
%Cpu1 : 12.5 us, 33.0 sy, 0.0 ni, 33.7 id, 0.0 wa, 0.0 hi, 20.8 si, 0.0 st
%Cpu2 : 11.2 us, 32.7 sy, 0.0 ni, 34.2 id, 0.0 wa, 0.0 hi, 21.9 si, 0.0 st
%Cpu3 : 13.4 us, 31.2 sy, 0.0 ni, 34.4 id, 0.0 wa, 0.0 hi, 21.0 si, 0.0 st
%Cpu4 : 12.1 us, 31.3 sy, 0.0 ni, 34.2 id, 0.0 wa, 0.0 hi, 22.4 si, 0.0 st
%Cpu5 : 10.5 us, 31.8 sy, 0.0 ni, 33.6 id, 0.0 wa, 0.0 hi, 24.1 si, 0.0 st
%Cpu6 : 12.9 us, 31.3 sy, 0.0 ni, 34.2 id, 0.0 wa, 0.0 hi, 21.6 si, 0.0 st
%Cpu7 : 12.3 us, 31.4 sy, 0.0 ni, 34.3 id, 0.0 wa, 0.0 hi, 22.0 si, 0.0 st
%Cpu8 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st

//MySQLD
Tasks: 1073 total, 1 running, 505 sleeping, 0 stopped, 1 zombie
%Node1 : 22.6 us, 10.1 sy, 0.0 ni, 62.4 id, 0.0 wa, 0.0 hi, 4.8 si, 0.0 st
%Cpu64 : 57.9 us, 29.1 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 12.9 si, 0.0 st
%Cpu65 : 60.3 us, 26.2 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 13.6 si, 0.0 st
%Cpu66 : 57.6 us, 28.1 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 14.2 si, 0.0 st
%Cpu67 : 60.9 us, 25.5 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 13.6 si, 0.0 st
%Cpu68 : 59.9 us, 26.2 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 13.9 si, 0.0 st
%Cpu69 : 57.9 us, 27.5 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 14.6 si, 0.0 st
%Cpu70 : 61.3 us, 26.2 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 12.6 si, 0.0 st
%Cpu71 : 64.0 us, 23.4 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 12.5 si, 0.0 st
%Cpu72 : 61.3 us, 26.8 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 11.9 si, 0.0 st
%Cpu73 : 63.0 us, 22.8 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 14.2 si, 0.0 st
%Cpu74 : 61.4 us, 27.4 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 11.2 si, 0.0 st
%Cpu75 : 63.9 us, 26.5 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 9.6 si, 0.0 st
%Cpu76 : 61.3 us, 27.2 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 11.6 si, 0.0 st
%Cpu77 : 55.0 us, 30.5 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 14.6 si, 0.0 st
%Cpu78 : 60.9 us, 26.8 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 12.3 si, 0.0 st
%Cpu79 : 58.4 us, 26.7 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 14.9 si, 0.0 st
%Cpu80 : 58.7 us, 29.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 12.2 si, 0.0 st
%Cpu81 : 62.6 us, 27.2 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 10.3 si, 0.0 st
%Cpu82 : 61.9 us, 25.5 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 12.6 si, 0.0 st
%Cpu83 : 58.7 us, 27.4 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 13.9 si, 0.0 st
%Cpu84 : 59.4 us, 27.7 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 12.9 si, 0.0 st
%Cpu85 : 58.9 us, 28.5 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 12.6 si, 0.0 st
%Cpu86 : 58.4 us, 28.4 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 13.2 si, 0.0 st
%Cpu87 : 61.1 us, 27.4 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 11.6 si, 0.0 st

就以上sysbench VS MySQLD 的CPU 消耗来看,因为sysbench 处理逻辑简单,就是发SQL给MySQLD,所以 sysbench自身US很少,大部分都是调用OS的网络操作,而MySQLD有 60% CPU用于US,也就是自身业务逻辑,MySQLD收到SQL要做SQL解析,要去查找数据,这些都是用户态消耗,找到数据后走网络发给Sysbench,这部分是sy

到这里可以拿着证据去VIP通道(土豪+专业的客户得有VIP通道)找做网络管控的了,不会再有撕逼和甩锅

sysbench 结果不是正态分布

把所有请求RT 分布进行图形化,此时平均 RT 8.33,理论上是一个正态分布,下图是有限速时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
 3.615 |                                         2177
3.681 |** 14738
3.748 |******* 55690
3.816 |************* 109713
3.885 |*************** 121830
3.956 |*************** 124851
4.028 |******************* 154927
4.101 |*********************** 188826
4.176 |*************************** 226206
4.252 |************************************ 302617
4.329 |**************************************** 333310 //这里以4.329为中心符合正态
4.407 |******************************* 257048
4.487 |******************** 163100
4.569 |************ 101785
4.652 |******** 63871
4.737 |***** 43998
4.823 |***** 40854
4.910 |***** 42189
4.999 |***** 41182
5.090 |**** 35652
5.183 |**** 30343
5.277 |*** 28573
5.373 |*** 24763
5.470 |*** 22210
5.570 |*** 21808
5.671 |*** 25606
5.774 |*** 26994
5.879 |*** 24672
5.986 |*** 22087
6.095 |** 18466
6.205 |** 14822
6.318 |** 13688
6.433 |** 15381
6.550 |** 13573
6.669 |* 11325
6.790 |* 9442
6.913 |* 7412
省略一大堆
20.736 |* 11407
21.112 |* 9755
21.496 |* 8957
21.886 |* 9434
22.284 |* 9715
22.689 |** 12774
23.101 |** 17000
23.521 |*** 22937
23.948 |***** 40401
24.384 |******** 65370
24.827 |********** 82186
25.278 |********** 85505
25.737 |*********** 94347 //以25.7附近大概又是一个新正态
26.205 |********** 82958
26.681 |**** 30760
27.165 | 2222
27.659 | 69
28.162 | 16
28.673 | 15
29.194 | 20
29.725 | 17

去掉限速后平均 RT 3.26(比下图中大概的中位数2.71大了不少) 完美正态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
1.857 |**                                       19894
1.891 |*** 23569
1.925 |*** 27912
1.960 |**** 33720
1.996 |**** 39892
2.032 |***** 48289
2.069 |****** 57649
2.106 |******** 69437
2.145 |********* 83611
2.184 |*********** 99507
2.223 |************* 119275
2.264 |**************** 141013
2.305 |******************* 165450
2.347 |********************** 191778
2.389 |************************* 219706
2.433 |**************************** 250885
2.477 |******************************* 278379
2.522 |********************************** 303931
2.568 |************************************* 325777
2.615 |*************************************** 342948
2.662 |**************************************** 354029
2.710 |**************************************** 356295
2.760 |**************************************** 353068
2.810 |************************************** 341345
2.861 |************************************ 324600
2.913 |********************************** 303525
2.966 |******************************* 280221
3.020 |***************************** 255042
3.075 |************************** 230861
3.130 |*********************** 206909
3.187 |********************* 184616
3.245 |******************* 164903
3.304 |**************** 146199
3.364 |*************** 131427
3.425 |************* 117059
3.488 |************ 104954
3.551 |*********** 94404
3.615 |********* 83739
3.681 |******** 75705
3.748 |******** 67944
3.816 |******* 60727
3.885 |****** 53757
3.956 |***** 47053
4.028 |***** 42130
4.101 |**** 38069
4.176 |**** 33666
4.252 |*** 30048
4.329 |*** 26923
4.407 |*** 23886
4.487 |** 21615
4.569 |** 19897
4.652 |** 18458
4.737 |** 17729
4.823 |** 17041
4.910 |** 16011
4.999 |** 16099
5.090 |** 16090
5.183 |** 16393
5.277 |** 16729
5.373 |** 17412

用其他网络业务验证

先测试一下网络下载时的ping:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
--无流量
64 bytes from 172.16.0.205: icmp_seq=11 ttl=64 time=0.075 ms
64 bytes from 172.16.0.205: icmp_seq=12 ttl=64 time=0.080 ms
--从有网络限速的机器下载,带宽100MB
64 bytes from 172.16.0.205: icmp_seq=13 ttl=64 time=0.738 ms
64 bytes from 172.16.0.205: icmp_seq=14 ttl=64 time=0.873 ms
64 bytes from 172.16.0.205: icmp_seq=15 ttl=64 time=0.993 ms
64 bytes from 172.16.0.205: icmp_seq=16 ttl=64 time=0.859 ms
64 bytes from 172.16.0.205: icmp_seq=17 ttl=64 time=0.892 ms
64 bytes from 172.16.0.205: icmp_seq=18 ttl=64 time=0.972 ms
64 bytes from 172.16.0.205: icmp_seq=19 ttl=64 time=1.05 ms
64 bytes from 172.16.0.205: icmp_seq=20 ttl=64 time=0.973 ms
64 bytes from 172.16.0.205: icmp_seq=21 ttl=64 time=0.997 ms
64 bytes from 172.16.0.205: icmp_seq=22 ttl=64 time=0.915 ms
64 bytes from 172.16.0.205: icmp_seq=23 ttl=64 time=0.892 ms
64 bytes from 172.16.0.205: icmp_seq=24 ttl=64 time=0.960 ms
64 bytes from 172.16.0.205: icmp_seq=25 ttl=64 time=1.05 ms
64 bytes from 172.16.0.205: icmp_seq=26 ttl=64 time=0.089 ms
64 bytes from 172.16.0.205: icmp_seq=27 ttl=64 time=0.097 ms
64 bytes from 172.16.0.205: icmp_seq=28 ttl=64 time=0.081 ms
--从没有网络限速的机器下载,带宽1000MB
64 bytes from 172.16.0.205: icmp_seq=29 ttl=64 time=0.078 ms
64 bytes from 172.16.0.205: icmp_seq=30 ttl=64 time=0.077 ms
64 bytes from 172.16.0.205: icmp_seq=31 ttl=64 time=0.073 ms
64 bytes from 172.16.0.205: icmp_seq=32 ttl=64 time=0.072 ms
64 bytes from 172.16.0.205: icmp_seq=33 ttl=64 time=0.079 ms
64 bytes from 172.16.0.205: icmp_seq=34 ttl=64 time=0.074 ms
64 bytes from 172.16.0.205: icmp_seq=35 ttl=64 time=0.080 ms
64 bytes from 172.16.0.205: icmp_seq=36 ttl=64 time=0.077 ms

有限速方向,尝试了BBR和cubic 拥塞算法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#tcpperf -c 172.16.0.205 -t 100
Connected mysqld217:51254 -> 172.16.0.205:2009, congestion control: cubic
Time (s) Throughput Bitrate Cwnd Rwnd sndbuf ssthresh Retr CA Pacing rtt/var
0.000s 0.00kB/s 0.00kbps 14.3Ki 41.3Ki 85.0Ki 2048Mi 0 0 65.2Mi 427us/213
1.029s 122MB/s 975Mbps 1595Ki 1595Ki 9512Ki 576Ki 0 0 123Mi 15.2ms/8
2.005s 105MB/s 843Mbps 1595Ki 1595Ki 9512Ki 576Ki 0 0 123Mi 15.2ms/10
3.010s 105MB/s 843Mbps 1595Ki 1595Ki 9512Ki 576Ki 0 0 123Mi 15.2ms/17
4.016s 105MB/s 843Mbps 1595Ki 1595Ki 9512Ki 576Ki 0 0 123Mi 15.2ms/13
5.022s 105MB/s 843Mbps 1595Ki 1595Ki 9512Ki 576Ki 0 0 123Mi 15.2ms/14
6.028s 105MB/s 842Mbps 1595Ki 1595Ki 9512Ki 576Ki 0 0 123Mi 15.2ms/17
7.003s 105MB/s 843Mbps 1595Ki 1595Ki 9512Ki 576Ki 0 0 123Mi 15.2ms/15
8.009s 105MB/s 843Mbps 1595Ki 1595Ki 9512Ki 576Ki 0 0 123Mi 15.2ms/13
#tcpperf -c 172.16.0.205 -t 100
Connected mysqld217:51932 -> 172.16.0.205:2009, congestion control: bbr
Time (s) Throughput Bitrate Cwnd Rwnd sndbuf ssthresh Retr CA Pacing rtt/var
0.000s 0.00kB/s 0.00kbps 14.3Ki 41.3Ki 128Ki 2048Mi 0 0 98.0Mi 406us/203
1.011s 120MB/s 957Mbps 271Ki 2281Ki 10.4Mi 560Ki 2244 0 108Mi 2427us/11
2.033s 104MB/s 831Mbps 271Ki 2281Ki 10.4Mi 560Ki 1056 0 109Mi 2417us/18
3.021s 104MB/s 830Mbps 274Ki 2281Ki 10.4Mi 560Ki 1056 0 109Mi 2428us/18
4.014s 103MB/s 827Mbps 271Ki 2281Ki 10.4Mi 560Ki 1452 0 108Mi 2423us/19
5.031s 104MB/s 835Mbps 274Ki 2281Ki 10.4Mi 560Ki 660 0 80.2Mi 2435us/22
6.033s 102MB/s 818Mbps 271Ki 2272Ki 10.4Mi 560Ki 2112 0 109Mi 2426us/17
7.030s 103MB/s 823Mbps 274Ki 2281Ki 10.4Mi 560Ki 1716 0 117Mi 2430us/18
8.023s 103MB/s 826Mbps 274Ki 2281Ki 10.4Mi 560Ki 1452 0 109Mi 2428us/20
9.016s 103MB/s 827Mbps 271Ki 2281Ki 10.4Mi 560Ki 1452 0 108Mi 2423us/15

跑tcpperf触发限速时的监控(上下两个窗口是同一台机器),红色是丢包率挺高的,绿色丢包就没了,应该是拥塞算法和限速管控达成了平衡

image-20230511215940306

反过来限速被我去掉了(限速可以进出双向单独控制)

1
2
3
4
5
6
7
8
9
10
11
#tcpperf -c mysqld217 -t 1000
Connected 172.16.0.205:32186 -> mysqld217:2009, congestion control: bbr
Time (s) Throughput Bitrate Cwnd Rwnd sndbuf ssthresh Retr CA Pacing rtt/var
0.000s 0.00kB/s 0.00kbps 14.3Ki 41.3Ki 128Ki 2048Mi 0 0 100Mi 397us/198
1.001s 1107MB/s 8859Mbps 471Ki 985Ki 4641Ki 277Ki 0 0 1083Mi 390us/22
2.001s 1103MB/s 8823Mbps 465Ki 985Ki 4641Ki 277Ki 0 0 1089Mi 393us/16
3.000s 1111MB/s 8892Mbps 465Ki 985Ki 4641Ki 277Ki 0 0 1072Mi 403us/25
4.000s 1099MB/s 8789Mbps 459Ki 985Ki 4794Ki 277Ki 0 0 799Mi 399us/18
5.001s 1098MB/s 8786Mbps 459Ki 985Ki 4794Ki 277Ki 0 0 1066Mi 387us/12
6.000s 1100MB/s 8799Mbps 462Ki 974Ki 4794Ki 277Ki 0 0 1069Mi 399us/16
7.001s 1135MB/s 9078Mbps 453Ki 985Ki 4794Ki 277Ki 0 0 1059Mi 377us/19

查看限速配置如下:

1
2
3
4
5
6
{txcmbps:844.000, txckpps:120.000}

//限速解释
0-31 我猜这是网卡队列(可以修改);
txcmbps:844.000 105.5MB/s 每秒带宽105.5MB
txckpps:120.000 120K packet/s 每秒12万网络包

sysbench(主键查询-小包) 12万QPS 正好命中 txckpps:120,tcpperf (大包)稳定的105MB带宽命中txcmbps:844

去掉后长这样:

1
2
3
4
#ovsctl -n set_out_pps -v -1  //把pps限制为-1==不限制
#ovsctl set_tx -p {} -r -1; //带宽不限制

{vport: 2 {map: 0, prio:L, weight: 0}meter: {-}queue: [ 0- 31L]}

对这块网络管控感兴趣可以去了解一下 ovs 这个开源项目(open virtual switch)

去掉网卡限速后的结果

实际结构如下:

image-20230513132101185

放开所有网络控制后,1000并发压力 30万QPS,RT 3.28,此时从sysbench 以及空闲机器ping MySQLD机器的 RTT和没压力基本一致

image-20230512090205685

top状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
%Node1 : 23.4 us, 12.3 sy,  0.0 ni, 61.4 id,  0.0 wa,  0.0 hi,  3.0 si,  0.0 st
%Cpu64 : 63.2 us, 36.8 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu65 : 44.4 us, 21.9 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 33.8 si, 0.0 st
%Cpu66 : 66.6 us, 33.4 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu67 : 63.4 us, 36.6 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu68 : 64.2 us, 35.8 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu69 : 64.9 us, 35.1 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu70 : 66.6 us, 33.4 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu71 : 65.3 us, 34.7 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu72 : 67.7 us, 32.3 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu73 : 63.6 us, 36.4 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu74 : 66.7 us, 33.3 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu75 : 42.4 us, 19.9 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 37.7 si, 0.0 st
%Cpu76 : 63.9 us, 36.1 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu77 : 67.0 us, 33.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu78 : 68.3 us, 31.7 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu79 : 64.9 us, 35.1 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu80 : 65.2 us, 34.8 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu81 : 44.4 us, 21.5 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 34.1 si, 0.0 st
%Cpu82 : 63.9 us, 36.1 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu83 : 44.2 us, 23.4 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 32.3 si, 0.0 st
%Cpu84 : 65.7 us, 34.3 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu85 : 68.3 us, 31.7 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu86 : 67.5 us, 32.5 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu87 : 62.4 us, 37.6 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu88 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st

image-20230512092713141

小思考:

我们中间尝试走本机127.0.0.1 压测时QPS 是24万,比跨机器压的 30万打了8折,想想为什么?网络延时消耗完全没影响?

总结

简单可复制的证明办法:抓包,快速撕逼和分析

肯定有很多人想到:内存、磁盘、线程池、队列、网络等等原因,但是这些所有原因有一个共同的爹:RT,所有这些影响因素最后体现出来就是RT 高了,你CPU资源不够、内存慢最后总表现就是在客户端看来你的 RT 太高。

所以我们去掉这些复杂因素先在MySQLD所在EC2 的网卡上抓一个包看看RT,再对比一下1000/2000并发时抓包看到的 RT 有没有升高,如果有升高说明问题在MySQLD这端(含OS、MySQLD的问题),如果 RT 不变那么问题不在MySQLD这端,并且从EC2网卡出去都是很快的,那么问题只能是在路上或者客户端的sysbench自己慢了。

这是我们星球里说的无招胜有招–抓包大法,扯皮过程中我还没见过几个不认网络抓包的,也有那么一两个扯上是不是网卡驱动有问题,我的代码不会有问题

两个限速条件:pps 120k(每秒最多12万网络包),带宽 844mbps=105.5MB/s

Sysbench 查询都是小包,触发第一个条件,tcpperf触发第二个条件

ping ping神功失效了吗?也没有,我后来又测试了100、200并发,rtt 0.2ms和0.4ms,也就是说随着并发的增加rtt 增加到0.8ms后就不再增加了。上来1000并发已经到了天花板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
64 bytes from polardbxyt27 (mysqld217): icmp_seq=159 ttl=64 time=0.226 ms
64 bytes from polardbxyt27 (mysqld217): icmp_seq=160 ttl=64 time=0.334 ms
64 bytes from polardbxyt27 (mysqld217): icmp_seq=161 ttl=64 time=0.336 ms
64 bytes from polardbxyt27 (mysqld217): icmp_seq=162 ttl=64 time=0.213 ms
64 bytes from polardbxyt27 (mysqld217): icmp_seq=163 ttl=64 time=0.104 ms
64 bytes from polardbxyt27 (mysqld217): icmp_seq=164 ttl=64 time=0.096 ms
64 bytes from polardbxyt27 (mysqld217): icmp_seq=165 ttl=64 time=0.101 ms
64 bytes from polardbxyt27 (mysqld217): icmp_seq=166 ttl=64 time=0.116 ms
64 bytes from polardbxyt27 (mysqld217): icmp_seq=167 ttl=64 time=0.104 ms--以上 100并发,QPS 119K
64 bytes from polardbxyt27 (mysqld217): icmp_seq=168 ttl=64 time=0.093 ms
64 bytes from polardbxyt27 (mysqld217): icmp_seq=169 ttl=64 time=0.088 ms
64 bytes from polardbxyt27 (mysqld217): icmp_seq=170 ttl=64 time=0.405 ms--以下 200并发,QPS 119K
64 bytes from polardbxyt27 (mysqld217): icmp_seq=171 ttl=64 time=0.419 ms
64 bytes from polardbxyt27 (mysqld217): icmp_seq=172 ttl=64 time=0.386 ms
64 bytes from polardbxyt27 (mysqld217): icmp_seq=173 ttl=64 time=0.474 ms
64 bytes from polardbxyt27 (mysqld217): icmp_seq=174 ttl=64 time=0.462 ms
64 bytes from polardbxyt27 (mysqld217): icmp_seq=175 ttl=64 time=0.410 ms

Nginx reuseport 导致偶发性卡顿

by @橘橘球

背景

从2018年开始,我们有个业务陆续接到反馈 Nginx 线上集群经常出现不响应或者偶发性的“超慢”请求。这种卡顿每天都有少量出现。而只有多个集群中的一个出现,其他压力更大的集群皆未出现。
业务结构比较简单:LVS->Nginx->后端,如图
image-20230607103449616

一些观察到的现象:

  • 出问题前不久升级 Nginx 配置,打开了 reuseport 功能
  • 在压力大的后端(upstream)服务环境不容易出现,后端压力轻对应的Nginx卡顿概率更高
  • 关闭 reuseport 后 问题少了很多
  • 失败的请求响应时间都是 0ms(Nginx日志不靠谱了)
  • 从 Nginx 日志上看,所有失败的健康检查请求都是0ms 的499 错误码(健康检查设置超时是2秒),但实际出问题的时候有5s-2分钟没有任何日志输出(Nginx卡了这么久)要么是Nginx卡住没去accept,要么是accept了没响应
  • 所有超时来自同一个worker(一个Nginx服务一般按照机器核数开启多个worker)

并且已知,卡顿的原因是打开 reuseport 后,新进来的请求可以由内核 hash 派发给一个 Nginx woker ,避免了锁争抢以及惊群。但如果网络条件足够好,压力足够低,Nginx worker 一直来不及读完 receive buffer 中的内容时,就无法切换并处理其他的 request,于是在新请求的客户端会观测不间断的卡顿,而压力大的后端由于网络传输慢,经常卡顿,Nginx worker 反而有时间能处理别的请求。在调小 receive buffer 人为制造卡顿后该问题得以解决。

目标

由于所述场景比较复杂,缺乏直接证据,打算通过构造一个较简单的环境来复现这个问题,并且在这个过程中抓包、观测Nginx worker的具体行为,验证这个假设。

术语

快连接和慢连接

  • 快连接:通常是传输时间短、传输量小的连接,耗时通常是ms级别
  • 慢连接:通常是传输时间长、传输量大的连接,可以维持传输状态一段时间(如30s, 1min)

在本次场景复现过程中,这两种连接都是短连接,每次请求开始前都需要三次握手建立连接,结束后都需要四次挥手销毁连接

Epoll

Nginx使用了epoll模型,epoll 是多路复用的一种实现。在多路复用的场景下,一个task(process)会批量处理多个socket,哪个来了数据就去读那个。这就意味着要公平对待所有这些socket,不能阻塞在任何socket的”数据读”上,也就是说不能在阻塞模式下针对任何socket调用recv/recvfrom。

epoll 每次循环为O(1) 操作,循环前会得到一个就绪队列,其中包含所有已经准备好的 socket stream(有数据可读),不需要循环全部 socket stream 读取数据,在循环后会将被读取数据的 stream 重新放回睡眠队列。睡眠队列中的 socket stream 有数据可读时,再唤醒加入到 就绪队列中。

epoll 伪代码 (不包含唤醒、睡眠)

1
2
3
4
5
6
while(true) {  
streamArr = getEpollReadyStream(); // 找到准备好的stream
for(Stream i: streamArr) { // 循环准备好的stream
doSomething();
}
}

reuseport与惊群

Nginx reuseport 选项解决惊群的问题:在 TCP 多进程/线程场景中(B 图),服务端如果所有新连接只保存在一个 listen socket 的全连接队列中,那么多个进程/线程去这个队列里获取(accept)新的连接,势必会出现多个进程/线程对一个公共资源的争抢,争抢过程中,大量资源的损耗,也就会发生惊群现象。
img
而开启reuseport后(C 图),有多个 listener 共同 bind/listen 相同的 IP/PORT,也就是说每个进程/线程有一个独立的 listener,相当于每个进程/线程独享一个 listener 的全连接队列,新的连接请求由内核hash分配,不需要多个进程/线程竞争某个公共资源,能充分利用多核,减少竞争的资源消耗,效率自然提高了。

但同时也是由于这个分配机制,避免了上下文切换,在服务压力不大,网络情况足够好的情况下,进程/线程更有可能专注于持续读取某个慢连接数据而忽视快连接建立的请求,从而造成快连接方卡顿。

复现过程

思路

  1. 整体的架构是N个client->1个Nginx->N个server。因为卡顿原因和reuseport机制有关,和server数量无关,server数量设为任意数字都能复现,这里为了方便设成1。client数量设为2,为了将快连接和慢连接区分开便于抓包观测
  2. 用慢连接制造卡顿环境,用快连接观测卡顿。在快连接客户端进行观测和抓包
  3. 进程数量要足够少,使得同一个 worker 有几率分配到多个连接 worker_processes 2
  4. 连接数目要足够多,慢连接数目>=进程数量,使得快连接在分配时,有一定概率分配到一个正在处理慢连接的worker上
  5. reuseport: 这个配置要开启,卡顿现象才能观测到。listen 8000 reuseport

环境

1
2
3
4
5
linux kernal version: 6.1  
linux image: amazon/al2023-ami-2023.0.20230419.0-kernel-6.1-x86_64
instance type:
1X AWS t2.micro (1 vCPU, 1GiB RAM) – Nginx client(fast request)
3X AWS t3.micro (2 vCPU, 1GiB RAM) – Http server, Nginx server, Nginx client(slow request)

复现操作

  1. 在server instance上放置一个 2GiB 大文件(0000000000000000.data)和一个 3MiB 小文件(server.pcap),并开启一个http server
1
nohup python -m http.server 8000
  1. 在Nginx instance上安装、配置好Nginx,并启动Nginx (注意要绑核!)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# install
sudo yum install nginx
# config (/etc/nginx/nginx.conf)
user nginx;
worker_processes 2;
error_log /var/log/nginx/error.log notice;
pid /run/nginx.pid;

include /usr/share/nginx/modules/*.conf;

events {
worker_connections 1024;
}

http {
log_format main '$remote_addr [$time_local] "$request" '
'status=$status body_bytes_sent=$body_bytes_sent '
'rt=$request_time uct="$upstream_connect_time" uht="$upstream_header_time" urt="$upstream_response_time"';

access_log /var/log/nginx/access.log main;

sendfile on;
tcp_nopush on;
keepalive_timeout 60;
types_hash_max_size 4096;

include /etc/nginx/mime.types;
default_type application/octet-stream;

# Load modular configuration files from the /etc/nginx/conf.d directory.
# See http://nginx.org/en/docs/ngx_core_module.html#include
# for more information.
include /etc/nginx/conf.d/*.conf;

server {
listen 8000 reuseport;
server_name server1;
root /usr/share/nginx/html;

# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;

location / {
proxy_pass http://172.31.86.252:8000; # server ip
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}

error_page 404 /404.html;
location = /404.html {
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
}
# start nginx
sudo taskset -c 0 nginx
  1. 启动慢连接client,开启4个下载进程并计时,测试脚本在此
  2. 启动快连接client,开启1个下载进程并计时,抓包,测试脚本在此
    需要注意的是此处使用了curl –max-time 1,意味着即使1s内文件没有下载完,也会自动终止。
  3. 进入Nginx instance观察access.log
  4. 关掉reuseport或者调小recv buffer大小,重试一次

结果

ip maping:

1
2
3
4
172.31.86.252: http server
172.31.89.152: nginx server
172.31.91.109: 快连接 client
172.31.92.10: 慢连接 client
  1. 快连接client端:下载同一个小文件的下载时长有快有慢,方差很大,完整日志在此
1
2
3
4
5
6
7
8
[2023-05-31 08:27:32,127] runtime=1010
[2023-05-31 08:27:33,140] runtime=1009
[2023-05-31 08:27:34,152] runtime=38
[2023-05-31 08:27:34,192] runtime=1011
[2023-05-31 08:27:35,205] runtime=37
[2023-05-31 08:27:35,245] runtime=1008
[2023-05-31 08:27:36,256] runtime=57
[2023-05-31 08:27:36,315] runtime=1011
  1. 快连接client:无论耗时长短,抓包结果都显示存在不同程度卡顿,抓包文件在此 耗时长的下载过程
    img
    耗时短的下载过程
    img

  2. Nginx access.log 存在大量未下载完的200请求,和少量499请求,且499请求的耗时为0,access.log文件在此
    卡顿的日志建立连接时长(utc)在0.3-0.4ms左右,超过1s的就出现499了

1
2
3
4
5
6
7
8
172.31.91.109 [31/May/2023:08:27:49 +0000] "GET /server.pcap HTTP/1.1" status=200 body_bytes_sent=102195 rt=0.790 uct="0.413" uht="0.592" urt="0.791"
172.31.91.109 [31/May/2023:08:27:50 +0000] "GET /server.pcap HTTP/1.1" status=200 body_bytes_sent=3602590 rt=0.058 uct="0.000" uht="0.002" urt="0.053"
172.31.91.109 [31/May/2023:08:27:51 +0000] "GET /server.pcap HTTP/1.1" status=499 body_bytes_sent=0 rt=0.000 uct="-" uht="-" urt="0.000"
172.31.91.109 [31/May/2023:08:27:51 +0000] "GET /server.pcap HTTP/1.1" status=200 body_bytes_sent=102195 rt=0.763 uct="0.400" uht="0.580" urt="0.763"
172.31.91.109 [31/May/2023:08:27:52 +0000] "GET /server.pcap HTTP/1.1" status=200 body_bytes_sent=102195 rt=0.767 uct="0.480" uht="0.768" urt="0.768"
172.31.91.109 [31/May/2023:08:27:53 +0000] "GET /server.pcap HTTP/1.1" status=200 body_bytes_sent=580007 rt=0.773 uct="0.330" uht="0.431" urt="0.773"
172.31.91.109 [31/May/2023:08:27:55 +0000] "GET /server.pcap HTTP/1.1" status=499 body_bytes_sent=0 rt=0.000 uct="-" uht="-" urt="0.000"
172.31.91.109 [31/May/2023:08:27:55 +0000] "GET /server.pcap HTTP/1.1" status=499 body_bytes_sent=0 rt=0.000 uct="-" uht="-" urt="0.000"

下载中途被关闭的连接(200),可以观测到Nginx server在客户端已经请求FIN并被ACK之后仍然在发送一些网络数据包,客户端非常迷惑,向Nginx发送RST
img
未和Nginx建立连接就被关闭的连接(499),可以观测到连接始终没有被建立,在等待1s后客户端超时,主动请求关连接
img

  1. 限制Nginx server所在的instance的recv buffer大小,重新进行实验,可以观测到仍然有少量停顿,但整体耗时好了很多,不再有长达1s的卡顿,也不再有RST,完整日志在此
1
sysctl -w net.ipv4.tcp_rmem="40960 40960 40960"

client runtime log: 耗时稳定在50-100ms,比无慢连接、纯跑快连接时要大一倍(25-50ms)

1
2
3
4
5
6
7
8
9
10
[2023-06-05 06:13:22,791] runtime=120
[2023-06-05 06:13:22,913] runtime=82
[2023-06-05 06:13:22,997] runtime=54
[2023-06-05 06:13:23,054] runtime=61
[2023-06-05 06:13:23,118] runtime=109
[2023-06-05 06:13:23,229] runtime=58
[2023-06-05 06:13:23,290] runtime=55
[2023-06-05 06:13:23,347] runtime=79
[2023-06-05 06:13:23,429] runtime=65
[2023-06-05 06:13:23,497] runtime=53

client 抓包结果:
img
Nginx access.log: 都发完了,而且发得很流畅,建立连接时间(utc)非常短

1
2
3
4
5
6
7
8
172.31.91.109 [05/Jun/2023:06:13:22 +0000] "GET /server.pcap HTTP/1.1" status=200 body_bytes_sent=3602590 rt=0.101 uct="0.001" uht="0.004" urt="0.101"
172.31.91.109 [05/Jun/2023:06:13:22 +0000] "GET /server.pcap HTTP/1.1" status=200 body_bytes_sent=3602590 rt=0.064 uct="0.001" uht="0.002" urt="0.064"
172.31.91.109 [05/Jun/2023:06:13:23 +0000] "GET /server.pcap HTTP/1.1" status=200 body_bytes_sent=3602590 rt=0.044 uct="0.000" uht="0.001" urt="0.044"
172.31.91.109 [05/Jun/2023:06:13:23 +0000] "GET /server.pcap HTTP/1.1" status=200 body_bytes_sent=3602590 rt=0.047 uct="0.000" uht="0.001" urt="0.047"
172.31.91.109 [05/Jun/2023:06:13:23 +0000] "GET /server.pcap HTTP/1.1" status=200 body_bytes_sent=3602590 rt=0.100 uct="0.000" uht="0.001" urt="0.099"
172.31.91.109 [05/Jun/2023:06:13:23 +0000] "GET /server.pcap HTTP/1.1" status=200 body_bytes_sent=3602590 rt=0.047 uct="0.000" uht="0.001" urt="0.047"
172.31.91.109 [05/Jun/2023:06:13:23 +0000] "GET /server.pcap HTTP/1.1" status=200 body_bytes_sent=3602590 rt=0.045 uct="0.001" uht="0.002" urt="0.045"
172.31.91.109 [05/Jun/2023:06:13:23 +0000] "GET /server.pcap HTTP/1.1" status=200 body_bytes_sent=3602590 rt=0.066 uct="0.000" uht="0.002" urt="0.066"

对于慢连接大文件下载时长略有影响:46s (无限制) vs 53s (有限制)

  1. 关闭nginx reuseport

卡顿依然大量存在,但大多以连接能够建立但是下载不完的形式(200)出现,499较少,并且存在惊群现象,完整日志在此

1
2
server {
listen 8000;

client runtime log:存在卡顿,和benchmark没有区别

1
2
3
4
5
6
7
8
9
[2023-06-05 06:38:06,682] runtime=1008
[2023-06-05 06:38:07,692] runtime=1008
[2023-06-05 06:38:08,703] runtime=220
[2023-06-05 06:38:08,926] runtime=112
[2023-06-05 06:38:09,040] runtime=60
[2023-06-05 06:38:09,103] runtime=865
[2023-06-05 06:38:09,970] runtime=1009
[2023-06-05 06:38:10,982] runtime=1008
[2023-06-05 06:38:11,992] runtime=1009

client抓包结果:存在卡顿,存在RST,和benchmark没有区别
img
img
access.log:卡顿的日志连接时间比benchmark略短,在0.2-0.3s左右,出现499的情况少了但是依然会有

1
2
3
4
5
6
7
172.31.91.109 [05/Jun/2023:06:38:02 +0000] "GET /server.pcap HTTP/1.1" status=200 body_bytes_sent=204595 rt=0.844 uct="0.362" uht="0.539" urt="0.845"
172.31.91.109 [05/Jun/2023:06:38:03 +0000] "GET /server.pcap HTTP/1.1" status=200 body_bytes_sent=204595 rt=0.907 uct="0.334" uht="0.476" urt="0.906"
172.31.91.109 [05/Jun/2023:06:38:04 +0000] "GET /server.pcap HTTP/1.1" status=200 body_bytes_sent=543900 rt=0.836 uct="0.319" uht="0.504" urt="0.836"
172.31.91.109 [05/Jun/2023:06:38:05 +0000] "GET /server.pcap HTTP/1.1" status=200 body_bytes_sent=204595 rt=0.831 uct="0.161" uht="0.480" urt="0.830"
172.31.91.109 [05/Jun/2023:06:38:06 +0000] "GET /server.pcap HTTP/1.1" status=200 body_bytes_sent=552849 rt=0.820 uct="0.180" uht="0.329" urt="0.819"
172.31.91.109 [05/Jun/2023:06:38:07 +0000] "GET /server.pcap HTTP/1.1" status=200 body_bytes_sent=204595 rt=0.800 uct="0.122" uht="0.462" urt="0.800"
172.31.91.109 [05/Jun/2023:06:38:08 +0000] "GET /server.pcap HTTP/1.1" status=200 body_bytes_sent=543900 rt=0.871 uct="0.251" uht="0.380" urt="0.871"

存在惊群现象,以下是Nginx worker进程的cpu使用率和上下文切换频率对比

1
2
# 每5s输出一次统计结果
pidstat -w -u 5

两者的cpu使用率和上下文切换频率差不多,但关闭reuseport后花在wait上的cpu时间明显增加(1.3-1.6% vs 2.8-2.9%),这就是惊群带来的性能损耗。原始文件:开启reuseport关闭reuseport

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 开启reuseport
Average: UID PID %usr %system %guest %wait %CPU CPU Command
Average: 992 2590 1.77 9.57 0.00 1.25 11.35 - nginx
Average: 992 2591 1.37 5.75 0.00 1.62 7.12 - nginx

Average: UID PID cswch/s nvcswch/s Command
Average: 992 2590 179.18 49.64 nginx
Average: 992 2591 342.51 9.87 nginx

# 关闭reuseport
Average: UID PID %usr %system %guest %wait %CPU CPU Command
Average: 992 2788 1.02 8.02 0.00 2.80 9.04 - nginx
Average: 992 2789 0.92 9.07 0.00 2.97 9.99 - nginx

Average: UID PID cswch/s nvcswch/s Command
Average: 992 2788 159.06 28.68 nginx
Average: 992 2789 250.26 22.93 nginx

惊群对于慢连接大文件下载时长略有影响:46s (开reuseport) vs 53s (关reuseport)

  1. 其他的观察

最初复现的场景是所有的instance都是t2.micro,但开2个慢连接进程时比较难复现,开4个进程又太容易触发限流,所以开始考虑用大一些又没那么容易限流的instance型号。考虑到aws是通过间歇掉包来限速的,慢连接进程数量并非越大越好,引发限速后反而会造成网络连接不畅,造成慢连接卡顿,使得快连接卡顿反而不容易观测。最后选择将慢连接全链路改成t3.micro,结果好复现多了.

可以观察到有一些access.log上499的连接,各种计时也是0,这其实是因为计时也是通过worker进行的,只有进行epoll和上下文切换才会在日志上打入时间信息,worker如果一直不进行切换,那么计时就会失真,就会看到日志上计时也是0的现象。

结论

  1. reuseport是Nginx避免惊群的优秀feature,应该开启
  2. 开启reuseport后如果网络情况非常好且后端服务压力不大,且存在大量慢连接时,会造成快连接卡顿,这是Nginx的worker-epoll架构带来的,原因是recv buffer一直读不完,NGINX采用的epoll ET 触发模式在这种情况下一直无法触发暂停导致worker无法响应其它请求
  3. 减小recv buffer通过人为制造卡顿,提供了epoll ET切换连接的条件,可以很大程度上缓解这个问题,同时带来的负面效果是有一定性能损耗。但卡顿无法根除,只能控制在可接受范围内

参考资料

  1. Nginx 惊群 – wenfh2020
  2. Nginx reuseport – wenfh2020
  3. Epoll – wenfh2020
  4. 上下文切换的案例以及CPU使用率 – cnhkzyy

MySQL线程池卡顿重现

by @wych42

起因

为了激励大家多动手少空想,我在推特发起了白嫖我的知识星球活动

白嫖我星球的机会来了,总有人说贵、没有优惠券,这次直接来一个完全100%免费的机会,要求: 在MySQL的基础上重现某个线程池卡的现象,给出可复制的重现过程。就是因为某个线程池满了导致落到这个池里的查询一定都慢,否则都快。 不愿意出钱就动手吧

参考现象:https://plantegg.github.io/2020/11/17/MySQL%E7%BA%BF%E7%A8%8B%E6%B1%A0%E5%AF%BC%E8%87%B4%E7%9A%84%E5%BB%B6%E6%97%B6%E5%8D%A1%E9%A1%BF%E6%8E%92%E6%9F%A5/


感谢推友王鱼翅同学,以下是他的教科书级的细致重现,你复制粘贴就能和他一样重现了

这个案例的重要性

这个现象对应我们年度四大案例之一,如下图左下角

image-20230517082106489

重现后请思考:

  1. MySQL为什么要将多个线程分成小池子,小池子肯定容易局部资源不足
  2. Nginx 一个连接固定在一个worker上,那么同样多个Worker也会有不均衡(有的worker很闲,有的很卡)
  3. 动手实验一下将多个小池子改成一个大线程池会怎么样
  4. Java ConcurrentHashMap为什么能够高性能

由 @wych42 重现

原因分析

根据 USE 分析套路。看到服务端执行快,但是整体RT慢的现象,大概率是中间哪个位置有排队。根据文章里的描述,原因是在thread pool group中出现了排队。

排队的主要原因是服务端拒绝创建新的thread(worker),导致新进来的SQL需要等待前面的执行完成。那么就需要重点分析thread(worker)的创建过程和约束条件。根据文章和文档的说明,重点在thread_pool_size, thread_pool_oversubscribe, thread_pool_max_threads, thread_pool_stall_limit这几个参数上。

跟据文档分析和实际执行结果,这几个参数在MySQL不同的发型版中的行为逻辑是不尽相同的。核心差异在对创建新worker的限制条件上,后面复现也会根据两个发型版的特点分别执行。

mariadb

文档

  • 通常情况下,新worker由listener worker创建
  • 当timer worker检测到thread group 有stall时,可能会选择创建一个新的worker
  • worker的数量上限由thread_pool_max_threads限制
  • thread_pool_oversubscribe约束的是被额外创建出来的worker,在执行完任务后,最多能保留active状态的数量

    To clarify, the thread_pool_oversubscribe system variable does not play any part in the creation of new worker threads. The thread_pool_oversubscribe system variable is only used to determine how many worker threads should remain active in a thread group, once a thread group is already oversubscribed due to stalls.

percona

文档

percona的行为更符合原文章里的说明:

  • 如果线程执行超过时间 thread_pool_stall_limit 的值,会被任务stalled,会创建一个新的线程执行排队的任务
  • thread_pool_oversubscribe 约束了每个thread group的线程数上限。

尝试复现

思路

并发向DB发起请求,观察客户端耗时,这些请求应当符合这些条件:

  • 可控的并发数量:以对比数据库服务端不同参数值的情况
  • 有稳定的、相同的服务端执行耗时:以对比客户端在不同场景下的耗时
  • 对服务端的硬件压力较小:避免因为并发不同时,因IO、CPU资源占用,影响服务端执行耗时

综合考虑使用 select sleep(2); 作为测试SQL。并发控制使用下面的golang代码实现。

再控制数据库服务端参数,运行同一个并发程序进行对比,mariadb和percona分析执行运行过程:

复现执行

mariadb

由上面分析可以,mariadb 中造成排队的约束是thread_pool_max_threads。

执行方案

  • DB配置
1
2
3
4
| thread_pool_max_threads                 | 6               |
| thread_pool_oversubscribe | 1 |
| thread_pool_size | 1 |
| thread_pool_stall_limit | 500 |
  • 执行SQL select sleep(2)
  • 执行并发:8

预期结果: 6个SQL执行的客户端观察耗时为2s;2个SQL为4s

若调整 thread_pool_max_threads=8,则8个SQL的执行客户端观察耗时都为2s

执行结果

  1. thread_pool_max_threads=6;concurrency=8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
go run ./main.go
2023/05/16 13:34:51 starting taskId:task_3
2023/05/16 13:34:51 starting taskId:task_1
2023/05/16 13:34:51 starting taskId:task_6
2023/05/16 13:34:51 starting taskId:task_4
2023/05/16 13:34:51 starting taskId:task_0
2023/05/16 13:34:51 starting taskId:task_7
2023/05/16 13:34:51 starting taskId:task_2
2023/05/16 13:34:51 starting taskId:task_5
2023/05/16 13:34:53 taskId:task_0 exec cost : 2.021305666s
2023/05/16 13:34:53 taskId:task_6 exec cost : 2.021421041s
2023/05/16 13:34:53 taskId:task_3 exec cost : 2.021258917s
2023/05/16 13:34:53 taskId:task_2 exec cost : 2.021275458s
2023/05/16 13:34:53 taskId:task_4 exec cost : 2.021254083s
2023/05/16 13:34:53 taskId:task_7 exec cost : 2.02146725s
2023/05/16 13:34:55 taskId:task_5 exec cost : 4.021478584s
2023/05/16 13:34:55 taskId:task_1 exec cost : 4.02192s
  1. thread_pool_max_threads=8;concurrency=8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
go run ./main.go
2023/05/16 13:36:17 starting taskId:task_7
2023/05/16 13:36:17 starting taskId:task_3
2023/05/16 13:36:17 starting taskId:task_1
2023/05/16 13:36:17 starting taskId:task_5
2023/05/16 13:36:17 starting taskId:task_0
2023/05/16 13:36:17 starting taskId:task_6
2023/05/16 13:36:17 starting taskId:task_4
2023/05/16 13:36:17 starting taskId:task_2
2023/05/16 13:36:19 taskId:task_6 exec cost : 2.045480167s
2023/05/16 13:36:19 taskId:task_2 exec cost : 2.045405667s
2023/05/16 13:36:19 taskId:task_7 exec cost : 2.045507334s
2023/05/16 13:36:19 taskId:task_1 exec cost : 2.04553075s
2023/05/16 13:36:19 taskId:task_3 exec cost : 2.04554975s
2023/05/16 13:36:19 taskId:task_0 exec cost : 2.045697375s
2023/05/16 13:36:19 taskId:task_4 exec cost : 2.046417375s
2023/05/16 13:36:19 taskId:task_5 exec cost : 2.046453792s

均符合预期。

percona

由上面分析可以,percona中造成排队的约束是thread_pool_oversubscribe。

执行方案

  • DB配置: thread_pool_max_threads设置一个较大的值,以排除影响。
1
2
3
4
| thread_pool_max_threads                 | 1000               |
| thread_pool_oversubscribe | 1 |
| thread_pool_size | 1 |
| thread_pool_stall_limit | 500 |
  • 执行SQL select sleep(2)
  • 执行并发:8

预期结果: 客户端观察到的耗时分四个批次输出,每个批次2个SQL,耗时分别为2s,4s,6s,8s.

若调整 thread_pool_oversubscribe=2,则三个批次输出,分别为3条SQL耗时均为2s,3条SQL耗时均为4s,2条SQL耗时均为6s

执行结果

  1. thread_pool_oversubscribe=1,concurrency=8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
go run ./main.go
2023/05/16 13:39:35 starting taskId:task_2
2023/05/16 13:39:35 starting taskId:task_4
2023/05/16 13:39:35 starting taskId:task_3
2023/05/16 13:39:35 starting taskId:task_5
2023/05/16 13:39:35 starting taskId:task_6
2023/05/16 13:39:35 starting taskId:task_0
2023/05/16 13:39:35 starting taskId:task_1
2023/05/16 13:39:35 starting taskId:task_7
2023/05/16 13:39:37 taskId:task_7 exec cost : 2.063547416s
2023/05/16 13:39:37 taskId:task_0 exec cost : 2.064091541s
2023/05/16 13:39:39 taskId:task_5 exec cost : 4.06672125s
2023/05/16 13:39:39 taskId:task_6 exec cost : 4.066822583s
2023/05/16 13:39:41 taskId:task_3 exec cost : 6.067720292s
2023/05/16 13:39:41 taskId:task_2 exec cost : 6.069995s
2023/05/16 13:39:43 taskId:task_4 exec cost : 8.069296042s
2023/05/16 13:39:43 taskId:task_1 exec cost : 8.071391709s
  1. thread_pool_oversubscribe=2,concurrency=8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
go run ./main.go
2023/05/16 13:41:02 starting taskId:task_7
2023/05/16 13:41:02 starting taskId:task_1
2023/05/16 13:41:02 starting taskId:task_3
2023/05/16 13:41:02 starting taskId:task_2
2023/05/16 13:41:02 starting taskId:task_5
2023/05/16 13:41:02 starting taskId:task_6
2023/05/16 13:41:02 starting taskId:task_4
2023/05/16 13:41:02 starting taskId:task_0
2023/05/16 13:41:04 taskId:task_1 exec cost : 2.057093667s
2023/05/16 13:41:04 taskId:task_3 exec cost : 2.057156334s
2023/05/16 13:41:04 taskId:task_5 exec cost : 2.057170667s
2023/05/16 13:41:06 taskId:task_6 exec cost : 4.066917041s
2023/05/16 13:41:06 taskId:task_7 exec cost : 4.066944125s
2023/05/16 13:41:06 taskId:task_2 exec cost : 4.066976875s
2023/05/16 13:41:08 taskId:task_4 exec cost : 6.070653125s
2023/05/16 13:41:08 taskId:task_0 exec cost : 6.070612083s

均符合预期。

real-world 模拟(percona)版本

现实场景中,很少会有大批量的2s在SQL在生产环境执行(限互联网业务),上述的分析过程能否在真实场景中验证呢?尝试用一个执行200ms的SQL来模拟下:

  • DB配置: thread_pool_max_threads设置一个较大的值,以排除影响。
1
2
3
4
| thread_pool_max_threads                 | 1000               |
| thread_pool_oversubscribe | 1 |
| thread_pool_size | 1 |
| thread_pool_stall_limit | 500 |
  • 执行SQL select sleep(0.2)
  • 执行并发:10

从执行结果中可以看到,只有第一条SQL按照预期的时间执行完成了。
从抓包结果中可以看到,所有SQL几乎是同时发出。观察最慢的一条SQL,但是从客户端发包到服务端响应包发出的耗时,与客户端观察到的耗时也能对应上。

可以验证上述分析过程。

1
2
3
4
5
6
7
8
9
10
2023/05/16 14:47:47 taskId:task_1 exec cost : 239.34925ms
2023/05/16 14:47:47 taskId:task_9 exec cost : 239.560833ms
2023/05/16 14:47:47 taskId:task_5 exec cost : 453.795084ms
2023/05/16 14:47:47 taskId:task_3 exec cost : 458.0005ms
2023/05/16 14:47:47 taskId:task_6 exec cost : 659.441541ms
2023/05/16 14:47:47 taskId:task_8 exec cost : 659.660917ms
2023/05/16 14:47:47 taskId:task_0 exec cost : 862.526375ms
2023/05/16 14:47:47 taskId:task_7 exec cost : 864.450042ms
2023/05/16 14:47:48 taskId:task_2 exec cost : 1.063766875s
2023/05/16 14:47:48 taskId:task_4 exec cost : 1.066266041s

send_sql

response_delay

复现文章中部分线程池卡的现象

配置两个线程池,在其中一个线程池上,通过select sleep()较长时间模拟线程池被慢SQL或者大量任务堵塞的情况,具体配置方案如下:

  • thread_pool_size=2: 保留两个线程池,验证一个卡顿,一个不卡
  • thread_pool_oversubscribe=1: 允许多创建一个线程,每个线程池中可以同时运行1+1=2个线程
  • thread_pool_max_threads=2: 每个线程池的线程数量上限,为thread_pool_oversubscribe的配置约束加一个硬限制,每个线程池中最多允许运行2个线程

操作步骤如下:

  • 通过mysql client在终端发起链接,通过 show processlist语句获取到链接Id, 该链接会分配到 id%2 的线程池中。
  • 用偶数id的链接验证卡顿线程池,用奇数id的链接验证不卡的线程池,链接情况如下:
1
2
3
4
5
6
7
8
9
10
  show processlist;
+-----+-----------------+----------------+------+---------+-------+------------------------+------------------+----------+-----------+---------------+
| Id | User | Host | db | Command | Time | State | Info | Time_ms | Rows_sent | Rows_examined |
+-----+-----------------+----------------+------+---------+-------+------------------------+------------------+----------+-----------+---------------+
| 5 | event_scheduler | localhost | NULL | Daemon | 23664 | Waiting on empty queue | NULL | 23663650 | 0 | 0 |
| 404 | root | _gateway:51310 | NULL | Sleep | 7256 | | NULL | 7256057 | 1 | 1 |
| 405 | root | _gateway:48860 | NULL | Sleep | 7295 | | NULL | 7295342 | 1 | 1 |
| 406 | root | _gateway:41144 | NULL | Sleep | 7254 | | NULL | 7254236 | 1 | 1 |
| 410 | root | _gateway:46794 | NULL | Sleep | 7196 | | NULL | 7196042 | 1 | 1 |
+-----+-----------------+----------------+------+---------+-------+------------------------+------------------+----------+-----------+---------------+
  • 在 id=404, id=406的链接上,执行 select sleep(30),再到 id=410 的链接上执行 select 1,预计 select 'slow'会直接卡顿约30s再执行完成。
  • 同时,在id=405的链接上,反复执行 select 'fast',都可以很快执行完成。

执行结果:

  • id=410 上的语句执行约25s返回结果(终端操作手速影响导致了5s误差),语句执行时数据库实例输出报错日志,提示线程不足:

    2023-05-16T11:27:09.997916Z 406 [ERROR] [MY-000000] [Server] Threadpool could not create additional thread to handle queries, because the number of allowed threads was reached. Increasing ‘thread_pool_max_threads’ parameter can help in this situation. If ‘admin_port’ parameter is set, you can still connect to the database with superuser account (it must be TCP connection using admin_port as TCP port) and troubleshoot the situation. A likely cause of pool blocks are clients that lock resources for long time. ‘show processlist’ or ‘show engine innodb status’ can give additional hints.

  • id=405链接上的执行都行快。可参考下面抓包截图。

抓包结果:

id=410 上的阻塞SQL,可以看到:

  1. 三条语句在3s内接连发出,但是由于线程池阻塞, select 'slow'原本应该很快返回结果,被卡住
  2. 在30s时,第一个select sleep(30)语句执行完成,空出的线程立刻执行了 select 'slow'并返回结果
    slow query

id=405链接上的执行结果可以看到,每条语句执行都很快。
fast query

参数合理值/已知参数的容量评估

percona 的默认配置中,thread_pool_size=核心数,thread_pool_oversubscribe=3.假设在一台 16core 的服务器上运行percona,默认配置下最多可以有 16*(1+3)=64个worker同时接受请求。也就是最大可并行处理的SQL数量为 64 个。

假设同时有65个执行耗时为10ms的SQL到达服务端,理论上,会有一个进入排队。排查网络、解析等阶段,在客户端观察到的64个SQL执行耗时10ms,1个SQL执行耗时约20ms。这也会导致耗时监控中出现毛刺、耗时分布不符合正态分布。

反之,根据硬件配置、查询的量、耗时等特点,也可以推算合理的参数值。

附录

过程回顾

阶段一 确定原因

看到文章时,基本确认问题根源在执行线程(worker)不够,导致排队,出于以下几点分析:

  • 开头提到的 USE 分析套路,结合排查过类似问题(非SQL)的经验
  • 看到文章作者调大thread_pool_oversubscribe便解决问题, 结合文章中对该参数作用的文档引用,基本可以确定

阶段二 走上弯路

尝试复现时,要先启动一个DB实例,便查询文档该参数如何在配置文件中配置,查了MySQL的文档,似乎只在enterprise版本中才有该配置项,便转头去看mariadb的配置说明(这一步给走弯路埋下了伏笔)。

用docker在本地启动了mariadb实例(thread_pool_size=2 thread_pool_oversubscribe=1)

先尝试用 select sleep(30) 模拟阻塞,用 sysbench 模拟正常流量,结果失败:

  1. 正常流量中有慢的,但是整体还符合正态分布,没有出现都卡的情况。
  2. 加大了 select sleep(30) 查询的并发量,现象同上。

又翻阅了一些文档,看到DB在调度时,对不同类型的SQL调度优先级会有所区别,类似sleep这种啥也不干的SQL,会不会被降低调度优先级,才导致了没有复现呢?(走上了弯路)

尝试人工制造慢查询:

  1. 用 sysbench 制造百万量级的表
  2. 执行 offset limit 的排序查询,并且不走索引

复现结果仍不满意:

  1. 整体耗时上升了,出现几笔长尾的耗时特别长的请求
  2. 但是整体仍然符合正态分布

此时分析了下,整体耗时上升是人工制造的慢查询,占用了过多IO和CPU资源,影响了sysbench SQL执行的效率。

阶段三 柳岸花明

回头又仔细看了下 mariadb关于线程池的文档,注意到文档中提到 thread_pool_oversubscribe 不决定同时有多少线程池被创建出来并执行任务,这个行为逻辑与文章中作者引用的并不相同。
又去查看了另一个MySQL发行版 percona 的文档,对该配置的行为描述与文章中的相符,基本就确定前面复现失败的原因了。

确定了前面提到的复现思路:用有稳定服务端执行耗时、并且不消耗大量硬件资源的SQL,用可控的并发进行模拟流量,到具体执行时:

  • SQL就用 select sleep(N)
  • 可控的并发就用 golang写个小脚本(事后看直接在终端手动操作也是可以的,不过写个脚本也不费事就是了)

mariadb 启动命令和配置

1
2
3
4
5
6
7
8
9
10
11
mkdir mariadb
cat > mariadb/my.cnf << EOF
[mariadb]
#thread pool
thread_handling=pool-of-threads
thread_pool_oversubscribe=1
thread_pool_size=1
thread_pool_max_threads=6
EOF

docker run --name mariadb -v ./mariadb:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=password -p3306:3306 mariadb:10.3

percona 启动命令和配置

1
2
3
4
5
6
7
8
9
10
11
mkdir percona
cat > percona/my.cnf << EOF
[mysqld]
#thread pool
thread_handling=pool-of-threads
thread_pool_oversubscribe=1
thread_pool_size=1
thread_pool_max_threads=1000
default_authentication_plugin=mysql_native_password
EOF
docker run --name percona -v ./percona:/etc/my.cnf.d -e MYSQL_ROOT_PASSWORD=123 -p33060:3306 percona:ps-8

注:Mac M1启动percona时,需要在 docker run 后面添加 --platform linux/x86_64 参数。(percona 未提供arm架构的image)

其他人的重现和分析

https://lotabout.me/2023/Verification-of-Percona-Thread-Pool-Behavior/ 从源代码debug上来分析

程序员案例星球介绍

【星球宗旨】

平时一学就懂,但是实践总是不会,这是因为学习时缺少实践案例、场景导致学起来没有体感。我们总是习惯通过课程、教科书想要一次系统性地掌握很多东西,但最终什么都没掌握好。所以星球想通过案例打透一个或几个知识点,让你通过这几个知识点再去生长发芽形成体系

image-20230510191422496

【关于案例】

本星球剖析各种程序员疑难经典案例,搞清楚一个案例基本能横扫一个领域,其次在一个案例后再带3/5个相关小案例可以帮你丰富场景,多角度理解。用做会来解决学不会的问题。 案例典型普适性强,代表基础组件基本原理等知识。分析手段尽量通用,分析过程一定要逻辑合理每个疑问都能回答清晰。 最终实现在新领域用旧知识旧工具解决疑难问题,无招胜有招

image-20230510191512744

【关于星主】

星主20多年的编程实践经历,疑难问题无数,擅长网络,性能,复杂系统的疑难问题分析,BAT背景,目前还在一线撕逼,作者的故事: https://plantegg.github.io/2022/01/01/%E4%B8%89%E4%B8%AA%E6%95%85%E4%BA%8B/

【星球成员成果】

强龙难压地头蛇的故事也引起各路技术大佬纷纷下场教年轻人如何学习:treeverse.app/view/RDzsOXjO

image-20230510193840999

【加入星球】

知识星球:https://t.zsxq.com/0cSFEUh2J

image-20230407232314969

我的网络传输速度为什么突然下降了

前言

这个问题是我星球成员做星球里面的必做实验时碰到的一个问题

最后的分析用到了我们的抓包大法+ping一ping真牛逼的证明方案,所以特意放出来供大家试试,同时也检验大家对知识的掌握和运用。

这个问题很好,在EC2上稳定重现,假如你们的业务碰到了这个问题你怎么解决?

或者换个场景描述:你有一个SQL单独执行很快,2个SQL并行一起查速度就降到了原来的10%,是DB还是谁的锅?

推特上大佬们的讨论,看看别人都是怎么思考和推理的:https://treeverse.app/view/RDzsOXjO

问题描述

一个降速问题,在AWS的 t2.micro机器上几乎100%复现,操作:

  1. 开三台aws t2.micro机器,一台做server两台做client, 已知正常情况rtt是0.5ms,bandwidth 60mbps,文件大小2g
  2. client1 去curl get server 文件,等一段时间速度稳定在 60mbps
  3. 然后用 client2 去curl get server 文件
  4. 可以观察到两个client都降速到3.5mbps,这种情况就是算把server跑坏了。
  5. 关掉client2, 观察到client1恢复到7-8mbps,但是远低于60mbps的带宽上限,也就是速度被限制到了标称的10%
  6. server搞坏之后,client重新下载就会出现一开始还行,但过10s就会掉到7-8mbps的情况,需要重启server才能恢复到60mbps

星球不能发多图,和pcap文件,重现的详细抓包、截图等都放在 google driver上了: https://drive.google.com/drive/folders/13rsOQ-6VZhXu0JRMLlUypRposRRcRN-a建议大家去下载client2.pcap抓包看看

抓包发现大量dup ack, 且bytes in flight很大,server send buffer很大。

img

==强烈建议你先别往下看,去下载上面链接中的抓包文件分析看看,然后和下面的分析对比一下==


分析

有网络大牛陈硕老师 在EC2上重现了这个问题 以及他的分析,速度由300Mbps下降到了60Mbps:

img

以及 左耳朵耗子 老师也做了实验以及分析:https://twitter.com/haoel/status/1654655067365179393

我的分析:https://articles.zsxq.com/id_iq5a872u8sux.html

image-20230506132001274

证明

证明原理如这个图

image-20230506131422140

image-20230506131709216

总结

99%的人不会弄脏双手去实验,哪怕是只需要下载一个抓包就能分析出来都不会去下载。但是为什么刚好是两位陈老师会去测试重现一下呢(原谅你没有AWS机器,但是不接受你不去google driver下载抓包文件 :) ),大概率他们的时间、经验、知识都比你要丰富一些,但是他们不忌惮弄脏手而你只想做个过客看看就懂,但最后你真的啥都没看懂!

从一个fin 卡顿问题到 scapy 的使用

scapy 使用

scapy 可以绕过内核构造任意网络包

使用比较简单,git clone https://github.com/secdev/scapy 然后在有python3的环境直接可以跑(python2官方说也支持)

注意:

scapy会触发内核发送reset,所以先要在iptables条件一条规则把内核的reset干掉,要不影响scapy的测试

1
2
3
iptables -A OUTPUT -p tcp --tcp-flags RST RST -s 192.168.0.1 -j DROP

iptables -A OUTPUT -p tcp --tcp-flags RST RST -s 192.168.0.1 -d 192.168.0.2 --sport 1234 --dport 12347 -j DROP

因为包是 scapy 绕过OS 构造的,导致 OS 不认识 scapy 模拟发出的包,内核里面没有你这个 Socket记录,只能 Reset你,所以还得用 iptables把这个OS 触发的 Reset 丢掉,否则 Reset 会让对端释放连接,这样测试才能顺利进行

三次握手

用scapy 模拟客户端来进行3次握手

代码:

1
2
3
4
sport=random.randint(1024,65535)
ip=IP(dst="127.0.0.1")
SYN=TCP(sport=sport, dport=22345, flags='S', seq=123451000)
c=sr1(ip/SYN)

完整案例:

1
2
3
4
5
6
7
8
9
10
11
# ./run_scapy
>>> sport=random.randint(1024,65535) //初始化一个本地随机端口
>>> SYN=TCP(sport=sport, dport=22345, flags='S', seq=123451000) //构造一个连 22345 目标端口的 SYN 包
>>> ip=IP(dst="192.168.0.2") //构造目标地址
>>> sr1(ip/SYN) 发送包
Begin emission:
Finished sending 1 packets.
.*
Received 2 packets, got 1 answers, remaining 0 packets
<IP version=4 ihl=5 tos=0x0 len=44 id=0 flags=DF frag=0 ttl=64 proto=tcp chksum=0xb978 src=192.168.0.2 dst=192.168.0.1 |<TCP sport=22345 dport=45814 seq=1599494827 ack=123451001 dataofs=6 reserved=0 flags=SA window=64240 chksum=0x99bb urgptr=0 options=[('MSS', 1460)] |<Padding load=b'\x00\x00' |>>>
>>>

上面的代码是给对端22345 发了个syn包,然后收到了 syn+ack 包并展示在最后,这一来一回的两个握手包都可以用tcpdump 抓到

服务端的话可以起一个标准http server来验证:

1
python3 -m http.server 22345

对应抓包:

1
2
3
4
15:58:43.301867 IP localhost.44633 > ky2.22345: Flags [S], seq 123451000, win 8192, length 0
15:58:43.301929 IP ky2.22345 > localhost.44633: Flags [S.], seq 106274946, ack 123451001, win 64240, options [mss 1460], length 0
15:58:44.361834 IP ky2.22345 > localhost.44633: Flags [S.], seq 106274946, ack 123451001, win 64240, options [mss 1460], length 0
15:58:46.441862 IP ky2.22345 > localhost.44633: Flags [S.], seq 106274946, ack 123451001, win 64240, options [mss 1460], length 0

fin 挥手端口卡顿案例

一个奇葩的tcp连接断开的卡顿问题(来自这里 https://mp.weixin.qq.com/s/BxU246Btm2FLt1pppBgYQg ),下面是我对这篇文章问题描述的总结:

1 两端几乎同时发fin, client收到fin回了一个ack

2 client发的ack先fin到达server,server收到ack直接进入time_wait

3 fin到达server被扔掉—-接下来就是要用scapy验证这个fin包被扔掉/忽略了,导致client不能立即断开要等200ms

4 client认为关闭失败,等了200ms重传fin然后关闭成功

这个问题的总结就是:TCP连接断开的四次挥手中,由于fin包和ack包乱序,导致等了一次timeout才关闭连接,但是上层业务设置了200ms超时,导致业务报错了,现在需要重现这个问题!

出现这种问题的场景:比如在 OVS/MOC 等网络场景下,SYN/FIN 等关键包需要更新路由(session flow),会走 slowpath(送到更高层的复杂逻辑处理,比如 SYN 就创建一个新的 session 记录,以后普通包直接命中这个 session 就快了;同样 FIN 需要走高层逻辑去释放这条 session 记录)

影响:因为 ack 比 FIN 先到,导致应用连接已被释放,但是后面的 FIN 被重传,从而可能使得应用记录的连接断开时间要晚甚至超时

原作者怎么分析定位,花了几周,这个过程大家可以去看上面的原因,本篇的目的是对这个问题用Scapy 来重现,目标掌握好 Scapy 这个工具,以后对各种其他问题大家自己都能快速定位

用scapy来模拟这个问题,server端用python实现,重点注意server端的断开方式有两个shutdown/close:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import socket

server_ip = "0.0.0.0"
server_port = 22345

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((server_ip, server_port))
server_socket.listen(5)

connection, client_address = server_socket.accept()

connection.shutdown(socket.SHUT_RDWR) //比较shutdown和close的不同
#connection.close()

time.sleep(3)
server_socket.close()

对应的client 测试代码,引入了scapy,代码首先是和服务端3次握手,然后抓取(sniff)所有服务端的来包,看看是不是fin,是的话故意先回ack再回fin人为制造乱序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
from scapy.all import *
import time
import sys

target_ip = "127.0.0.1"
target_port = 22345
#src_port=random.randint(1024,65535)
src_port=54322

ip = IP(dst=target_ip)
syn = TCP(sport=src_port, dport=target_port, flags="S", seq=4294967293)
syn_ack = sr1(ip / syn)
if syn_ack and TCP in syn_ack and syn_ack[TCP].flags == "SA":
print("Received SYN-ACK")
ack = TCP(sport=src_port, dport=target_port,
flags="A", seq=4322, ack=syn_ack.seq+1)
send(ip / ack)
data="rrrrrrrrrrrrrrrrrrrr"
payload=TCP(sport=src_port, dport=22345, flags='S', seq=4294967294)
send(ip/payload()/Raw(load=data))
print("Send ACK")
else:
print("Failed to establish TCP connection")

def handle_packet(packet):
print("handle fin packet")
print(Ether(raw(packet)))
if TCP in packet and packet[TCP].flags & 0x011 and packet[TCP].sport == 22345:
print("Received FIN packet")
ack = TCP(sport=src_port, dport=target_port,
flags="A", seq=packet.ack+1, ack=packet.seq+1)
send(ip / ack)

time.sleep(0.1)
fin = TCP(sport=src_port, dport=target_port,
flags="FA", seq=packet.ack, ack=packet.seq)
send(ip / fin)
sys.exit(0)
#抓包,抓到的包给handle_packet处理
#sniff(filter="tcp port 22345", prn=handle_packet)
sniff(filter="tcp port 22345", iface="lo",prn=handle_packet)

下图是服务端 shutdown时模拟挥手断开时ack包和fin包乱序了,也就是先回ack,sleep一段时间后再回fin包,如图:

image-20231009101444587

如果server端代码中将shutdown改成close 并做个对比,关键是上面绿框回复ack是4322(challenge_ack 表示seq=4322的fin包被忽略了),而下面close时的seq=4322的fin包会被正确接收并回复ack 4323 确认,那么这时client 可以断开了。而上图绿框表示fin 被忽略了,那么内核要继续等200ms 再次发 fin,等收到ack后client 才能断开

image-20231008175355835

server上通过 netstat 观察连接的状态变化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//shutdown,可以看到server 发fin进入FIN_WAIT1,然后收到ack 进入 FIN_WAIT2,此时收到fin了,但是被扔掉了,无法断开进入TIME_WAIT
# sh test.sh
tcp 0 0 192.168.0.2:22345 192.168.0.1:54322 SYN_RECV
tcp 0 1 192.168.0.2:22345 192.168.0.1:54322 FIN_WAIT1
tcp 0 0 192.168.0.2:22345 192.168.0.1:54322 FIN_WAIT2
tcp 0 0 192.168.0.2:22345 192.168.0.1:54322 FIN_WAIT2
tcp 0 0 192.168.0.2:22345 192.168.0.1:54322 FIN_WAIT2
tcp 0 0 192.168.0.2:22345 192.168.0.1:54322 FIN_WAIT2
tcp 0 0 192.168.0.2:22345 192.168.0.1:54322 FIN_WAIT2

//close 可以看到server 发fin进入FIN_WAIT1,然后收到ack 进入 FIN_WAIT2,此时收到fin了没有被扔掉,所以很快连接断开进入了TIME_WAIT
tcp 0 0 192.168.0.2:22345 192.168.0.1:54322 SYN_RECV
tcp 0 1 192.168.0.2:22345 192.168.0.1:54322 FIN_WAIT1
tcp 0 0 192.168.0.2:22345 192.168.0.1:54322 FIN_WAIT2
tcp 0 0 192.168.0.2:22345 192.168.0.1:54322 TIME_WAIT
tcp 0 0 192.168.0.2:22345 192.168.0.1:54322 TIME_WAIT

seq 回绕到0 会导致丢包吗?

首先学习下 seq 是一个无符号32位的整数,最大值是4294967295

如图,有人发现探活连接不通,导致了一次非正常切换,所以需要分析连接为什么断开,抓包发现重传的时候正好seq 为0,于是他就奇怪了是不是这个seq溢出搞的鬼?怎么这么巧seq 刚好为0了?

image-20231130113732507

要想正好巧合在 seq 回绕的时候刚好回绕到 0 还是非常不容易的,不过好在用 scapy 来模拟这事就简单了

重现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from scapy.all import *
import time
import sys

target_ip = "server_host_ip"
target_port = 22345
src_port=random.randint(1024,65535)

#接近最大值 4294967295
start_seq=4294967292

ip = IP(dst=target_ip)
syn = TCP(sport=src_port, dport=target_port, flags="S", seq=start_seq)
syn_ack = sr1(ip / syn)
if syn_ack and TCP in syn_ack and syn_ack[TCP].flags == "SA":
print("Received SYN-ACK")
ack = TCP(sport=src_port, dport=target_port,
flags="A", seq=syn_ack.ack, ack=syn_ack.seq+1)
print(syn_ack.seq)
print(syn_ack.ack)
print(ack)
send(ip/ack)
print("Send ACK")
else:
print("Failed to establish TCP connection")

print("send payload")
#4294967293+3(3个r) 正好是无符号整数溢出(最大 4294967295),回绕到0
data="rrr"
payload=TCP(sport=src_port, dport=22345,flags="AP", seq=syn_ack.ack, ack=syn_ack.seq+1)
payload2=TCP(sport=src_port, dport=22345,flags="AP", seq=0, ack=syn_ack.seq+1)
syn_ack=send(ip/payload/Raw(load=data))
syn_ack=send(ip/payload2/Raw(load=data))

对应的抓包:

image-20231130114200515

从上面的实验来看内核能正确处理这种 seq 回绕到 0 的场景,所以问题不在内核处理上。进一步分析发现是交换机的 bug 导致的

最终确认原因:交换机bug 丢掉 seq=0的包

最后发现这个问题是中兴 9900系列交换机存在seq=0 push 发送模式下报文存在丢失缺陷, 丢包原因是中兴交换机从安全角度考量是支持antidos防攻击功能,该功能开启后会将 TCP seq 为 0 的报文作为非法报文并丢弃。中兴 9900交换机上该功能默认是关闭的但是未生效,需要重新触发关闭(现场看配置是关闭的,实际是开启的,现在执行将配置先打开再关闭)。
临时规避方案:(中兴内部验证测试针对此报文有效,现场环境可能有差异,需要现场验证确认)

1
2
先执行 (config)#anti-dos abnormal enable
再执行 (config)#anti-dos abnormal disable

现场实施完毕后,发包验证恢复正常,后续持续观察业务。

彻底解决方案:将该版本升级至V2.00.00R8P16

华为交换机针对默认连接丢包 bug

借着中兴这个交换机问题,说一下华为交换机的的 bug,华为 CE12800系列,V200R022C00SPC500之前的版本

当大规格路由反复震荡场景下会小概率

  1. 出现优先级更高的(子网掩码范围更小)路由表项残留,导致整个子网不通
  2. 某个接口下发的路由表和其他接口(芯片)不一致,导致某条连接一直丢包

可以重启单板修复表项异常问题,该问题在新版本上(V200R022C00SPC500)已经补丁修复(SPH220)

根因:残留表项或表项异常导致,老版本在大规格路由反复震荡场景下会小概率触发,是软件多线程处理路由下发或删除时,出现线程读取数据异常,导致芯片表项错误。

scapy 构造全连接队列溢出

server 端用python 起一个WEB 服务:

1
nohup python3 -m http.server 22345 &

然后client端用如下scapy 代码不断去3次握手建立连接,试几次后就抓到如下现象:

image-20231130111028268

抓包效果:

image-20231130133248747

总结

我觉得scapy还是挺好用的,比packetdrill好用一万倍,直观明了,还有命令行可以交互测试

但是要注意 scapy 是绕过内核在模拟发包,收包靠 sniff,所以内核收到这些回包会认为连接不存在,直接reset,需要在iptables 里处理一下

这个问题是别人推荐给我看的,一般10分钟就看完了,但是我差不多花了2天时间,不断地想和去实验重现

参考资料

https://wizardforcel.gitbooks.io/scapy-docs/content/3.html

https://www.osgeo.cn/scapy/usage.html

https://zhuanlan.zhihu.com/p/51002301

如果你觉得看完对你很有帮助可以通过如下方式找到我

find me on twitter: @plantegg

知识星球:https://t.zsxq.com/0cSFEUh2J

开了一个星球,在里面讲解一些案例、知识、学习方法,肯定没法让大家称为顶尖程序员(我自己都不是),只是希望用我的方法、知识、经验、案例作为你的垫脚石,帮助你快速、早日成为一个基本合格的程序员。

争取在星球内:

  • 养成基本动手能力
  • 拥有起码的分析推理能力–按我接触的程序员,大多都是没有逻辑的
  • 知识上教会你几个关键的知识点
image-20240324161113874

比较不同CPU下的分支预测

目的

本文通过一段对分支预测是否友好的代码来验证 branch load miss 差异,已经最终带来的 性能差异。同时在x86和aarch64 下各选几款CPU共5款进行差异性对比

CPU 情况

intel x86

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 48
On-line CPU(s) list: 0-47
Thread(s) per core: 1
Core(s) per socket: 24
Socket(s): 2
NUMA node(s): 2
Vendor ID: GenuineIntel
CPU family: 6
Model: 85
Model name: Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz
Stepping: 4
CPU MHz: 2500.195
CPU max MHz: 3100.0000
CPU min MHz: 1000.0000
BogoMIPS: 4998.89
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 1024K
L3 cache: 33792K
NUMA node0 CPU(s): 0-23
NUMA node1 CPU(s): 24-47
Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch ida arat epb invpcid_single pln pts dtherm spec_ctrl ibpb_support tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm cqm mpx rdt avx512f avx512dq rdseed adx smap clflushopt avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local cat_l3 mba

hygon 7260

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
Address sizes: 43 bits physical, 48 bits virtual
CPU(s): 48
On-line CPU(s) list: 0-47
Thread(s) per core: 1
Core(s) per socket: 24
Socket(s): 2
NUMA node(s): 8
Vendor ID: HygonGenuine
CPU family: 24
Model: 1
Model name: Hygon C86 7260 24-core Processor
Stepping: 1
Frequency boost: enabled
CPU MHz: 1069.534
CPU max MHz: 2200.0000
CPU min MHz: 1200.0000
BogoMIPS: 4399.38
Virtualization: AMD-V
L1d cache: 32K
L1i cache: 64K
L2 cache: 512K
L3 cache: 8192K
NUMA node0 CPU(s): 0-5
NUMA node1 CPU(s): 6-11
NUMA node2 CPU(s): 12-17
NUMA node3 CPU(s): 18-23
NUMA node4 CPU(s): 24-29
NUMA node5 CPU(s): 30-35
NUMA node6 CPU(s): 36-41
NUMA node7 CPU(s): 42-47

ARM 鲲鹏920

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#lscpu
Architecture: aarch64
Byte Order: Little Endian
CPU(s): 96
On-line CPU(s) list: 0-95
Thread(s) per core: 1
Core(s) per socket: 48
Socket(s): 2
NUMA node(s): 4
Model: 0
CPU max MHz: 2600.0000
CPU min MHz: 200.0000
BogoMIPS: 200.00
L1d cache: 64K
L1i cache: 64K
L2 cache: 512K
L3 cache: 24576K
NUMA node0 CPU(s): 0-23
NUMA node1 CPU(s): 24-47
NUMA node2 CPU(s): 48-71
NUMA node3 CPU(s): 72-95
Flags: fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma dcpop asimddp asimdfhm

ARM M710

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#lscpu
Architecture: aarch64
Byte Order: Little Endian
CPU(s): 128
On-line CPU(s) list: 0-127
Thread(s) per core: 1
Core(s) per socket: 128
Socket(s): 1
NUMA node(s): 2
Model: 0
BogoMIPS: 100.00
L1d cache: 64K
L1i cache: 64K
L2 cache: 1024K
L3 cache: 65536K
NUMA node0 CPU(s): 0-63
NUMA node1 CPU(s): 64-127
Flags: fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 sm3 sm4 asimddp sha512 sve asimdfhm dit uscat ilrcpc flagm ssbs sb dcpodp sve2 sveaes svepmull svebitperm svesha3 svesm4 flagm2 frint svei8mm svebf16 i8mm bf16 dgh

ARM FT S2500

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#lscpu
Architecture: aarch64
Byte Order: Little Endian
CPU(s): 128
On-line CPU(s) list: 0-127
Thread(s) per core: 1
Core(s) per socket: 64
Socket(s): 2
NUMA node(s): 16
Model: 3
BogoMIPS: 100.00
L1d cache: 32K
L1i cache: 32K
L2 cache: 2048K
L3 cache: 65536K
NUMA node0 CPU(s): 0-7
NUMA node1 CPU(s): 8-15
NUMA node2 CPU(s): 16-23
NUMA node3 CPU(s): 24-31
NUMA node4 CPU(s): 32-39
NUMA node5 CPU(s): 40-47
NUMA node6 CPU(s): 48-55
NUMA node7 CPU(s): 56-63
NUMA node8 CPU(s): 64-71
NUMA node9 CPU(s): 72-79
NUMA node10 CPU(s): 80-87
NUMA node11 CPU(s): 88-95
NUMA node12 CPU(s): 96-103
NUMA node13 CPU(s): 104-111
NUMA node14 CPU(s): 112-119
NUMA node15 CPU(s): 120-127
Flags: fp asimd evtstrm aes pmull sha1 sha2 crc32 cpuid

测试代码

对一个数组中较大的一半的值累加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <algorithm>
#include <ctime>
#include <iostream>

int main()
{
// 随机产生整数,用分区函数填充,以避免出现分桶不均
const unsigned arraySize = 32768;
int data[arraySize];

for (unsigned c = 0; c < arraySize; ++c)
data[c] = std::rand() % 256;

//排序后数据有序,CPU可以准确预测到if的分支
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;
}

以上代码可以注释掉第15行,也就是不对代码排序直接累加,不排序的话 if (data[c] >= 128) 有50% 概率成立,排序后前一半元素if都不成立,后一半元素if都成立,导致CPU流水线很好预测后面的代码,可以提前加载运算打高IPC

测试结果

aarch64 鲲鹏920

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#perf stat -e branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,stalled-cycles-backend,stalled-cycles-frontend,alignment-faults,bpf-output,context-switches,cpu-clock,cpu-migrations,dummy,emulation-faults,major-faults,minor-faults,page-faults,task-clock,L1-dcache-load-misses,L1-dcache-loads,L1-dcache-store-misses,L1-dcache-stores,L1-icache-load-misses,L1-icache-loads,branch-load-misses,branch-loads,dTLB-load-misses,dTLB-loads,iTLB-load-misses,iTLB-loads ./aftersort
11.44
sum = 314931600000

470,740 branch-misses (59.99%)
29,716,627,485 bus-cycles # 2595.890 M/sec (60.03%)
96,469,435 cache-misses # 0.420 % of all cache refs (60.03%)
22,984,316,728 cache-references # 2007.791 M/sec (60.03%)
29,716,018,641 cpu-cycles # 2.596 GHz (65.02%)
83,666,813,837 instructions # 2.82 insn per cycle
# 0.10 stalled cycles per insn (65.02%)
8,765,807,804 stalled-cycles-backend # 29.50% backend cycles idle (65.02%)
8,917,112 stalled-cycles-frontend # 0.03% frontend cycles idle (65.02%)
0 alignment-faults # 0.000 K/sec
0 bpf-output # 0.000 K/sec
5 context-switches # 0.000 K/sec
11,447.57 msec cpu-clock # 1.000 CPUs utilized
0 cpu-migrations # 0.000 K/sec
0 dummy # 0.000 K/sec
0 emulation-faults # 0.000 K/sec
0 major-faults # 0.000 K/sec
132 minor-faults # 0.012 K/sec
132 page-faults # 0.012 K/sec
11,447.57 msec task-clock # 1.000 CPUs utilized
96,471,779 L1-dcache-load-misses # 0.42% of all L1-dcache accesses (65.02%)
22,985,408,745 L1-dcache-loads # 2007.886 M/sec (65.02%)
96,472,614 L1-dcache-store-misses # 8.427 M/sec (65.02%)
22,986,056,706 L1-dcache-stores # 2007.943 M/sec (65.02%)
184,402 L1-icache-load-misses # 0.00% of all L1-icache accesses (65.02%)
14,779,996,797 L1-icache-loads # 1291.104 M/sec (64.99%)
330,651 branch-load-misses # 0.029 M/sec (64.96%)
6,561,353,921 branch-loads # 573.166 M/sec (64.96%)
3,464,612 dTLB-load-misses # 0.02% of all dTLB cache accesses (64.96%)
23,008,097,187 dTLB-loads # 2009.868 M/sec (59.96%)
745 iTLB-load-misses # 0.00% of all iTLB cache accesses (59.96%)
14,779,577,851 iTLB-loads # 1291.067 M/sec (59.96%)

#perf stat -e branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,stalled-cycles-backend,stalled-cycles-frontend,alignment-faults,bpf-output,context-switches,cpu-clock,cpu-migrations,dummy,emulation-faults,major-faults,minor-faults,page-faults,task-clock,L1-dcache-load-misses,L1-dcache-loads,L1-dcache-store-misses,L1-dcache-stores,L1-icache-load-misses,L1-icache-loads,branch-load-misses,branch-loads,dTLB-load-misses,dTLB-loads,iTLB-load-misses,iTLB-loads ./beforesort
30.92
sum = 314931600000

1,639,558,981 branch-misses (59.96%)
80,284,783,419 bus-cycles # 2595.949 M/sec (59.96%)
118,459,436 cache-misses # 0.356 % of all cache refs (59.96%)
33,285,701,200 cache-references # 1076.269 M/sec (59.96%)
80,283,427,379 cpu-cycles # 2.596 GHz (64.96%)
83,694,841,472 instructions # 1.04 insn per cycle
# 0.11 stalled cycles per insn (64.98%)
8,849,746,372 stalled-cycles-backend # 11.02% backend cycles idle (64.99%)
8,064,207,583 stalled-cycles-frontend # 10.04% frontend cycles idle (65.00%)
0 alignment-faults # 0.000 K/sec
0 bpf-output # 0.000 K/sec
10 context-switches # 0.000 K/sec
30,926.95 msec cpu-clock # 1.000 CPUs utilized
0 cpu-migrations # 0.000 K/sec
0 dummy # 0.000 K/sec
0 emulation-faults # 0.000 K/sec
0 major-faults # 0.000 K/sec
133 minor-faults # 0.004 K/sec
133 page-faults # 0.004 K/sec
30,926.95 msec task-clock # 1.000 CPUs utilized
118,445,576 L1-dcache-load-misses # 0.36% of all L1-dcache accesses (65.02%)
33,286,586,418 L1-dcache-loads # 1076.297 M/sec (65.03%)
118,441,599 L1-dcache-store-misses # 3.830 M/sec (65.04%)
33,286,751,407 L1-dcache-stores # 1076.302 M/sec (65.05%)
410,040 L1-icache-load-misses # 0.00% of all L1-icache accesses (65.05%)
51,611,031,810 L1-icache-loads # 1668.805 M/sec (65.04%)
1,639,731,725 branch-load-misses # 53.020 M/sec (65.03%)
7,520,634,791 branch-loads # 243.174 M/sec (65.02%)
3,536,061 dTLB-load-misses # 0.01% of all dTLB cache accesses (65.00%)
47,898,134,543 dTLB-loads # 1548.751 M/sec (59.99%)
2,529 iTLB-load-misses # 0.00% of all iTLB cache accesses (59.97%)
51,612,575,118 iTLB-loads # 1668.854 M/sec (59.96%)

以上在相同CPU下数据对比可以看到核心差异是branch-load-misses和branch-misses,当然最终也体现在 IPC 数值上,排序后IPC更高不是因为数据有序取起来更快,而是因为执行逻辑更容易提前预测,也就是可以提前加载if代码到cache中。符合预期

aarch64 M710

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#perf stat -e branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,stalled-cycles-backend,stalled-cycles-frontend,alignment-faults,bpf-output,context-switches,cpu-clock,cpu-migrations,dummy,emulation-faults,major-faults,minor-faults,page-faults,task-clock,L1-dcache-load-misses,L1-dcache-loads,L1-icache-load-misses,L1-icache-loads,LLC-load-misses,LLC-loads,branch-load-misses,branch-loads,dTLB-load-misses,dTLB-loads,iTLB-load-misses,iTLB-loads ./aftersort
8.20237
sum = 314931600000

912,836 branch-misses (29.86%)
22,560,165,604 bus-cycles # 2748.461 M/sec (29.91%)
205,068,961 cache-misses # 0.892 % of all cache refs (29.96%)
22,998,186,284 cache-references # 2801.824 M/sec (30.01%)
22,559,518,941 cpu-cycles # 2.748 GHz (35.03%)
77,068,271,833 instructions # 3.42 insn per cycle
# 0.06 stalled cycles per insn (35.08%)
4,892,933,264 stalled-cycles-backend # 21.69% backend cycles idle (35.13%)
1,103,203,963 stalled-cycles-frontend # 4.89% frontend cycles idle (35.13%)
0 alignment-faults # 0.000 K/sec
0 bpf-output # 0.000 K/sec
17 context-switches # 0.002 K/sec
8,208.29 msec cpu-clock # 1.000 CPUs utilized
3 cpu-migrations # 0.000 K/sec
0 dummy # 0.000 K/sec
0 emulation-faults # 0.000 K/sec
0 major-faults # 0.000 K/sec
227 minor-faults # 0.028 K/sec
227 page-faults # 0.028 K/sec
8,208.30 msec task-clock # 1.000 CPUs utilized
205,384,990 L1-dcache-load-misses # 0.89% of all L1-dcache accesses (35.13%)
22,997,494,522 L1-dcache-loads # 2801.739 M/sec (35.13%)
66,804 L1-icache-load-misses # 0.00% of all L1-icache accesses (35.13%)
15,486,704,750 L1-icache-loads # 1886.715 M/sec (30.12%)
76,066 LLC-load-misses # 0.00% of all LL-cache accesses (30.09%)
0 LLC-loads # 0.000 K/sec (30.03%)
672,231 branch-load-misses # 0.082 M/sec (29.98%)
9,844,109,024 branch-loads # 1199.288 M/sec (29.93%)
107,198 dTLB-load-misses # 0.00% of all dTLB cache accesses (29.89%)
22,998,647,232 dTLB-loads # 2801.880 M/sec (29.84%)
9,497 iTLB-load-misses # 0.08% of all iTLB cache accesses (29.81%)
11,755,825 iTLB-loads # 1.432 M/sec (29.82%)

8.210235171 seconds time elapsed

#perf stat -e branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,stalled-cycles-backend,stalled-cycles-frontend,alignment-faults,bpf-output,context-switches,cpu-clock,cpu-migrations,dummy,emulation-faults,major-faults,minor-faults,page-faults,task-clock,L1-dcache-load-misses,L1-dcache-loads,L1-icache-load-misses,L1-icache-loads,LLC-load-misses,LLC-loads,branch-load-misses,branch-loads,dTLB-load-misses,dTLB-loads,iTLB-load-misses,iTLB-loads ./beforesort
16.8872
sum = 314931600000

1,229,182,485 branch-misses (29.93%)
46,401,675,872 bus-cycles # 2747.200 M/sec (29.95%)
206,116,950 cache-misses # 0.546 % of all cache refs (29.97%)
37,773,036,315 cache-references # 2236.343 M/sec (30.01%)
46,410,071,081 cpu-cycles # 2.748 GHz (35.03%)
77,083,625,280 instructions # 1.66 insn per cycle
# 0.06 stalled cycles per insn (35.07%)
1,961,071,890 stalled-cycles-backend # 4.23% backend cycles idle (35.11%)
4,988,241,014 stalled-cycles-frontend # 10.75% frontend cycles idle (35.11%)
0 alignment-faults # 0.000 K/sec
0 bpf-output # 0.000 K/sec
1,100 context-switches # 0.065 K/sec
16,890.39 msec cpu-clock # 0.997 CPUs utilized
7 cpu-migrations # 0.000 K/sec
0 dummy # 0.000 K/sec
0 emulation-faults # 0.000 K/sec
0 major-faults # 0.000 K/sec
229 minor-faults # 0.014 K/sec
229 page-faults # 0.014 K/sec
16,890.69 msec task-clock # 0.997 CPUs utilized
205,761,970 L1-dcache-load-misses # 0.54% of all L1-dcache accesses (35.09%)
37,832,336,945 L1-dcache-loads # 2239.854 M/sec (35.06%)
207,158 L1-icache-load-misses # 0.00% of all L1-icache accesses (35.04%)
41,944,228,741 L1-icache-loads # 2483.298 M/sec (30.00%)
135,144 LLC-load-misses # 0.00% of all LL-cache accesses (29.97%)
0 LLC-loads # 0.000 K/sec (29.97%)
1,232,325,180 branch-load-misses # 72.960 M/sec (29.96%)
14,776,289,690 branch-loads # 874.827 M/sec (29.96%)
177,790 dTLB-load-misses # 0.00% of all dTLB cache accesses (29.97%)
37,839,288,998 dTLB-loads # 2240.266 M/sec (29.95%)
46,301 iTLB-load-misses # 0.00% of all iTLB cache accesses (29.94%)
12,631,307,441 iTLB-loads # 747.833 M/sec (29.92%)

16.943678377 seconds time elapsed

M710上排序与否和鲲鹏差不多,但是 M710比 鲲鹏要快一些,差别只要有主频高一点点(6%),另外M710编译后的指令数量也略少(8%)。

最大的差别是没有排序的话 branch-load-misses(1,232,325,180)/branch-loads(14,776,289,690) 比例只有鲲鹏的50%,导致整体 IPC 比鲲鹏高不少(1.66 VS 1.04)

如果是排序后的数据来看 M710比鲲鹏好40%,IPC 好了20%,iTLB-loads 差异特别大

aarch64 FT S2500

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#perf stat -e branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,alignment-faults,context-switches,cpu-clock,cpu-migrations,dummy,emulation-faults,major-faults,minor-faults,page-faults,task-clock,L1-dcache-load-misses,L1-dcache-loads,L1-dcache-store-misses,L1-dcache-stores,L1-icache-load-misses,L1-icache-loads,branch-load-misses,branch-loads,dTLB-load-misses,iTLB-load-misses ./aftersort
16.63
sum = 314931600000

1,298,873 branch-misses # 0.078 M/sec (37.49%)
34,893,306,641 bus-cycles # 2096.049 M/sec (37.51%)
211,447,452 cache-misses # 0.913 % of all cache refs (37.53%)
23,154,909,673 cache-references # 1390.921 M/sec (37.54%)
34,891,766,353 cpu-cycles # 2.096 GHz (43.79%)
83,918,069,835 instructions # 2.41 insns per cycle (43.79%)
0 alignment-faults # 0.000 K/sec
102 context-switches # 0.006 K/sec
16647.131540 cpu-clock (msec)
35 cpu-migrations # 0.002 K/sec
0 dummy # 0.000 K/sec
0 emulation-faults # 0.000 K/sec
0 major-faults # 0.000 K/sec
384 minor-faults # 0.023 K/sec
384 page-faults # 0.023 K/sec
16647.178560 task-clock (msec) # 1.000 CPUs utilized
211,277,069 L1-dcache-load-misses # 0.91% of all L1-dcache hits (43.79%)
23,168,806,437 L1-dcache-loads # 1391.756 M/sec (43.77%)
211,376,611 L1-dcache-store-misses # 12.697 M/sec (43.75%)
23,172,492,978 L1-dcache-stores # 1391.977 M/sec (43.73%)
6,060,438 L1-icache-load-misses # 0.364 M/sec (43.72%)
23,283,174,318 L1-icache-loads # 1398.626 M/sec (37.48%)
1,201,268 branch-load-misses # 0.072 M/sec (37.47%)
6,626,003,512 branch-loads # 398.026 M/sec (37.47%)
4,417,981 dTLB-load-misses # 0.265 M/sec (37.47%)
58,242 iTLB-load-misses # 0.003 M/sec (37.47%)

#perf stat -e branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,alignment-faults,context-switches,cpu-clock,cpu-migrations,dummy,emulation-faults,major-faults,minor-faults,page-faults,task-clock,L1-dcache-load-misses,L1-dcache-loads,L1-dcache-store-misses,L1-dcache-stores,L1-icache-load-misses,L1-icache-loads,branch-load-misses,branch-loads,dTLB-load-misses,iTLB-load-misses ./beforesort
39.8
sum = 314931600000

1,641,714,982 branch-misses # 41.244 M/sec (37.50%)
83,450,971,727 bus-cycles # 2096.514 M/sec (37.51%)
209,942,920 cache-misses # 0.625 % of all cache refs (37.51%)
33,584,108,027 cache-references # 843.724 M/sec (37.51%)
83,446,991,284 cpu-cycles # 2.096 GHz (43.76%)
83,872,213,462 instructions # 1.01 insns per cycle (43.75%)
0 alignment-faults # 0.000 K/sec
165 context-switches # 0.004 K/sec
39804.395840 cpu-clock (msec)
104 cpu-migrations # 0.003 K/sec
0 dummy # 0.000 K/sec
0 emulation-faults # 0.000 K/sec
0 major-faults # 0.000 K/sec
728 minor-faults # 0.018 K/sec
728 page-faults # 0.018 K/sec
39804.626860 task-clock (msec) # 1.000 CPUs utilized
209,884,485 L1-dcache-load-misses # 0.62% of all L1-dcache hits (43.75%)
33,591,847,895 L1-dcache-loads # 843.918 M/sec (43.75%)
209,796,158 L1-dcache-store-misses # 5.271 M/sec (43.75%)
33,595,628,139 L1-dcache-stores # 844.013 M/sec (43.75%)
5,575,802 L1-icache-load-misses # 0.140 M/sec (43.75%)
68,272,798,305 L1-icache-loads # 1715.198 M/sec (37.50%)
1,642,653,627 branch-load-misses # 41.268 M/sec (37.50%)
6,846,418,902 branch-loads # 172.001 M/sec (37.50%)
4,162,728 dTLB-load-misses # 0.105 M/sec (37.50%)
57,375 iTLB-load-misses # 0.001 M/sec (37.50%)

Intel x86 8163

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#perf stat -e branch-instructions,branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,ref-cycles,alignment-faults,context-switches,cpu-clock,cpu-migrations,dummy,emulation-faults,major-faults,minor-faults,page-faults,task-clock,L1-dcache-load-misses,L1-dcache-loads,L1-dcache-stores,L1-icache-load-misses,LLC-load-misses,LLC-loads,LLC-store-misses,LLC-stores,branch-load-misses,branch-loads,dTLB-load-misses,dTLB-loads,dTLB-store-misses,dTLB-stores,iTLB-load-misses,iTLB-loads,node-load-misses,node-loads,node-store-misses,node-stores ./aftersort
9.77
sum = 314931600000

6,541,060,672 branch-instructions # 669.204 M/sec (10.72%)
727,847 branch-misses # 0.01% of all branches (14.30%)
241,730,862 bus-cycles # 24.731 M/sec (17.88%)
275,443 cache-misses # 44.685 % of all cache refs (21.45%)
616,413 cache-references # 0.063 M/sec (25.03%)
24,186,369,646 cpu-cycles # 2.474 GHz (28.60%)
29,491,804,977 instructions # 1.22 insns per cycle (32.18%)
24,198,780,299 ref-cycles # 2475.731 M/sec (35.75%)
0 alignment-faults # 0.000 K/sec
16 context-switches # 0.002 K/sec
9774.393202 cpu-clock (msec)
8 cpu-migrations # 0.001 K/sec
0 dummy # 0.000 K/sec
0 emulation-faults # 0.000 K/sec
0 major-faults # 0.000 K/sec
490 minor-faults # 0.050 K/sec
490 page-faults # 0.050 K/sec
9774.396556 task-clock (msec) # 1.001 CPUs utilized
74,078,676 L1-dcache-load-misses # 1.64% of all L1-dcache hits (189256748561.94%)
4,515,522,850 L1-dcache-loads # 461.975 M/sec (189237344482.16%)
3,798,032 L1-dcache-stores # 0.389 M/sec (189217941721.85%)
1,077,146 L1-icache-load-misses # 0.110 M/sec (189198537875.18%)
89,144 LLC-load-misses # 74.54% of all LL-cache hits (189179139811.86%)
119,586 LLC-loads # 0.012 M/sec (189159737036.24%)
3,450 LLC-store-misses # 0.353 K/sec (189140342885.02%)
105,021 LLC-stores # 0.011 M/sec (7.15%)
458,465 branch-load-misses # 0.047 M/sec (10.73%)
6,557,558,579 branch-loads # 670.891 M/sec (14.30%)
733 dTLB-load-misses # 0.00% of all dTLB cache hits (17.87%)
12,039,967,837 dTLB-loads # 1231.786 M/sec (21.44%)
104 dTLB-store-misses # 0.011 K/sec (25.01%)
7,040,783 dTLB-stores # 0.720 M/sec (28.58%)
763 iTLB-load-misses # 62.80% of all iTLB cache hits (28.56%)
1,215 iTLB-loads # 0.124 K/sec (28.55%)
168,588 node-load-misses # 0.017 M/sec (28.55%)
131,578 node-loads # 0.013 M/sec (28.55%)
4,484 node-store-misses # 0.459 K/sec (7.14%)
42 node-stores # 0.004 K/sec (7.14%)

#perf stat -e branch-instructions,branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,ref-cycles,alignment-faults,context-switches,cpu-clock,cpu-migrations,dummy,emulation-faults,major-faults,minor-faults,page-faults,task-clock,L1-dcache-load-misses,L1-dcache-loads,L1-dcache-stores,L1-icache-load-misses,LLC-load-misses,LLC-loads,LLC-store-misses,LLC-stores,branch-load-misses,branch-loads,dTLB-load-misses,dTLB-loads,dTLB-store-misses,dTLB-stores,iTLB-load-misses,iTLB-loads,node-load-misses,node-loads,node-store-misses,node-stores ./beforesort
29.52
sum = 314931600000

6,565,036,614 branch-instructions # 222.370 M/sec (10.72%)
1,599,826,737 branch-misses # 24.37% of all branches (14.29%)
730,977,010 bus-cycles # 24.760 M/sec (17.86%)
920,858 cache-misses # 48.057 % of all cache refs (21.43%)
1,916,178 cache-references # 0.065 M/sec (25.00%)
73,123,904,158 cpu-cycles # 2.477 GHz (28.57%)
29,618,485,912 instructions # 0.41 insns per cycle (32.14%)
73,152,861,566 ref-cycles # 2477.828 M/sec (35.72%)
0 alignment-faults # 0.000 K/sec
26 context-switches # 0.001 K/sec
29522.972689 cpu-clock (msec)
13 cpu-migrations # 0.000 K/sec
0 dummy # 0.000 K/sec
0 emulation-faults # 0.000 K/sec
0 major-faults # 0.000 K/sec
593 minor-faults # 0.020 K/sec
593 page-faults # 0.020 K/sec
29522.976661 task-clock (msec) # 1.001 CPUs utilized
76,164,025 L1-dcache-load-misses # 1.68% of all L1-dcache hits (62596004730.92%)
4,521,935,099 L1-dcache-loads # 153.167 M/sec (62593882213.79%)
1,170,288 L1-dcache-stores # 0.040 M/sec (62591759384.11%)
2,975,318 L1-icache-load-misses # 0.101 M/sec (62591281765.29%)
178,510 LLC-load-misses # 66.98% of all LL-cache hits (62591281765.30%)
266,514 LLC-loads # 0.009 M/sec (62591281765.18%)
6,841 LLC-store-misses # 0.232 K/sec (62591578887.87%)
335,369 LLC-stores # 0.011 M/sec (7.15%)
1,600,893,693 branch-load-misses # 54.225 M/sec (10.72%)
6,565,516,562 branch-loads # 222.387 M/sec (14.29%)
33,070 dTLB-load-misses # 0.00% of all dTLB cache hits (17.87%)
12,043,088,689 dTLB-loads # 407.923 M/sec (21.44%)
180 dTLB-store-misses # 0.006 K/sec (25.01%)
2,359,365 dTLB-stores # 0.080 M/sec (28.58%)
9,399 iTLB-load-misses # 849.82% of all iTLB cache hits (28.58%)
1,106 iTLB-loads # 0.037 K/sec (28.58%)
439,052 node-load-misses # 0.015 M/sec (28.58%)
367,546 node-loads # 0.012 M/sec (28.58%)
7,539 node-store-misses # 0.255 K/sec (7.15%)
1,736 node-stores # 0.059 K/sec (7.14%)

从 x86 和 aarch 对比来看,x86 编译后的指令数是 aarch 的35%,ARM 是精简指令,数量多比较好理解。主频2.5 GHz 较 M710低了11%。

IPC 差异比较大,有一部分是因为 ARM 精简指令本来有较高的 IPC。

从排序前的差异来看除了指令集外导致 IPC 较高的原因主要也是 branch-load-misses(1,232,325,180)/branch-loads(14,776,289,690) 比 intel的 1,602,020,038/6,568,921,480, 也就是 M710的 branch miss 率比 intel 低了一倍。

排序后去掉了 branch miss 差异,M710 比 intel 快了 10%,只要是因为主频的差异

on 8269 3.2GHz

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#perf stat -e branch-instructions,branch-misses,cpu-cycles,instructions,branch-load-misses,branch-loads,task-clock,cpu-clock ./beforesort
22.96
sum = 314931600000

Performance counter stats for './beforesort':

6,573,626,859 branch-instructions # 286.177 M/sec (83.33%)
1,602,898,541 branch-misses # 24.38% of all branches (83.33%)
73,189,204,878 cpu-cycles # 3.186 GHz (66.67%)
29,627,520,323 instructions # 0.40 insns per cycle (83.33%)
1,602,848,454 branch-load-misses # 69.779 M/sec (83.33%)
6,572,915,651 branch-loads # 286.146 M/sec (83.33%)
22970.482491 task-clock (msec) # 1.001 CPUs utilized
22970.482557 cpu-clock (msec)

hygon 7260

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#perf stat -e branch-instructions,branch-misses,cache-misses,cache-references,cpu-cycles,instructions,stalled-cycles-backend,stalled-cycles-frontend,alignment-faults,bpf-output,context-switches,cpu-clock,cpu-migrations,dummy,emulation-faults,major-faults,minor-faults,page-faults,task-clock,L1-dcache-load-misses,L1-dcache-loads,L1-dcache-prefetches,L1-icache-load-misses,L1-icache-loads,branch-load-misses,branch-loads,dTLB-load-misses,dTLB-loads,iTLB-load-misses,iTLB-loads ./aftersort
10.9479
sum = 314931600000
9,848,123,830 branch-instructions # 898.161 M/sec (26.26%)
496,734 branch-misses # 0.01% of all branches (26.30%)
713,235 cache-misses # 0.336 % of all cache refs (26.34%)
212,455,257 cache-references # 19.376 M/sec (26.37%)
27,277,461,559 cpu-cycles # 2.488 GHz (26.41%)
32,785,270,866 instructions # 1.20 insn per cycle
# 0.58 stalled cycles per insn (26.43%)
19,069,766,918 stalled-cycles-backend # 69.91% backend cycles idle (26.43%)
6,560,109 stalled-cycles-frontend # 0.02% frontend cycles idle (26.42%)
0 alignment-faults # 0.000 K/sec
0 bpf-output # 0.000 K/sec
1,086 context-switches # 0.099 K/sec
10,964.61 msec cpu-clock # 0.999 CPUs utilized
0 cpu-migrations # 0.000 K/sec
0 dummy # 0.000 K/sec
0 emulation-faults # 0.000 K/sec
0 major-faults # 0.000 K/sec
154 minor-faults # 0.014 K/sec
154 page-faults # 0.014 K/sec
10,964.91 msec task-clock # 0.999 CPUs utilized
206,294,123 L1-dcache-load-misses # 1.14% of all L1-dcache hits (26.38%)
18,083,269,173 L1-dcache-loads # 1649.217 M/sec (26.35%)
205,499,292 L1-dcache-prefetches # 18.742 M/sec (26.31%)
749,548 L1-icache-load-misses # 8.67% of all L1-icache hits (26.27%)
8,643,478 L1-icache-loads # 0.788 M/sec (26.25%)
305,577 branch-load-misses # 0.028 M/sec (26.25%)
9,850,674,490 branch-loads # 898.394 M/sec (26.25%)
6,736 dTLB-load-misses # 6.85% of all dTLB cache hits (26.25%)
98,327 dTLB-loads # 0.009 M/sec (26.25%)
114 iTLB-load-misses # 78.62% of all iTLB cache hits (26.25%)
145 iTLB-loads # 0.013 K/sec (26.25%)

#perf stat -e branch-instructions,branch-misses,cache-misses,cache-references,cpu-cycles,instructions,stalled-cycles-backend,stalled-cycles-frontend,alignment-faults,bpf-output,context-switches,cpu-clock,cpu-migrations,dummy,emulation-faults,major-faults,minor-faults,page-faults,task-clock,L1-dcache-load-misses,L1-dcache-loads,L1-dcache-prefetches,L1-icache-load-misses,L1-icache-loads,branch-load-misses,branch-loads,dTLB-load-misses,dTLB-loads,iTLB-load-misses,iTLB-loads ./beforesort
23.3648
sum = 314931600000

9,843,358,378 branch-instructions # 421.186 M/sec (26.26%)
1,156,804,801 branch-misses # 11.75% of all branches (26.28%)
754,542 cache-misses # 0.351 % of all cache refs (26.29%)
215,234,724 cache-references # 9.210 M/sec (26.31%)
58,274,116,562 cpu-cycles # 2.493 GHz (26.33%)
32,850,416,330 instructions # 0.56 insn per cycle
# 0.06 stalled cycles per insn (26.34%)
1,838,222,200 stalled-cycles-backend # 3.15% backend cycles idle (26.34%)
1,187,291,146 stalled-cycles-frontend # 2.04% frontend cycles idle (26.34%)
0 alignment-faults # 0.000 K/sec
0 bpf-output # 0.000 K/sec
2,326 context-switches # 0.100 K/sec
23,370.23 msec cpu-clock # 0.999 CPUs utilized
0 cpu-migrations # 0.000 K/sec
0 dummy # 0.000 K/sec
0 emulation-faults # 0.000 K/sec
0 major-faults # 0.000 K/sec
150 minor-faults # 0.006 K/sec
150 page-faults # 0.006 K/sec
23,370.97 msec task-clock # 0.999 CPUs utilized
207,451,839 L1-dcache-load-misses # 0.82% of all L1-dcache hits (26.34%)
25,180,673,249 L1-dcache-loads # 1077.451 M/sec (26.34%)
205,669,557 L1-dcache-prefetches # 8.800 M/sec (26.34%)
1,725,971 L1-icache-load-misses # 8.12% of all L1-icache hits (26.34%)
21,265,604 L1-icache-loads # 0.910 M/sec (26.34%)
1,157,454,249 branch-load-misses # 49.526 M/sec (26.34%)
9,843,015,406 branch-loads # 421.171 M/sec (26.33%)
22,287 dTLB-load-misses # 7.08% of all dTLB cache hits (26.31%)
314,618 dTLB-loads # 0.013 M/sec (26.29%)
445 iTLB-load-misses # 44.95% of all iTLB cache hits (26.28%)
990 iTLB-loads # 0.042 K/sec (26.26%)

hygon 在这两个场景中排序前比 intel 好了 20%,IPC 好30%,但是指令数多了10%,最关键的也是因为hygon的 branch-load-misses 率较低。

排序后 hygon 略慢10%,主要是指令数多了将近10%。

如果直接将 intel 下 编译好的二进制放到 hygon 下运行,完全可以跑通,指令数也显示和 intel 一样了,但是总时间较在hygon下编译的二进制没有变化

image-20230308145915585

开启 gcc O3 优化

intel 8163

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#perf stat -e branch-instructions,branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,ref-cycles,alignment-faults,context-switches,cpu-clock,cpu-migrations,dummy,emulation-faults,major-faults,minor-faults,page-faults,task-clock,L1-dcache-load-misses,L1-dcache-loads,L1-dcache-stores,L1-icache-load-misses,LLC-load-misses,LLC-loads,LLC-store-misses,LLC-stores,branch-load-misses,branch-loads,dTLB-load-misses,dTLB-loads,dTLB-store-misses,dTLB-stores,iTLB-load-misses,iTLB-loads,node-load-misses,node-loads,node-store-misses,node-stores ./beforesort
2.94
sum = 314931600000

3,268,501,946 branch-instructions # 1109.263 M/sec (10.74%)
226,833 branch-misses # 0.01% of all branches (14.33%)
72,998,727 bus-cycles # 24.774 M/sec (17.90%)
89,636 cache-misses # 34.026 % of all cache refs (21.47%)
263,434 cache-references # 0.089 M/sec (25.03%)
7,301,839,495 cpu-cycles # 2.478 GHz (28.59%)
26,180,809,574 instructions # 3.59 insns per cycle (32.16%)
7,304,150,283 ref-cycles # 2478.880 M/sec (35.73%)
0 alignment-faults # 0.000 K/sec
10 context-switches # 0.003 K/sec
2946.550492 cpu-clock (msec)
7 cpu-migrations # 0.002 K/sec
0 dummy # 0.000 K/sec
0 emulation-faults # 0.000 K/sec
0 major-faults # 0.000 K/sec
370 minor-faults # 0.126 K/sec
370 page-faults # 0.126 K/sec
2946.552985 task-clock (msec) # 1.001 CPUs utilized
73,550,829 L1-dcache-load-misses # 8.97% of all L1-dcache hits (627063379426.55%)
820,264,255 L1-dcache-loads # 278.381 M/sec (627063379426.55%)
6,301 L1-dcache-stores # 0.002 M/sec (627063379426.52%)
344,639 L1-icache-load-misses # 0.117 M/sec (627063379426.51%)
70,181 LLC-load-misses # 84.80% of all LL-cache hits (630745019998.59%)
82,757 LLC-loads # 0.028 M/sec (630529428492.86%)
592 LLC-store-misses # 0.201 K/sec (630313967916.99%)
33,362 LLC-stores # 0.011 M/sec (7.17%)
153,522 branch-load-misses # 0.052 M/sec (10.75%)
3,263,884,498 branch-loads # 1107.696 M/sec (14.33%)
274 dTLB-load-misses # 0.00% of all dTLB cache hits (17.90%)
2,179,821,780 dTLB-loads # 739.787 M/sec (21.47%)
8 dTLB-store-misses # 0.003 K/sec (25.04%)
12,708 dTLB-stores # 0.004 M/sec (28.61%)
59 iTLB-load-misses # 52.68% of all iTLB cache hits (28.60%)
112 iTLB-loads # 0.038 K/sec (28.59%)
5,919 node-load-misses # 0.002 M/sec (28.59%)
1,648 node-loads # 0.559 K/sec (28.58%)
560 node-store-misses # 0.190 K/sec (7.15%)
14 node-stores # 0.005 K/sec (7.14%)

#perf stat -e branch-instructions,branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,ref-cycles,alignment-faults,context-switches,cpu-clock,cpu-migrations,dummy,emulation-faults,major-faults,minor-faults,page-faults,task-clock,L1-dcache-load-misses,L1-dcache-loads,L1-dcache-stores,L1-icache-load-misses,LLC-load-misses,LLC-loads,LLC-store-misses,LLC-stores,branch-load-misses,branch-loads,dTLB-load-misses,dTLB-loads,dTLB-store-misses,dTLB-stores,iTLB-load-misses,iTLB-loads,node-load-misses,node-loads,node-store-misses,node-stores ./aftersort
2.95
sum = 314931600000

3,255,184,180 branch-instructions # 1102.320 M/sec (10.74%)
791,180 branch-misses # 0.02% of all branches (14.35%)
73,001,075 bus-cycles # 24.721 M/sec (17.93%)
520,140 cache-misses # 82.262 % of all cache refs (21.52%)
632,298 cache-references # 0.214 M/sec (25.11%)
7,309,286,959 cpu-cycles # 2.475 GHz (28.69%)
26,120,077,275 instructions # 3.57 insns per cycle (32.28%)
7,316,568,954 ref-cycles # 2477.649 M/sec (35.86%)
0 alignment-faults # 0.000 K/sec
10 context-switches # 0.003 K/sec
2953.027151 cpu-clock (msec)
3 cpu-migrations # 0.001 K/sec
0 dummy # 0.000 K/sec
0 emulation-faults # 0.000 K/sec
0 major-faults # 0.000 K/sec
370 minor-faults # 0.125 K/sec
370 page-faults # 0.125 K/sec
2953.029425 task-clock (msec) # 1.001 CPUs utilized
73,778,174 L1-dcache-load-misses # 8.94% of all L1-dcache hits (625801033059.49%)
825,038,324 L1-dcache-loads # 279.387 M/sec (625693600466.98%)
6,137 L1-dcache-stores # 0.002 M/sec (625693600466.94%)
339,275 L1-icache-load-misses # 0.115 M/sec (625693600466.87%)
7,611 LLC-load-misses # 52.34% of all LL-cache hits (625693600466.22%)
14,542 LLC-loads # 0.005 M/sec (625693600466.18%)
975 LLC-store-misses # 0.330 K/sec (625718826721.74%)
28,542 LLC-stores # 0.010 M/sec (7.17%)
150,256 branch-load-misses # 0.051 M/sec (10.75%)
3,260,765,171 branch-loads # 1104.210 M/sec (14.33%)
84 dTLB-load-misses # 0.00% of all dTLB cache hits (17.91%)
2,177,927,665 dTLB-loads # 737.523 M/sec (21.48%)
0 dTLB-store-misses # 0.000 K/sec (25.05%)
12,502 dTLB-stores # 0.004 M/sec (28.62%)
10 iTLB-load-misses # 5.62% of all iTLB cache hits (28.61%)
178 iTLB-loads # 0.060 K/sec (28.60%)
14,538 node-load-misses # 0.005 M/sec (28.59%)
1,527 node-loads # 0.517 K/sec (28.62%)
2,339 node-store-misses # 0.792 K/sec (7.18%)
0 node-stores # 0.000 K/sec (7.14%)

可以看到 O3 优化后是否排序执行时间差不多,并且都比没有 O3 前的快几倍,指令数较优化前基本不变。

最明显的是排序前的 branch-load-misses 几乎都被优化掉了,这也导致 IPC 从0.41 提升到了3.59

aarch64 M710

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#perf stat -e branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,stalled-cycles-backend,stalled-cycles-frontend,alignment-faults,bpf-output,context-switches,cpu-clock,cpu-migrations,dummy,emulation-faults,major-faults,minor-faults,page-faults,task-clock,L1-dcache-load-misses,L1-dcache-loads,L1-icache-load-misses,L1-icache-loads,LLC-load-misses,LLC-loads,branch-load-misses,branch-loads,dTLB-load-misses,dTLB-loads,iTLB-load-misses,iTLB-loads  ./beforesort
1.19468
sum = 314931600000

178,045 branch-misses (29.84%)
3,290,281,574 bus-cycles # 2748.321 M/sec (29.84%)
204,017,139 cache-misses # 24.768 % of all cache refs (29.84%)
823,700,482 cache-references # 688.024 M/sec (29.84%)
3,290,247,311 cpu-cycles # 2.748 GHz (34.85%)
5,730,855,778 instructions # 1.74 insn per cycle
# 0.26 stalled cycles per insn (34.85%)
1,485,014,712 stalled-cycles-backend # 45.13% backend cycles idle (35.03%)
980,441 stalled-cycles-frontend # 0.03% frontend cycles idle (35.08%)
0 alignment-faults # 0.000 K/sec
0 bpf-output # 0.000 K/sec
2 context-switches # 0.002 K/sec
1,197.20 msec cpu-clock # 1.000 CPUs utilized
0 cpu-migrations # 0.000 K/sec
0 dummy # 0.000 K/sec
0 emulation-faults # 0.000 K/sec
0 major-faults # 0.000 K/sec
140 minor-faults # 0.117 K/sec
140 page-faults # 0.117 K/sec
1,197.20 msec task-clock # 1.000 CPUs utilized
205,399,817 L1-dcache-load-misses # 25.00% of all L1-dcache accesses (35.08%)
821,607,081 L1-dcache-loads # 686.276 M/sec (35.08%)
10,361 L1-icache-load-misses # 0.00% of all L1-icache accesses (35.08%)
1,150,511,080 L1-icache-loads # 961.004 M/sec (30.07%)
6,275 LLC-load-misses # 0.00% of all LL-cache accesses (30.07%)
0 LLC-loads # 0.000 K/sec (30.07%)
103,368 branch-load-misses # 0.086 M/sec (30.07%)
821,524,106 branch-loads # 686.206 M/sec (30.07%)
15,315 dTLB-load-misses # 0.00% of all dTLB cache accesses (30.07%)
821,589,564 dTLB-loads # 686.261 M/sec (30.07%)
1,084 iTLB-load-misses # 0.07% of all iTLB cache accesses (30.07%)
1,613,786 iTLB-loads # 1.348 M/sec (29.89%)


#perf stat -e branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,stalled-cycles-backend,stalled-cycles-frontend,alignment-faults,bpf-output,context-switches,cpu-clock,cpu-migrations,dummy,emulation-faults,major-faults,minor-faults,page-faults,task-clock,L1-dcache-load-misses,L1-dcache-loads,L1-icache-load-misses,L1-icache-loads,LLC-load-misses,LLC-loads,branch-load-misses,branch-loads,dTLB-load-misses,dTLB-loads,iTLB-load-misses,iTLB-loads ./aftersort
1.1949
sum = 314931600000

656,175 branch-misses (29.91%)
3,293,615,450 bus-cycles # 2748.397 M/sec (29.91%)
203,683,518 cache-misses # 24.631 % of all cache refs (29.91%)
826,934,774 cache-references # 690.046 M/sec (29.91%)
3,293,560,111 cpu-cycles # 2.748 GHz (34.92%)
5,732,241,288 instructions # 1.74 insn per cycle
# 0.29 stalled cycles per insn (34.91%)
1,645,938,192 stalled-cycles-backend # 49.97% backend cycles idle (35.00%)
1,757,056 stalled-cycles-frontend # 0.05% frontend cycles idle (35.05%)
0 alignment-faults # 0.000 K/sec
0 bpf-output # 0.000 K/sec
2 context-switches # 0.002 K/sec
1,198.38 msec cpu-clock # 1.000 CPUs utilized
0 cpu-migrations # 0.000 K/sec
0 dummy # 0.000 K/sec
0 emulation-faults # 0.000 K/sec
0 major-faults # 0.000 K/sec
137 minor-faults # 0.114 K/sec
137 page-faults # 0.114 K/sec
1,198.38 msec task-clock # 1.000 CPUs utilized
205,557,180 L1-dcache-load-misses # 25.00% of all L1-dcache accesses (35.05%)
822,366,213 L1-dcache-loads # 686.233 M/sec (35.04%)
12,708 L1-icache-load-misses # 0.00% of all L1-icache accesses (35.05%)
987,422,733 L1-icache-loads # 823.967 M/sec (30.04%)
6,234 LLC-load-misses # 0.00% of all LL-cache accesses (30.04%)
0 LLC-loads # 0.000 K/sec (30.04%)
103,635 branch-load-misses # 0.086 M/sec (30.04%)
822,357,251 branch-loads # 686.226 M/sec (30.04%)
13,961 dTLB-load-misses # 0.00% of all dTLB cache accesses (30.04%)
822,374,897 dTLB-loads # 686.241 M/sec (30.04%)
709 iTLB-load-misses # 0.05% of all iTLB cache accesses (30.04%)
1,562,083 iTLB-loads # 1.303 M/sec (29.96%)

可以看到在M710上开启 O3 优化后是否排序执行时间差不多,并且都比没有 O3 前

的快几倍,最明显的是指令数只有之前的7%。另外就是排序前的 branch-load-misses 几乎都被优化掉了,虽然这里 IPC 提升不大但主要在指令数的减少上。

O3意味着代码尽可能展开,更长的代码意味着对 L1i(以及 L2和更高级别)高速缓存的压力更高。这会导致性能降低。更短的代码可以运行得更快。幸运的是,gcc 有一个优化选项可以指定此项。如果使用-Os,则编译器将优化代码大小。使用后,能够增加代码大小的哪些优化将被禁用。使用此选项通常会产生令人惊讶的结果。特别是循环展开和内联没有实质优势时,那么此选项将是一个很好的选择。

分支预测原理介绍

img

如上图的上面部分代表通常情况下的简单代码布局。如果区域 B(这里是内联函数 inlfct 生成的代码)经常由于条件 I 被跳过,而不会执行,处理器的预取将拉入很少使用的包含块 B 的高速缓存行。使用块重新排序可以改变这种局面,改变之后的效果可以在图的下半部分看到。经常执行的代码在内存中是线性的,而很少执行的代码被移动到不会损害预取和 L1i 效率的位置。

Linux内核流水线优化案例

在Linux Kernel中有大量的 likely/unlikely

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//ip 层收到消息后,如果是tcp就调用tcp_v4_rcv作为tcp协议的入口
int tcp_v4_rcv(struct sk_buff *skb)
{
...
if (unlikely(th->doff < sizeof(struct tcphdr) / 4))
goto bad_packet; //概率很小
if (!pskb_may_pull(skb, th->doff * 4))
goto discard_it;

//file: net/ipv4/tcp_input.c
int tcp_rcv_established(struct sock *sk, ...)
{
if (unlikely(sk->sk_rx_dst == NULL))
......
}

//file: include/linux/compiler.h
#define likely(x) __builtin_expect(!!(x),1)
#define unlikely(x) __builtin_expect(!!(x),0)

__builtin_expect 这个指令是 gcc 引入的。该函数作用是允许程序员将最有可能执行的分支告诉编译器,来辅助系统进行分支预测。(参见 https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html)

它的用法为:__builtin_expect(EXP, N)。意思是:EXP == N的概率很大。那么上面 likely 和 unlikely 这两句的具体含义就是:

  • __builtin_expect(!!(x),1) x 为真的可能性更大 //0两次取反还是0,非0两次取反都是1,这样可以适配__builtin_expect(EXP, N)的N,要不N的参数没法传
  • __builtin_expect(!!(x),0) x 为假的可能性更大

当正确地使用了__builtin_expect后,编译器在编译过程中会根据程序员的指令,将可能性更大的代码紧跟着前面的代码,从而减少指令跳转带来的性能上的下降。让L1i中加载的代码尽量有效紧凑

这样可以让 CPU流水线分支预测的时候默认走可能性更大的分支。如果分支预测错误所有流水线都要取消重新计算。

如果程序员利用这些宏,然后使用 -freorder-blocks 优化选项,则 gcc 将对块进行重新排序,如原理解图所示。该选项在 -O 2中启用,但在-Os 中禁用。还有另一种对块进行重新排序的选项(-freorder-blocks-and-partition ),但是它的用处有限,因为它不适用于异常处理。

总结

不排序的代码(分支极难预测正确)运行数据对比:

branch-load-misses/branch-loads instructions IPC 耗时(秒) 排序后耗时(秒)
鲲鹏920 21.7% 83,694,841,472 1.04 30.92 11.44
M710 8.3% 77,083,625,280 1.66 16.89 8.20
Intel 8163 24.4% 29,618,485,912 0.41 29.52 9.77
hygon 7260 11.8% 32,850,416,330 0.56 23.36 10.95
FT S2500 24% 83,872,213,462 1.01 39.8 16.63

排序后的代码(分支预测容易)运行数据对比:

instructions instructions(排序前) IPC 耗时(秒)
鲲鹏920 83,666,813,837 83,694,841,472 2.82 11.44
M710 77,068,271,833 77,083,625,280 3.42 8.20
Intel 8163 29,491,804,977 29,618,485,912 1.22 9.77
hygon 7260 32,785,270,866 32,850,416,330 1.20 10.95
FT S2500 83,918,069,835 83,872,213,462 2.41 16.63
  • 所有 CPU 都期望对分支预测友好的代码
  • 分支预测重点关注 perf branch-load-misses/branch-loads
  • aarch64 较 x86_64 指令数是2.6倍,同时对流水线更友好,也就是 IPC 更高(2.6倍),测试代码单线程、无锁
  • M710的分支预测正确率是鲲鹏920、intel的3倍,hygon 是鲲鹏 、intel的分支预测率的2倍
  • 10% 的分支load miss 会带来一倍的性能差异
  • gcc O3 优化效果很明显,代价就是代码展开后很大,容易造成icache不够,对小段测试代码效果最好,实践不一定
  • 测试代码只是极简场景,实际生产环境更复杂,也就是预测效果不会这么明显

为什么你不去看文档

起因

在推特上看到这张图片,我觉得很好,但是评论里面都在说:这是国内特有的现状。

好像国外就不会这样一样,实际我的看法是国外也是这个鸟样子

two_queue

我的看法

这可不只是国内的问题,medium/reddit 上最受欢迎的都是10个坑、5个技巧、3个必知这类文章。其实原因我反倒是很理解,官方文档通篇读下来感觉都看了但是有点不得要领,典型就是看完还是解决不了问题。XX系列简明直接要害,多搞清楚几个XX系列后再看官方手册那感觉完全不一样

为什么会这样

大多人没有能力只看知识就会掌握并解决问题,这就是为什么我们学数理化,知道了那些公式还是不会解题(有极少数人会),而是需要老师手把手多讲解几道习题,把习题里面如何套用公式给大家示范并解决问题。

我们学程序相关知识也是这样,只看官方手册大多数人也解决不了问题,但是如果看10个坑、8个技巧后基本会解决一些问题了,如果这个时候我们再去看手册就会发现看起来有感觉多了。

读书的时候有老师带我们解题,做程序员后就只能靠自己了,实际这些XX系列就是我们最好的老师,但最终要记住靠XX系列入门后还是要回到文档、手册、帮助上来。

究其原因总结下来可以把学习分成工程效率、知识效率

但是我们最容易犯的错误就是:没有知识效率能力确犯了知识效率的毛病。看到知识一看就懂,但实际一用就懵这才是我们的常态

image-20230406203738548

什么是工程效率,什么是知识效率

有些人纯看理论就能掌握好一门技能,不实践还能在脑海里举一反三,这是知识效率,这种人非常少;

大多数普通人都是看点知识然后结合实践来强化理论,要经过反反复复才能比较好地掌握一个知识,这就是工程效率,讲究技巧、工具来达到目的。

肯定知识效率最牛逼,但是拥有这种技能的人毕竟非常少(天生的高智商吧)。从小我们周边那种不怎么学的学霸型基本都是这类,这种学霸都还能触类旁通非常快的掌握一个新知识,非常气人。剩下的绝大部分只能拼时间+方法+总结等也能掌握一些知识

我自己就是工程效率型,只能羡慕那些知识效率型的学霸。所以我花了长时间去总结他们的差异,在程序员这个领域学会了用案例去学习,也就是深度学习深挖一个案例,通过案例来学习背后的知识,这种方式极大的好处就是学得牢固,并且通过案例掌握的知识点就像一根长长的钉子一样,深深地插入你的记忆里。再然后去看XX官方手册就会发现轻松多了。同时经过多个案例锤炼后举一反三也是积极自然。

使劲挖掘自己在知识效率型方面的能力吧,两者之间当然没有明显的界限,知识积累多了逻辑训练好了在别人看来你的智商就高了

其他想说的

看完故事升华一下方法论:如何在工作中学习

如果你觉得看完对你很有帮助可以通过如下方式找到我

find me on twitter: @plantegg

知识星球:https://t.zsxq.com/0cSFEUh2J

开了一个星球,在里面讲解一些案例、知识、学习方法,肯定没法让大家称为顶尖程序员(我自己都不是),只是希望用我的方法、知识、经验、案例作为你的垫脚石,帮助你快速、早日成为一个基本合格的程序员。

争取在星球内:

  • 养成基本动手能力
  • 拥有起码的分析推理能力–按我接触的程序员,大多都是没有逻辑的
  • 知识上教会你几个关键的知识点
image-20230407232314969

为什么你有知识但没有能力

起因

有同学想抓一下访问 baidu.com 的流量,然后分析学习,抓完包后想过滤只看 baidu.com 的流量,减少干扰,于是他在 Wireshark 里面用上了过滤条件: http.host eq “baidu.com” 但是没有过滤到任何包,所以他带着这个问题来问我了

如下图是他的过滤结果:

img

多说一句,要是我我就只留一个条件来提问:http.host eq “baidu.com”

看到这个问题,虽然我从来没有用过 http.host 这种过滤方式,但我大概猜到了原因,所以我先找了一个政府网站(他们是为数不多还在用 http 的网站),然后我轻松用同样的方式正确过滤到了我要的包:

image-20230402200619645

于是我回复他:

第一,你不应该搞一堆条件,不好调试;最简单用一个条件过滤验证

第二,为什么你百度过滤不了,我想留给你自己去看书、想一想,如果不行一周后我再告诉你答案。自己琢磨出来会让你的正向激励跟吸du一样更嗨,唾手可得的答案不符合本星球希望帮助成员达到无招胜有招的目的,知识是学不完的,总有你不会,但是分析能力、解决问题的能力才是我们要可以去训练,最终你要达到把你丢到一个不懂的领域你很快可以解决问题

其实我是想引导他自己分析解决问题。

我认为这个同学能动手去抓包分析,学习的劲头已经有了,居然会 http.host 这种用法,这是我第一次看到这么用(我平时不和 http 打交道,别鄙视我),我想他肯定知道https

但是为什么他知道这些知识但是在实践中什么阻碍了他把学到的知识和他碰到这个头疼的问题联系不起来呢?

有知识但没有能力

我以前在《如何在工作中学习》就讨论过这种情况,如图

image-20230402201307165

显然,这次这位同学的只是具备了,但是没有转化成能力,也就虽然我们都学了TCP、HTTP、HTTPS这些信息,但是没有理解透彻,更具体一点没有把 HTTPS,这层 TLS 工作结构就没理解清楚,TLS 把你原来的 http host 都给加密了,你自然没法按原来的方式过滤。

如下图,这是加密后的结构,你是没法知道TCP 里面是http/redis还是MySQL协议的,如果你要理解了TLS直接作用在TCP(四层),而http这种七层协议哪还有说话的空间啊?

image-20230402201725641

总结

不要总是抱怨学不会、学不懂,你就是思考的稍微少一点、浅一点。

不要总是抱怨自己10年工作经验实践下来还不如一年的新手,同上!

思维方式是最难改变的,但是是最重要的。

如果你觉得看完对你很有帮助可以通过如下方式找到我

find me on twitter: @plantegg

知识星球:https://t.zsxq.com/0cSFEUh2J

开了一个星球,在里面讲解一些案例、知识、学习方法,肯定没法让大家称为顶尖程序员(我自己都不是),只是希望用我的方法、知识、经验、案例作为你的垫脚石,帮助你快速、早日成为一个基本合格的程序员。

争取在星球内:

  • 养成基本动手能力
  • 拥有起码的分析推理能力–按我接触的程序员,大多都是没有逻辑的
  • 知识上教会你几个关键的知识点
image-20230407232314969
0%