plantegg

java tcp mysql performance network docker Linux

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

以上四个案例中的三个都给出了完整的重现环境、配置、重现步骤、抓包分析、日志结果、现场截图等等资料,保证100%重现问题并带你进行分析,让你感受实际工作一样的场景,真正做到学透一个案例顶3年工作经验

image-20230607085652520

【关于案例】

本星球剖析各种程序员疑难经典案例,搞清楚一个案例基本能横扫一个领域,其次在一个案例后再带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

一年感悟

用心做了一年知识星球后总结了一个我才懂的道理:发现大部分情况下看文章或者真实案例还是不够(更别提看教材了,教材更抽象),大部分人即使当时看明白文章、案例了,事后还是不会用发懵

那经过这一年的总结怎么解决这个问题呢?我的摸索是:一定要把案例重现出来,把现场交给大家。通过这样三步:

\1. 看纸上案例,以及别人怎么分析、诊断

\2. 到自己跟着重现这个现象,自己试试去把案例里的分析步骤走一遍

\3. 最后通过视频或者直播我再演示一遍

这样才是真正和你经历了一样,但是这个过程完美了吗?不,人都是懒惰的,畏惧撘环境,碰到一点坑就掉里面出不来了,还怎么重现、诊断?

所以对于撘环境我也总结了一条经验:大家都买个一样的 ECS,OS 镜像也一样,这回老师学生就对齐(黑化)了操作环境,所以只要我把重现步骤写清楚基本你就能重现出来

剩下的就是跟着做了,如果这样你还学不会,那我就要退出江湖了,明年到期直接把星球关了

最后我准备了一张优惠券送给大家(or 链接复制到微信里打开有优惠券:https://t.zsxq.com/18PM8m2ln ),强烈建议你试试

image-20240324161113874

讲个笑话:如果你问一个老程序员,在你的系统上看到超多 CLOSE_WAIT 的连接,大概99%的会建议你去改:tcp_tw_reuse 还不行再改tcp_tw_recycle,然后让你改 tcp_timestamps、tcp_max_tw_buckets等等,这老工程师懂得还真多、但又屁都不懂

【加入星球】

知识星球:https://t.zsxq.com/0cSFEUh2J,在这里有600多成员等着你和你一起分享案例

image-20230607090024270

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

前言

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

最后的分析用到了我们的抓包大法+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

比较不同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官方手册就会发现轻松多了。同时经过多个案例锤炼后举一反三也是积极自然。

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

其他想说的

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

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

起因

有同学想抓一下访问 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年工作经验实践下来还不如一年的新手,同上!

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

nginx性能和软中断

问题

  • 如何调整软中断才能达到最优性能?
  • 通过 top 观察软中断 和 si%、sy% 的关系

测试机型

双路 Intel(R) Xeon(R) CPU E5-2682 v4 sh

两块万兆网卡:Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection (rev 01)

内核:3.10.0-327

1
2
NUMA node0 CPU(s):     0-15,32-47
NUMA node1 CPU(s): 16-31,48-63

软中断和 si%

压nginx 碰到一个奇怪的问题,将软中断绑到48-63核,如果nginx绑到这个socket下的其它核比如 16-23,我就基本上看不到 si% 的使用率;如果所有条件都不变我将nginx 绑0-7core(另外一个socket),那么我能看到0-7 core上的软中断 si%使用率达到600%以上(8core累加)。 si%使用率应该只和 PPS、流量相关,这个测试中不同绑核nginx的QPS 差了20%以内。

image-20221031152031791image-20221031152044825

CPU是intel E5,网卡插在node0上

1
2
3
4
5
Model name:            Intel(R) Xeon(R) CPU E5-2682 v4 @ 2.50GHz
NUMA node0 CPU(s): 0-15,32-47
NUMA node1 CPU(s): 16-31,48-63

软中断绑定:IRQBALANCE_BANNED_CPUS=0000ffff,ffffffff

默认业务进程调用内核软中断do_softirq等来处理收发包,不需要跨core,如果将软中断绑定到具体的core后,会触发ksoftirqd 来调用do_softirq来处理收发包,整体上肯定效率不如同一个core处理业务和软中断的效率高。进一步如果软中断跨socket绑定导致处理时长进一步升高、总效率更差

https://askubuntu.com/questions/7858/why-is-ksoftirqd-0-process-using-all-of-my-cpu

image-20221101113948809

下图场景下,收包没有占用 si,而是占用的 sy

image-20221101114217738

将软中断和业务进程拆开绑核,均将软中断、业务基本压满的情况下,如果软中断在本node,QPS 增加20%+

软中断打满单核后的IPC:

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
#perf stat --cpu 29  //软中断所在core,si%=100%,和业务以及网卡跨node
Performance counter stats for 'CPU(s) 29':

4470.584807 task-clock (msec) # 1.001 CPUs utilized (100.00%)
252 context-switches # 0.056 K/sec (100.00%)
8 cpu-migrations # 0.002 K/sec (100.00%)
3 page-faults # 0.001 K/sec
11,158,106,237 cycles # 2.496 GHz (100.00%)
<not supported> stalled-cycles-frontend
<not supported> stalled-cycles-backend
7,976,745,525 instructions # 0.71 insns per cycle (100.00%)
1,444,740,326 branches # 323.166 M/sec (100.00%)
7,073,805 branch-misses # 0.49% of all branches

4.465613433 seconds time elapsed

#perf stat --cpu 1 //软中断所在core,si%=100%,和业务以及网卡跨node
Performance counter stats for 'CPU(s) 1':

5132.639092 task-clock (msec) # 1.002 CPUs utilized (100.00%)
1,119 context-switches # 0.218 K/sec (100.00%)
6 cpu-migrations # 0.001 K/sec (100.00%)
0 page-faults # 0.000 K/sec
12,773,996,227 cycles # 2.489 GHz (100.00%)
<not supported> stalled-cycles-frontend
<not supported> stalled-cycles-backend
12,457,832,798 instructions # 0.98 insns per cycle (100.00%)
2,243,820,953 branches # 437.167 M/sec (100.00%)
12,769,358 branch-misses # 0.57% of all branches

5.124937947 seconds time elapsed

Nginx业务进程的IPC

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
#perf stat -p 30434   //软中断跨node

Performance counter stats for process id '30434':

6838.088642 task-clock (msec) # 0.953 CPUs utilized (100.00%)
19,664 context-switches # 0.003 M/sec (100.00%)
0 cpu-migrations # 0.000 K/sec (100.00%)
4 page-faults # 0.001 K/sec
17,027,659,259 cycles # 2.490 GHz (100.00%)
<not supported> stalled-cycles-frontend
<not supported> stalled-cycles-backend
14,315,679,297 instructions # 0.84 insns per cycle (100.00%)
2,919,774,303 branches # 426.987 M/sec (100.00%)
34,643,571 branch-misses # 1.19% of all branches

7.176493377 seconds time elapsed

#perf stat -p 30434 //软中断和nginx、网卡在同一node
^C
Performance counter stats for process id '30434':

5720.308631 task-clock (msec) # 0.979 CPUs utilized (100.00%)
11,513 context-switches # 0.002 M/sec (100.00%)
1 cpu-migrations # 0.000 K/sec (100.00%)
0 page-faults # 0.000 K/sec
14,234,226,577 cycles # 2.488 GHz (100.00%)
<not supported> stalled-cycles-frontend
<not supported> stalled-cycles-backend
14,741,777,543 instructions # 1.04 insns per cycle (100.00%)
3,009,021,477 branches # 526.024 M/sec (100.00%)
35,690,882 branch-misses # 1.19% of all branches

5.845534744 seconds time elapsed

如果将nginx绑到node1(和网卡分开),同样再将软中断绑到node0、node1上,这个时候同样是软中断和业务在同一node性能要好,也就是软中断要和业务在一个node和网卡在哪里没关系。

网络包收发涉及两块内存分配:描述符(指针)和data buffer(存放网络包数据);

网卡的描述符、data buffer申请的内存都在设备所在的numa上, 如果将队列的中断绑定到其他cpu上, 那么队列申请的data buffer的节点也会跟着中断迁移,但是描述符是和网卡所在的node绑定不会迁移的。

Top 看到的 ksoftirqd 占用cpu不高,但是去看对应的 CPU core si消耗比较高,这是因为 ksoftirqd 只是触发软中断后的入口,进而会调用do_softirq/net_rx_action 等内核函数,在 si% 的消耗中包含了这些被调用的消耗

总结

  • 软中断绑定优先让irqbalance自己决定,默认系统倾向于自动在业务中调用软中断,代价最低

  • 尽量不要让包溢出net.core.netdev_budget,溢出后触发ksoftirqd 来处理效率更低

  • 尽量控制不要让 ksoftirqd 打满,所以可以绑定更多core来

微博备份

原因:2022 10月的时候微博被封杀了,之前的重点内容做下备份,评论、转发就没有了

马克思是搞阶级斗争的;列宁是搞革命的;对的,他们就是没有能力治国、搞经济。你见过哪个唯马、列独尊的制度下搞好了的?改革前我们也没搞好,后来有了邓放弃阶级斗争,提出白猫黑猫、科学是第一生产力,放弃马和列的那套阶级斗争,才迎来40年的发展。到了江这里他自评做了三件大事,其中两件是:将邓小平理论写入党章(不要搞斗争,好好搞经济大家都有饭吃),另外就是三个代表,团结一切力量和阶层,尤其是科学(臭老九),真正地让党从斗争转入了治国的轨道上,从此也就不应该有那么多敌对势力了

nginx sys CPU消耗到95%这个是非常不正常的,似乎测试的是短连接,那么惊群问题很严重。打开reuseport看看(listen 80 reuseport;)我测试8163CPU单core压index页面能到7万 QPS,期待定制系统比nginx性能好。反过来想你的Gateway用了70% US CPU在和nginx 5%的US比(bypass的话忽略这句)

经常被问到Apple M1的购买建议,以及M1和Intel 12代谁强,于是我跑到Apple官网查了下,发现性价比超高的一款M1,如图1

M1 Pro都是10+16核的(图二),突然出来一个8+14核不太正常,就两个核的差异再搞条生长线一起生产良品率都不高,并且10核中有很多次品,比如坏掉了1C还剩下9C你扔掉还是?于是聪明的工程师设计的时候就做好了软开关,台积电下线后检测出略微坏的就尽量当8c卖,提升总的良品率降低成本,所以你看看这款的差价其实买到就是赚到.

说回芯片成本,Die(裸片,一般大拇指指甲大,你买到的都是封装后的火柴盒大)越大良品率越低成本越高如图三(发热控制另说),Intel也一直这么干,如图四,拿出大拇指感受下Die的大小,注意里面L3的大小,现在的CPU cache大小超过了Die一半的面积了,下次说这个。

Intel 无论哪代的I5、I7、I9基本都是一条生产线在玩关核的把戏,Die的面积都是 215.25 mm²,详细参数参考这里https://en.wikichip.org/wiki/intel/core_i5/i5-12600k ,如图5,把I9放到显微镜下看到如图6

购买建议如图8

N年前我刚加入一家公司几个月,有一个客户购买了我们的产品上线后金额对不上(1类生产事故),于是经理带着我们几个技术去现场看看是什么原因,路上经理说你们不要有什么心理压力,我不懂技术但是我过去就是帮助你们挨骂的,我好好跪在客户那你们好好安心解决问题。

问题大概就是客户代码在一段事务中,但是提交到后端我们的服务上后前对不上了,客户认为我们产品事务有问题。

到了现场客户不让下载他们代码,只能人肉趴在他们指定的机器上用眼睛看问题在哪里,看了三天大家非常沮丧地回来了,自然产品被下线,客户直接用MySQL了,但是三天后一个振奋人心的消息传过来了:金额还是对不上 ……

于是我们再度派出技术人员帮他们看为什么(这次客户配合度高了很多),最后有个同事提了一嘴tcpdump抓个包看看,到底应用代码有没有set autocommit=0, 半个小时后传来喜讯用户代码发出的就是autocommit=1,说明用户代码的事务配置没生效。

最后查出来配置文件中有中文注释,而生产环境机器不支持中文出现了乱码,导致事务没有生效!

事情还没完,当我听到这个结果后恨不得实际抽自己,tcpdump咱也会用,怎么当时就没想到呢!于是后来我天天看tcpdump、分析网络包,有段时间最开心的是在酒店看书了。一个月后写了几篇文章放在公司内网,再然后公司内部各个团队开始拿着各种问题找过来,我的case也越来越多,结果呢我内心自我认为阿满老师去了西半球后是不是东半球抓包我最牛了 :)

有一次产品调用是这样的 1->2->3->4->5->6 产品5是我们的,1说性能上不去,rt太大,扯了两天皮,然后说5有问题,于是我到5上抓了个包,明确告诉他们5的rt是多少,压力还没有到5这里来,另外按照我抓包结果的rt分析,5的能力是20万,现在还不到1万,瓶颈在1-5之间,后来我上1/2/3/4用 netstat 分别看下网络状态发现1-2之间网络到了瓶颈(2回包给1的时候大量的包no ack),不要怀疑netstat真有这么强大,只是你不会看而已。如图三 2上的9108服务端口给1发回结果的时候1那边迟迟不给ack。其实这个case用好工具只是很小的一点,关键的是我能抓包分析出rt,然后从rt推断出系统的能力(别说全链路监控之类的,有时候还得拼刺刀),进而快速定位到瓶颈

现在我们的产品文档必备一份tcpdump、tshark(wireshark命令行版本)救急命令箱,有时候让客户复制粘贴执行后给我们某个结果,好多问题不再是问题了,如图1/2

网络这个卡点是在一个复杂、长链路的系统中非常关键的点,大家都认网络数据(抓包数据),可信度比日志高多了,除了鹰眼之类的全链路监控外,可以在Kernel的网络模块中插入代码记下网络收包、回包的时间点(大概20-30行代码),然后监控系统分析内核吐出来的日志形成监控数据。这样一个不侵入应用、0代码实现的完美监控就有了,其实不算完美,因为这种做法只能监控到同步请求一来一回的RT。当然对我们来说就够了,上线后好多次都是通过这个系统进行完美甩锅(快速发现问题)

一次听风扇声音来定位性能瓶颈

问题描述背景

在一次POC测试过程中,测试机构提供了两台Intel压力机来压我们的集群

  • 压力机1:两路共72core intel 5XXX系列 CPU,主频2.2GHz, 128G内存
  • 压力机2:四路共196core intel 8XXX系列 CPU,主频2.5GHz, 256G内存 (8系列比5系列 CPU的性能要好、要贵)

从CPU硬件指标来看压力机2都是碾压压力机1,但是实际测试是压力机2只能跑到接近压力机1的能力,两台机器CPU基本都跑满,并且都是压测进程消耗了90%以上的CPU,内核态消耗不到5%CPU

所以问题就是为什么196core没打过72core,关键是CPU都还用完了

机器在客户环境缺网络、缺各种工具(连perf都没有),于是只能趴在机箱上听风扇声音,两台机器都听了1分钟,我觉察到了问题,196core机器的CPU风扇声音更小,说明196core的CPU出工不出力,大概是流水线在频繁地Stall。

知识点:通过top看到CPU在运行,但是在芯片内可不一定是真正在running。比如执行一条指令,需要读取数据,如果数据没在cache中那么需要到内存中取进来,这个时候CPU就会休息(放电、降温),这个就叫Stall

于是做了个读写内存的带宽和时延测试,得到如下数据:

72core机器, 本路时延1.1,跨路时延1.4,因为是2路所以有50%的概率跨路,性能下降30%,查到内存条速度2900

196core机器,本路时延1.2,跨路时延1.85,因为是4路所以有75%的概率跨路,性能下降50%,查到内存条速度2100

赶快给196core机器换上2900的内存条速度一下子就上去了,同时这多路服务器不能这么用,要在每一路上起一个实例,不要让内存跨路访问,速度又是几十个个点的提升

面试官为什么喜欢问算法题(算法岗除外):本质就是对招人方成本最低!

面试官和候选人很难在大部分技术点上match,也就是候选人擅长的面试官问不出深浅;面试官擅长的候选人不一定懂问了也白问。这个时候上来几道力扣算法题最轻松了,只要认识字的面试官都能看出来候选人会不会,当然面试官也没法追问候选人是刷题了还是真现场想出来的(真正现场想出来的应该不会超过5%吧),一般不敢追问怕被反杀:( 因为面试官也是背的答案

这个成本低的本质则是面试官水平不行、或者面试官想偷懒。

什么样的面试官想偷懒?一上来让你自我介绍,然后闷头看简历的,基本都是没做任何准备趁着你自我介绍的时间赶紧看两眼你的简历。好的面试官会提前看简历,针对性地准备好几个问题,然后上来只需要寒暄几句暖场一下从简历上擅长的技术或者项目开始问(最好从简单的,让候选人先把气场打开)。

为什么好的面试官不多呢?成本太高,准备好问题、读完你的博客结果电话一通候选人不感兴趣 :) 慢慢地大家都开始往节省成本的方向靠近

好的面试行为:

  • 提前看几遍简历,针对性地准备好问题
  • 简历有博客的,去博客中看看
  • 上来寒暄下,从候选人最熟悉的地方开始发问
  • 针对一些技术点问到候选人答不出来,这样能看出候选人的深浅
  • 场景式面试法,什么场景下、问题是什么、做了什么(如何做)、得到了什么结果
  • 候选人表示不太熟悉的就不要追问了

江湖大佬无招胜有招的故事(如何在不懂领域解决问题起来胜过该领域的工程师)

这位同学从chinaren出道,跟着王兴一块创业5Q,5Q在学校靠鸡腿打下大片市场和校内网竞争,最后被陈一舟的校内收购(据说被收购后5Q的好多技术都走了,最后王兴硬是呆在校内网把合约上的所有钱都拿到了–收购合约的钱都是分期付款)。

在我们公司负责技术(所有解决不了的问题都找他),这位同学让我最佩服的解决问题的能力,好多问题其实他也不一定就擅长,但是他就是有本事通过man、Help、Google不停地验证尝试、分析就把一个不熟悉的问题给解决了,这是我最羡慕的能力。

案例:应用刚启动连接到MySQL数据库的时候比较慢,但又不是慢查询,对这个问题有如下几种解决方案:

  1. 这位同学的解决办法是通过tcpdump来分析网络通讯包,看具体卡在哪个步骤,把这个问题硬生生地给找到了。
  2. 如果是专业的DBA可能会通过show processlist 看具体连接在做什么,比如看到这些连接状态是 authentication 状态,然后再通过Google或者对这个状态的理解知道创建连接的时候MySQL需要反查IP、域名这里比较耗时,通过配置参数 skip-name-resolve 跳过去就好了。
  3. 如果是MySQL的老司机,一上来就知道 skip-name-resolve 这个参数要改改默认值。

在我眼里这三种方式都解决了问题,最后一种最快但是纯靠积累和经验,换个问题也许就不灵了;第一种方式是最牛逼和通用的,只需要最少的业务知识+方法论就可以更普遍地解决各种问题。

每次碰到问题我尽量让他在我的电脑上来操作,解决后我再自己复盘,通过history调出他的所有操作记录,看他在我的电脑上用Google搜了哪些关键字,然后一个个去学习分析他每个动作,去想他为什么搜这个关键字,复盘完还有不懂的再到他面前跟他面对面的讨论他为什么要这么做,指导他这么做的知识和逻辑又是什么(这个动作没有任何难度吧,你照着做就是了,实际我发现绝对不会有10%的同学会去分析history的,而我则是通过history 搞到了各种黑科技 :) )。

感觉这个实现还是不对,padding只对齐了尾巴不让合别人共享一个cache line,但是没法避免前面和别人对齐跨cache line。即使后面对齐也不对,用一个rp而不是8个就能对齐到64,刚好一个cache_line,正确做法得和Disruptor一样前后夹击对齐 https://plantegg.github.io/2021/05/16/CPU_Cache_Line和性能/

一语惊醒梦中人的感觉最爽,我是做了10年性能优化后碰到了一次醍醐灌顶般的醒悟

这之后,无数次只需要看一眼服务的RT、CPU状态就能很快给出服务的极限QPS是多少。

这个原理最简单的总结就是:QPS和延时的乘积是常量(复杂版总结如图1);其次如图2

当别人给我压测结果数据(并发、QPS、RT)的时候大多我一眼就能看出来数据错了,比如打压力5分钟,然后给了一个5分钟的平均QPS,我一推算QPS不对,再让业务仔细一查原来是5分钟前面有3分钟热身,真正完整压力2分钟,但是QPS给了5分钟的!

如果QPS和延时同时下降那么一定是并发过来的压力不够了。

我见到99%的性能压测就像洞房的处男一顿乱鼓捣,只会不停地加并发,从来没有停下来计算一个并发的QPS是多少、对应的RT是多少、US CPU是多少,最佳并发压力是多少。

而我的做法是:1)用很少的线程压,收集RT、QPS、CPU数据;2)计算得到总QPS、最佳并发线程;3)用计算所得的并发数打压力

理解了下面两张图顶极客时间上所有性能优化的课程(我就在那些课程上找过一些错误数据),胜过你看一堆的性能优化书籍 :)

这也是以一挡一百的知识点,搞懂就打通一个领域

来说一次教科书式徒手全链路性能分析过程

强调徒手是缺少工具、监控的时候不能抱怨、不能甩锅,经理不听

强调全链路是不纠缠某段代码、业务逻辑的问题,而是需要找到问题瓶颈在哪个环节上

场景描述

某客户通过PTS(一个打压力工具)来压选号业务(HTTP服务在9108端口上),一个HTTP请求对应一次select seq-id 和 一次insert

PTS端看到RT900ms+,QPS大概5万(期望20万), 数据库代理服务 rt 5ms,QPS 10万+

调用链路:

pts发起压力 -> 5个eip -> slb -> app(300个容器运行tomcat监听9108端口上) -> slb -> 数据库代理服务集群 -> RDS集群

问题

性能不达标,怀疑数据库代理服务或者RDS性能不行,作为数据库需要自证清白,所以从RDS和数据库代理服务开始分析问题在哪里。

app业务方也尝试过增加app容器对性能没啥提升,所以怀疑问题在数据库上

分析过程

这里缺各个环节的RT监控,所以定位不了哪个环节瓶颈了

先到app服务上抓到数据库代理服务上的包,快速确认下从app到后端有没有瓶颈,如图1

重点在如何分析图1的数据,我从图1可以得到 数据库代理服务 RT是 15ms,也就是单连接的TPS是1000/15=70, 实际一条连接一秒钟才给后面发20个请求。

所以结论是后端能扛40万 QPS,压力没有从app服务打给后端

继续在app上抓包,这次抓服务端口9108的响应时间(如图2),分析如图1,结论是压力根本就没有打到9108上

临门一脚得结论

如图三,netstat 一看就知道问题在app服务前面,总结在图4

这次只是在各种监控缺乏的场景下,如何借助各种工具来有理有据地甩锅,其实核心理论在图1的分析过程,也是能力的体现,这后面是对并发、RT、QPS的理解

性能问题最好别找我,找我我能把你们卷飞了

我们的服务一般都在链路的最末端,也是最容易被责问性能不行的(正常)

所以无数次项目需要证明你行

我一般都能徒手透过5/6个中间环节一直打到发压力端。

有一次压力机用了12台,总QPS 总是无法逾越10000(上下波动),链路上各种加机器扩容,但是都无法突破10000 QPS

链路上所有环节的工程师都说自己的服务没有问题

于是我从最后端撸到发压力机器,发现每台压力机器的port range是10000-60000,也就是5万可用端口,12台总共60万可用端口,测试用的短连接,一个端口默认是60秒timewait,那么TPS就是 60万除以60秒,正好10000 QPS。

完美的1000 QPS,你说问题简单不,就是很简单,改成长连接就好了

比如这个case:https://weibo.com/1667773473/Lrl2vzX0Z

性能优化是最能体现全栈能力的

曾经有一次紧急被派过去优化一个项目,3天将性能提升了10倍

1
2
3
4
5
- docker bridge网络性能问题和网络中断si不均衡    (优化后:500->1000TPS)
- 短连接导致的local port不够 (优化后:1000-3000TPS)
- 生产环境snat单核导致的网络延时增大 (优化后生产环境能达到测试环境的3000TPS)
- Spring MVC Path带来的过高的CPU消耗 (优化后:3000->4200TPS)
- 其他业务代码的优化(比如异常、agent等) (优化后:4200->5400TPS)

前面从500到3000是比较容易的,优化起来效果很明显,主要是把CPU从SI、SY赶到US的过程,当然各种工具配合使用要熟练,过程如图1、图2(动图)、图3

当然最有意思的是优化后放到生产环境压测就又不行了,这个时候经理投来不信任的眼神,你丫忽悠我啊

吹了一天牛逼说是从500翻了6倍,然后生产环境一验证,白瞎!

线上最大的差别就是会调用第三方服务,但是第三方服务监控显示rt很小、也没啥压力(又到了扯皮时间)

于是我设计如下三个场景证明问题在中间链路上:

  1. 压测的时候在业务机器 ping 依赖第三方服务的机器;
  2. 将一台业务机器从负载均衡上拿下来(没有压力),ping 依赖第三方服务的机器;
  3. 从公网上非我们机房的机器 ping 依赖第三方服务的机器;

这个时候奇怪的事情发现了,压力一上来场景1、2的两台机器ping 依赖第三方服务的机器的rt都从30ms上升到100-150ms,场景1 的rt上升可以理解,但是场景2的rt上升不应该,同时场景3中ping依赖第三方服务的机器在压力测试的情况下rt一直很稳定(说明压力下依赖第三方服务的机器没有问题),到此确认问题在我们到依赖第三方服务机房的链路上有瓶颈,而且问题在我们机房出口扛不住这么大的压力。于是从上海Passport的团队找到北京Passport的PE团队,确认在我们调用依赖第三方服务的出口上使用了snat,PE到snat机器上看到snat只能使用单核,而且对应的单核早就100%的CPU了,因为之前一直没有这么大的压力所以这个问题一直存在只是没有被发现。

于是PE去掉snat,再压的话 TPS稳定在3000左右

这个优化最戏剧性的就是在上线后因为snat导致性能不行的证明问题,链路很长、团队很多,直接说不是自己的问题是没有意义的,关键是要如何在一个长链路中证明不是自己的问题,并且定位问题在哪里

image.png

教科书和实践很容易脱节

比如讲DNS出问题总是喜欢谈到DNS的递归解析,这些是DNS工程是需要关心的,但是对程序员来说更重要的是DNS在我的服务器上是怎么一个解析流程,解析不了再发给DNS服务器,但是在发给DNS服务器之前出问题是需要程序员兜底的。比如域名不能ping通,但是nslookup能通;

这方面就很少有教科书、文章来讲了。

比如讲到LVS总是告诉你那LVS的几种模式,但是没有从技术的原理上讲通LVS是怎么样在不同的模式下工作的,从而得到他们的优缺点,教科书一上来就把优缺点告诉你,没有带你推导他们的背后原因!

再比如讲LVS负载均衡原理一上来就是那10种负载均衡算法,但是现实中我们经常碰到的是:咦,这个负载均衡算法为什么导致了我的服务负载这么不均衡!

也就是教科书部分重点喜欢大而全的总结,实践希望我们揪住重点彻底掌握

教科书不负责跨界,实践需要我们跨界

来说一个知识上降维打击(学习)的案例

大多程序员对LVS的几种转发模式有点晕菜,但时不时又要涉及一下,晕菜是看着似乎懂但是没有真的懂,一用就发懵

实际上如果你要是理解了RFC1180,然后从包的流转,当包在LVS上被LVS修改并继续路由的时候,你要理解LVS对包做了什么修改、为什么要做这个修改、这种修改必须要求的场景(将来部署的缺陷)、作了修改后包怎么到达后端的Real Server,你就彻底理解了这些转发模式,同时优缺点也了如指掌。

这个时候再让你就某个应用特点来设计一个新的LVS代理转发模式就很容易了

RFC1180的威力 https://weibo.com/1667773473/LpXXUpLj2

用RFC 1180的逻辑来理解LVS https://plantegg.github.io/2019/06/20/就是要你懂负载均衡--lvs和转发模式/

大家一起切磋下如何解读性能测试数据

比如这个测试报告显示某个产品性能比Nginx好3倍,并且有详细的测试环境、数据比较:https://xie.infoq.cn/article/a25a30a1f190e7c6a41c4580f

所以问题是你仔细看完整个测试数据报告后,你觉得测试数据有问题吗?我想告诉大家的是这个数据测试不对,然后你要分析哪里不对了

理解超线程是掌握CPU相关知识非常重要的一个抓手、也超级实用

超线程(Hyper-Threading)原理

一个物理核还可以进一步分成几个逻辑核,来执行多个控制流程,这样可以进一步提高并行程度,这一技术就叫超线程,有时叫做 simultaneous multi-threading(SMT)。

超线程技术主要的出发点是,当处理器在运行一个线程,执行指令代码时,很多时候处理器并不会使用到全部的计算能力,部分计算能力就会处于空闲状态。而超线程技术就是通过多线程来进一步“压榨”处理器。pipeline进入stalled状态就可以切到其它超线程上

举个例子,如果一个线程运行过程中,必须要等到一些数据加载到缓存中以后才能继续执行,此时 CPU 就可以切换到另一个线程,去执行其他指令,而不用去处于空闲状态,等待当前线程的数据加载完毕。通常,一个传统的处理器在线程之间切换,可能需要几万个时钟周期。而一个具有 HT 超线程技术的处理器只需要 1 个时钟周期。因此就大大减小了线程之间切换的成本,从而最大限度地让处理器满负荷运转。

ARM芯片基本不做超线程,另外请思考为什么有了应用层的多线程切换还需要CPU层面的超线程?

超线程(Hyper-Threading)物理实现: 在CPU内部增加寄存器等硬件设施,但是ALU、译码器等关键单元还是共享。在一个物理 CPU 核心内部,会有双份的 PC 寄存器、指令寄存器乃至条件码寄存器。超线程的目的,是在一个线程 A 的指令,在流水线里停顿的时候,让另外一个线程去执行指令。因为这个时候,CPU 的译码器和 ALU 就空出来了,那么另外一个线程 B,就可以拿来干自己需要的事情。这个线程 B 可没有对于线程 A 里面指令的关联和依赖。

CPU超线程设计过程中会引入5%的硬件,但是有30%的提升(经验值,场景不一样效果不一样,MySQL/Hadoop业务经验是提升35%),这是引入超线程的理论基础。如果是一个core 4个HT的话提升会是 50%

这两年到处收集各种CPU,然后测试他们的性能

发现不同厂家CPU相同频率性能差异极大(单核)

在数据库场景下测试下来CPU的性能基本基本和内存延时正相关

谁家把延时做得低性能就好

如图1 core:120 490.402 表示120号core访问0号内存延时490,core:0 149.976 表示0号core访问0号内存延时149(差异巨大)

data f1

20年前Intel为了搞多核,开始将两个Die封装成一块CPU售卖

如图1,两个Die 上共4个core,然后封装成一块CPU

这被大家瞧不起,说是胶水核,因为1/2和3/4间延时很大

后来Intel再也不搞胶水核了,现在一个Intel的Die能放下几十个core,延时都很低

不过AMD则依靠胶水核要翻身了

如图2,这块CPU上AMD用了8+1个Die才放下去32core,性能延时都很好

便宜到人人说香

其实这两条路线这两家都能搞,但是Intel那个路线就是太贵

而AMD这种搞法恰好很适合云计算(拆开售卖)

不信你去各家云平台看看他们的价格差距

CPU中的cache变迁历史

80486(1989), 8K的L1 cache第一次被集成在CPU中,图1

80686(1995) ,L2被放入到CPU的Package上,但是是一个独立的Die,可以看到L2大小和一个Die差不多,图2

以酷睿为例,现在的CPU集成了L1/L2/L3等各级CACHE,CACHE面积能占到CPU的一半,图3

从上图可以看到L3的大小快到die的一半,L1/L2由每个core独享,L3是所有core共享,3级CACHE总面积跟所有core差不多大了。

最近这些年Cache上基本也没什么大的花样了

折腾cache都是为了解决内存延时问题(内存墙),或者说内存的延时配不上CPU的速度了(越来越大),图4

之前的好几条微博写了很多网络问题、学习方法等

我试着把他们总结起来写了篇《程序员如何学习和构建网络知识体系》

核心是从程序员实用、常碰到问题出发,提炼出相关的核心知识点,然后串联起来

里面每一个知识点都是碰到了具体问题

先是去解决,解决后再分析、总结、提炼。

比如网络为什么不通,还有哪些原因会导致不通,这个原因在不同Linux内核版本下会有什么不一样吗?

以后这种问题有没有快速定位工具、手段

比如网络传输慢的时候,定性定量分析RT的影响、Buffer的影响、BDP如何打满等

这种总结性的文章一般都比较务虚,所以每一个务虚的地方我放了一个案例

总共写了大概10篇相关案例来反过来证明务虚的话语

希望对你有所帮助

链接地址:https://plantegg.github.io/2020/05/24/程序员如何学习和构建网络知识体系/

程序员面试考算法(非算法岗)和考 拧魔方 差别大不大?

视频网站各种魔方手法总结,学一学都会

letcode各种算法总结也是非常完善

这两都能让面试官好好休息一下,有标准答案,对一下就行

不要抱怨招进来的人为啥水、干不了活

还不是letcode总结得好、八股文总结得好

之前的好几条微博写了很多网络问题、学习方法等
我试着把他们总结起来写了篇《程序员如何学习和构建网络知识体系》

核心是从程序员实用、常碰到问题出发,提炼出相关的核心知识点,然后串联起来
里面每一个知识点都是碰到了具体问题
先是去解决,解决后再分析、总结、提炼。

比如网络为什么不通,还有哪些原因会导致不通,这个原因在不同Linux内核版本下会有什么不一样吗?
以后这种问题有没有快速定位工具、手段

比如网络传输慢的时候,定性定量分析RT的影响、Buffer的影响、BDP如何打满等

这种总结性的文章一般都比较务虚,所以每一个务虚的地方我放了一个案例
总共找了大概10篇相关案例来反过来证明务虚的话语

希望对你有所帮助
链接地址:https://plantegg.github.io/2020/05/24/程序员如何学习和构建网络知识体系/

造新词、新概念是个很有意思的事情

大多时候能够化腐朽为神奇,一下子让大家都通透、凝聚、共识

听起来还很高深、高端,这就导致很多人为了高深、高端而造新词

这在很多大厂非常流行,搞好了四两拨千斤

但很多时候也会把简单事情复杂化、搞得神神叨叨,这就是为了造词而造词

比如WEB3是个啥,你看各个百科的解释是有点蒙逼

但是如果告诉你WEB3背后的存储就是区块链,区块链就是一个大DB

更简单点说WEB3就是一张大Excel表格,把大家的信息都放在这张表格里

别的网站都能来读取,这就是WEB3描绘的网站间数据共享

但是WEB3太模糊了,表达不了背后的本质,明显是为了操作往WEB2上套(区块链、比特币已经没人爱上钩了)

长链路性能压测之瞎几把压典范

1->2->3->4—->N

1使劲加并发打压力,但是QPS非常小,1-N每个环节的工程师都说自己没有问题

关键是每个环节的理由是:我的CPU、网络都很闲,没有压力。我基本没见过说自己RT稳定的!

然后长时间扯皮、互相甩锅,在我看来这些甩锅没有一点技术含量,真的是在甩锅,这些人都没入门。

这里本末倒置了,你要证明自己没问题必须说加压后自己的RT没增加,而不是说CPU、网络很闲

这里CPU是过程,RT才是我们要的结果(不要质疑别人的CPU高,先质疑RT高–或者说RT上升快)。

展开下:性能优化目的是提升QPS也就是降低RT,CPU、内存、网络等等都是不关键的要素,最关键的结果就是RT

因为并发一定的情况下QPS和RT是反比关系

记住这句话:长链路性能压测先追着RT跑而不是追着CPU、内存等资源跑

写了这么多增删改查,你会对JDBC驱动了如指掌吗(仅限Java+MySQL技术栈)?

我碰到几个厉害的程序员一个增删改查下去业务代码如何与MySQL互动一清二楚,业务上有了bug、性能有了小问题

大概率半个小时就给你分析得明明白白

但是90%以上的程序员即使天天增删改查,却对JDBC驱动一直是盲人摸象,今天这里看个参数明天那里优化下、明天那里鼓捣下

不成系统,碰到硬扎问题还是只能瞎试、求人

比如什么是JDBC流式驱动,有些程序员被一个大查询把内存怼爆了,才想起来优化,然后网上抄个参数好像问题解决了,但是怎么很多情况下反而慢了,经常timeout了;

比如预编译优化以为加上useServerPrepStmts=true就可以了,测试也性能好了,但他妈的上线后发现性能反而差了

建强的初衷是什么?肯定不是为了保护淘宝、百度、腾讯以及锅内互联网。

建强的事实结果导致了锅内互联网的繁荣吗?这没法证伪和证实。

那么现在事实上锅内互联网这么繁荣了,强能拿掉吗?

你可以随意使用汉字不被限流河蟹吗?这些汉字有依据不能使用吗?

一个体制内、媒体多年工作者(靠采访、写字为生)被全网封杀确实是灭顶之灾,封杀三年后远遁他乡,尝试过妥协解封,没有结果。

终于没有希望回来后,

可以在那些不存在的网站讲讲CCTV的一些潜规则

X86有个有意思的指令:pause, 调用这个指令进程啥也不干,就是休息N个时钟周期,这个时候CPU可以省电、避免切换到其它进程导致上下文切换、也可以调度给超线程。一般抢锁失败会pause一下,比如内核中到处的spin。–这是原理

Intel Broadwell架构(比如 E5 至强5代)及之前的CPU都是pause一次休息10个时钟周期,Skylake架构之后这个pause一次休息时钟周期从10调到了140,整整增加了14倍啊,带来的后果是灾难性的(因为很多软件是不会测试、考虑这么细致的)如图1

比如MySQL使用innodb_spin_wait_delay控制spin lock等待时间(底层会调用这个pause指令),跑在Broadwell CPU上等待时间时间从innodb_spin_wait_delay5010个时钟周期(6微秒)。如果跑在Skylake上spin一次休息innodb_spin_wait_delay50140个时钟周期(84微秒,我相信没有几个DBA会根据CPU型号去调整参数吧–有的话请私聊我,交个朋友),后果就是MySQL在高并发场景下TPS拉胯的厉害,如图2

这是我第一次感受CPU对程序的影响,后来的事前面的微博都写过了 http://t.cn/A6XHRiYl ,每一次被现实鞭打后得知耻而后勇[二哈]

你看程序员啥都没做,但是锅在你头上

图1这张图让我学习到了特别多的东西:
1)为了成本把坏掉了一个核四核芯片关掉2个核(红圈所示),当2核卖(i3/i5/i7/i9 就是这么来的)
2)右下角告诉我这块CPU 芯片是177平方毫米,没概念就拿出你的大拇指看看,大概是这么大,再有人吹牛逼说他们的芯片多牛逼,先看看那块芯片的大小你就知道成本高了多少,基本主流的PC芯片都是 200以内,服务器略大点
3)这块芯片去掉四个核后,还有一半的面积(成本)用在了cache上,也就是现在的芯片cache成本基本和核的成本差不多了,要花掉你一半的钱在上面,他们很珍贵
4)PC芯片GPU占用面积(成本)也不小

图1 也叫Die(裸片),就是台积电加工完成后的东西,得再做封装后变成火柴盒字大小你能买到的CPU实物(如图三)

结合图二,这是一个封装后的AMD CPU,在云计算时代我挺看好这种模式的,AMD把一块CPU一个Die切割成了9个Die 来加工然后用胶水黏在一起卖(真他妈便宜),关键是性能还和Intel差不多,然后拿到云上本来也得切割成小的ECS售卖,所以感觉Intel真浪费!–你去各家云上看看AMD虚拟机卖得就是便宜

Die的大小成本到底差了多少呢? 加工Die最关键的是良品率,Die越大良品率越低(你想想显示器大小和坏点的关系)。如图4,这是个Die大小对良品率影响的计算案例(最外面那个圆盘就是我们所说的晶圆,台积电就是把一块大晶圆加工成多个小的CPU 芯片),一般良品率超过50%就是个不错的成绩了

你要是反复吃透这篇博文的话基本对CPU的理解算是入门了,如果没就要把图一供起来反复看

软设置定制内存大小的故事
有一次我们有一台1T内存,CPU4个NUMA Node的物理机,但是不符合使用要求,内存太大,规定只能用512G内存。机器借过来的不能撕毁标签

第一次尝试,在Linux OS grub启动参数中设置 mem=512G,起来后果然只剩下512G内存了。但是跑下来性能不够好,如图1,相当于拿掉了红框里的内存,右边的core要跑很远才能用上左边的512G内存,自然性能不好了。

第二次尝试,在Linux OS grub启动参数中按numa node设置每个node内存为128G(4个合起来512G,关键字 memmap),这基本是完美方案,性能也和1024G时一样好

不过意外发生在某次的国产CPU上,我们用方案二,另外一个团队直接拆机箱把内存,把我们打垮了

读中学的时候我有个同学对物理很感兴趣,天天拿着一本奥赛的书,晚自习逮到物理老师就问,开始的时候物理老师还挺有耐心的,老师不会的会回去研究下再跟这个同学交流

可实际上吧每次物理考试这个同学也就及格水平(班里处于中游),但是挡不住自己的热情,一学期下来物理老师看到他就躲,晚自习教室门口瞄一眼就赶紧闪,最后这同学也没考上大学,你想想物理是他最拿手的科目了只是中游水平。

他这些竞赛书我也拿过来翻过,题目都看不懂,只好绕着跑。

后来工作后认识个朋友,自学编程,一上来迷上了《计算机程序设计艺术》,三大本都买回来了,天天琢磨算法,CRUD也搞不好、计算机基础知识也不太懂,就这样自学了1年多后跑北京找工作,结果找了半年一个正经工作也没有,后来去了广州就没消息了。也怪我不应该告诉他有这书的,同样这书我也看不懂。

这种人大家身边也许都有,有热情但是就是不能脚踏实地,没有那么高的能力非要摸尖尖。大部分民科都是这种吧

它力压超线程成为冯诺依曼计算机体系下唯一的特优设计、它降低了程序运行性能但是程序员依然如痴如醉地离不开它、它究竟是如何拳打超线程脚踩cache成为计算机体系的最优设计的?它就是虚拟内存

一个月前的微博总是被屏蔽,周末终于知道是哪个词了(放在最后说),突然感觉挺无聊的,不想多写了

-——

高中的学校门口时候亲眼见过一个“民科”

一个老爷子,摆了几张大白布,上面全是各种公式,说是证明了哥德巴赫猜想(记不太清了,大概是那几个世界难题),现在想想我们学校没有数学牛人啊,这种“民科”–这也是后来形容这类人提出的名词,现在主要集中在中科院数学研究所门口了,算是找对地方了。

不过这种“民科”特别单纯、自娱自乐,也没啥不好,跟你玩王者荣耀、魔方、刷letcode差别不大。

https://weibo.com/1667773473/LwlKNtfZp 这条我本意不是要说民科。痴迷奥数和《计算机程序设计艺术》也不是民科,准确来说是不务实,重要的基础和工作相关的技术还没掌握好,在明确目标面前非要走“邪路”。

这样的例子还有很多,比如很多文章讲TCP各种拥塞算法条条是道,可是我们工作中需要用到这些吗?你TCP握手、断开还有点迷糊,就痴迷这些不恰当;

再比如好多书讲cache_line 的tag、组一套一套的,但是cache_line的本质、如何发现False sharing等偏实践的还没搞懂呢。

还有人每天都要化一两小时刷几道letcode, 你也知道用不上、面试官也知道用不上,但是大家就是痴迷拿到题一气呵成没有bug。其实都是背好了而已,真要考就先看思路、然后wei代码能表达出来就可以了。

其实这里讲的是取舍和重点问题。

说这些总的意思是优先尽量从实践出发多学天天能用上的知识,借用 jjhou 老师20年前很有名的一句:勿在浮沙筑高台(出自 程序员 杂志 @蒋涛CSDN )

最后希望大家不用为了工作疲于奔命,闲暇至于为了怡情、喜欢可以多看奥数、TAOCP(高级享受)

-——-
false sharing 的中文是min敢词

高中的学校门口时候亲眼见过一个“民科” 一个老爷子,摆了几张大白布,上面全是各种公式,说是证明了哥德巴赫猜想(记不太清了,大概是那几个世界难题),现在想想我们学校没有数学牛人啊,这种“民科”–这也是后来形容这类人提出的名词,现在主要集中在中科院数学研究所门口了,算是找对地方了,也许是有钱有闲买票容易了。

不过这种“民科”特别单纯、自娱自乐,也没啥不好,跟你玩王者荣耀、魔方、刷letcode差别不大。 http://t.cn/A6XrYUzm 这条我本意不是要说民科。痴迷奥数和《计算机程序设计艺术》也不是民科,准确来说是不务实,重要的基础和工作相关的技术还没掌握好,在明确目标面前非要走“邪路”。 这样的例子还有很多,比如很多文章讲TCP各种拥塞算法条条是道,可是我们工作中需要用到这些吗?你TCP握手、断开还有点迷糊,就痴迷这些不恰当; 再比如好多书讲cache_line 的tag、组一套一套的,但是cache_line的本质、如何发现False sharing等偏实践的还没搞懂呢。 还有人每天都要化一两小时刷几道letcode, 你也知道用不上、面试官也知道用不上,但是大家就是痴迷拿到题一气呵成没有bug。其实都是背好了而已,真要考就先看思路、然后伪代码能表达出来就可以了。 其实这里讲的是取舍和重点问题。 说这些总的意思是优先尽量从实践出发多学天天能用上的知识,借用 jjhou 老师20年前很有名的一句:勿在浮沙筑高台( 出自 程序员 杂志 @蒋涛CSDN ) 最后希望大家不用为了工作疲于奔命,闲暇至于为了怡情、喜欢可以多看奥数、TAOCP(高级享受)

10年前P10大佬无招胜有招的故事

JBoss启动失败,没有太多错误信息,唯一有一行LogFactory.release的warning日志

对这个问题,如果熟悉JBoss启动流程那么很容易排查(套路熟练),如果不熟悉JBoss怎么办呢。大佬虽然对JBoss不熟但是对btrace无比熟练,所以从trace这个warning入手一步步trace出来启动流程堆栈

然后追踪到listenerStart,再然后trace到Exception,继续通过trace dump到Excepiton内容是因为jar加载冲突了,再加上 启动参数上增加-XX:+TraceClassLoading就能知道具体冲突的版本

你看一套流程下来考的是btrace无比熟练,跟我之前讲的抓包一样。抓包、strace、btrace

啊,P10还查问题?嗯10年前P10也要弄脏双手干活的,现在P8就不用了

最后讲个故事,有次别人面试我,问我TCP的close_wait 是为啥,我答偏了,在这之前我写过两篇如图2一样关于close_wait的文章

为什么操作系统有了多进程调度能力之后,CPU还在一个物理core上搞两个线程来分享这一个物理core呢(超线程)?

操作系统多进程调度有两个目的:1)让计算机拥有多任务能力;2)一个线程卡顿(比如读文件、网络)时切换到另外一个线程,高效使用CPU。目的2和超线程目标是一致的但不重叠

关键在于一次超线程切换只需要几个时钟周期,而一次操作系统的多线程、进程调度需要大几千个时钟周期,对CPU来说这种切换太慢了,没法充分利用CPU

据说4线程的CPU一直在评估设计中(90%的场景下,即使2个超线程也只跑满CPU流水线的一半能力)

业务代码必须插入安全团队的XSS扫描等代码,这个扫描代码每次在扫描结束的时候抛出 EOFException 然后自己catch,然后结束。用异常来控制业务流程,每次都 fillInStackTrace 然后自己悄悄吃掉,外部啥也感知不到,但是性能降低了30%。这样的同事多给我来几打

重新翻了公司10年前的经典案例排查过程,用10年后的姿势水平看当时的过程十分曲折。

现在看都是非常直白的知识点:比如JVM YGC耗时只和存活对象数有关和新生代大小无关(结果我看到10年前的工程师反复试验得到了这个结论);比如TCP全连接队列是否爆了用 ss 看下就知道了没必要netty代码分析来去(10年前花了3天时间也搞定了)– 他们都厉害在没有知识也能解决问题[中国赞],比我厉害1万倍

财新网等媒体眼中的著名专家 @逮獭科技 曾说过:
我觉得这个是时代的变化,每一代人中的佼佼者,其知识系统在下一代人看都平平;二战之后真正开挂的是全球的教育产业,几何级数膨胀受教育人口,而且,知识更迭很快,能追上前沿很不容易

程序员领域很卷很大一部分原因除了知识会过期还有很大一部分知识门槛变低了!我们的知识在下一代人眼里之所以平平无奇就是这些知识很快会变成八股文了,就像一个培训班训练出来的学生一样,可以解题拿高分,但是一旦出来一个新题型就嗝屁了

完整清晰地解决一个你所面临的新问题就像冲塔,一个人自己冲是最难的,别人冲完后告诉你攻略(八股文,就变成经验了)就容易多了,这是无招胜有招(真学霸),刷题多的都是假学霸;同样靠刷题面试牛逼的不一定是真学霸,问题出在了面试官分辨能力上。

信息流下几点经验分享下

看到一篇好文章后把整个博客都看下,挑你擅长的先看,快速确认博客内容深度以及是否适合你;

看到好微博也是,话痨的就算了,有些人连评论都懒得开就开始问!

公众号重点看看开号前面半年发的内容,一般都是干货,后面大多都是带货、为了发而发

不要沉迷信息流,多翻翻箱底的经典文章,他们能沉淀下来相对更有实力。我就发现新同学基本不太关心老文章,总是追求新的,你看我前一阵还在翻公司10多年前的案例

有疑问的先放狗搜一下再提问

看到一张好图片可以先搜图,然后根据图片能给你搜出来一大堆好文章(好文章配图一般也不差)

专门给微博用户的:不要在评论里 @**笔记 实在想,就转发 @**笔记, 不至于打扰别人

一个有意思的想法
每一代人中的佼佼者,其知识系统在下一代人看都平平。大概是因为完整清晰地解决一个你所面临的新问题就像冲塔,一个人自己冲是最难的,别人冲完后告诉你攻略(八股文,就变成经验了)就容易多了,第一个解决问题并沉淀的是牛人,让问题成为知识

这样让后面的普通资质的人也有了牛人的知识和“能力”,随着这种牛人沉淀下来的死知识越来越多,后面的人只需要掌握更多的死知识,但是失去了更多的单独冲塔的机会。当然每个时期的牛人还是存在的,只是牛人里面掺入的沙子越来越多了,你看互联网行业人人大佬、人人专家。

面试也是靠刷题、背八股文,刷题就是典型的牛人把思路方法放那里了,普通人还需要花上1/2周来消化,消化后面试效果比牛人还牛(熟练啊),面试官也甄别不了

结果会怎么样呢……

讲一个诈骗程序员的案例

程序员都喜欢注册域名,如果注册域名并在公安注册后,过几年域名到期了(大概率),这个时候有专门的流氓公司

他们会抢注域名,然后在这个域名下放一些热门盗版电影(不涉黄),这个时候他们的另一个公司(拥有电影版权的公司)出来取证了

接下来就是去法院告你盗版要求赔偿,在公安那里这个域名的所有人还是你(或贵司)从法律流程上来说完美无缺,你一定会输掉官司,这个时候流氓公司就等着你和解割地赔款

他们有专门的团队把整个过程流程化、低成本化

如果你们有废弃的域名记得注销ICP备案,如果是大厂更要记得这事,大厂赔得更多

以前主要是分析TCP协议,HTTP 接触得少,这几天补了一把,只能说wireshark对HTTP解析做得太好了. 比如以前我说抓包发现一个请求15ms,然来这个数据wireshark帮我们解析好了,MySQL协议的解析就没那么友好

我常用又不多见的命令(参数)

用curl调试sock5代理:curl -x socks5h://localhost:8001 www.不存在的网站.com/

nc走sock5转发: ProxyCommand /usr/bin/nc -X 5 -x 127.0.0.1:13659 %h %p //连github时

wget不存在的网站:wget -Y on -e “http_proxy=http://[HTTP_HOST]:[HTTP_PORT]“ http://不存在的网站.com/其中:[HTTP_HOST]和[HTTP_PORT]是http proxy的ADDRESS和PORT。

curl指定本地端口连远程(这样抓包只抓这个端口):curl –local-port

nc测试udp能否通(比如overlay网络、dns):nc -v -u -z -w 3 1.1.1.1 53

awk分组统计分析平均值:awk ‘{ sum[$1]+=$2; count[$1]+=1 ;} END { for (key in count) { printf “time= %s \t count=%s \t avg=%.6f \n”, key, count[key], sum[key]/count[key] } }’

将最近多少天的md笔记发表到博客:find $srcDir -maxdepth 1 -type f -mtime -$1 -name “*.md” -not -name “template.md” -not -name “temp.md” -exec cp “{}” ./source/_posts/ ; //改改可以用户备份本地最近修改的文件、配置

将博客上的大图压小(节省博客流量):find img_small -size +1024k -type f -exec sips -Z 1024 {} ;

检查用户使用的是长、短连接(别被用户的描述坑了):netstat -ato

发起ping 风暴:ping -f

测试网络MTU:ping -M

带时间戳的ping: ping -D 114.114.114.114 | awk ‘{ if(gsub(/[|]/, “”, $1)) $1=strftime(“[%F %T]”, $1); print}’

算是小抄,大多时候都是man、放狗可以获取,还有很多下次放

很多知识没啥用,但是逼格高,网上讲得多

比如拥塞算法,那个算法能搞明白的没几个,程序员基本不需要懂,最多最多就是sysctl配置换一下。程序员要的是打满带宽、延迟低、不丢包

还有cache line 分组编码,我们程序员要的是cache line不发生False sharing,多给我几个Disruptor如何做、Netty如果做的案例就完美了,比如Netty里面的代码实现全错了这么多年也没啥大事,还有程序员继续在错误的代码上,继续提交仍然是错误的patch,还没合并了 https://weibo.com/1667773473/LrEKR1lIL

比如说起DNS,现在的资料主要是将服务器怎么递归解析域名,程序员一脸懵逼,你就告诉我域名解析是我配置的问题(出在本机)还是发走后服务器解析不了(可以call 运维支撑),比如本机能ping但是不能nslookup 又是怎么回事?本机配置问题、lookup流程可以给程序员多讲讲,这里程序员可以兜底,出了本机就得运维啥的来支撑了。程序员要的是这种 https://plantegg.github.io/2019/01/09/nslookup-OK-but-ping-fail/

还有刷算法题,面试造火箭入职拧螺丝,大多指的这种。现在保守估计99%的letcode算法都用不上,95%的程序员不需要写心的算法,也就是现在的工具箱里螺丝刀这么多、这么好用了,你居然要面试让程序员如何设计一个新的螺丝刀?关键是贵司也不是螺丝刀工厂啊。最后说新员工能力不行

我是真没想到985毕业还非要说等额本金比等额本息的利息少!

首先两者的利率是一样的,你还多少利息=借钱数*利率(按月算吧,就可以去掉时间变量了)

这几个变量一样利息就一样,之所以等额本金给你感觉还利息少是因为你从第二个月开始欠的本金少了(不是还款方式导致的利息差异),欠的本金少了是因为你每个月还得多。

你借100万,每个月还1万,假设一个月这100万欠款的利息是5000,那么下个月你只欠99.5万了,下个月只需要还99.5万一个月的利息。–这是核心逻辑

顺便说下现在信用卡、套路贷就是用的这种方式打插边球让你以为利率低(不敢直接宣传利率是多少),比如1万块分期账单分10期,每期还1050,让你以为利息一个月 50块(按1万本金算一年利率折合6%),总利息也确实只还了 500块,但是你想想最后一个月你只欠他1000本金,但是仍然还了50块利息,也就是1000块年息600块(12*50),也就是年利率 60%,妥妥的高利贷

#程序员的螺丝刀# nc(netcat)

测试udp端口的连通性(比如dns、比如overlay服务),如图1

nc -l -u 4789

文件上传下载,下载有更方便的:python -m SimpleHTTPServer 8080 (如果要上传呢,如果没有python呢)
nc 也可以的:nc -l -p 8210 > demo.txt (server上),client端上传:nc dest_ip 8210 < demo.txt

打洞,我的ssh配置里面无数的nc转发,用ssl加密,然后nc代理

#程序员的螺丝刀# netstat
netstat -o 查看keepalive、重传
netstat -t 查看收包(自身)慢,还是发包走后对方慢
强大的丢包统计,保命的命令:netstat -s |egrep -i “drop|route|overflow|filter|retran|fails|listen”
tcp队列是否溢出:netstat -s | egrep “listen|LISTEN”
通过netstat -s来观察IPReversePathFilter 是否导致了网络不通
tc你说没听过,好吧,ping、netcat总归是耳熟能详了吧,你会用吗?

给大家一些具体的数字
用ab压Nginx的index.html页面数据对比(软中断在0核上, 软中断队列都设为1, nginx version: nginx/1.21.0, 测试中所有场景Nginx 把CPU全部吃满)
用了两台Intel服务器,同一台E5-2682 开关NUMA对比(对比NUMA的差异),另外一台是 intel 8163,看看芯片之间能力差异
结论:
1)单物理核TPS能到82000,8163 比2682 提升了60%
2)开NUMA有5%的提升
3)超线程能提升 50% 左右的性能(开NUMA后提升了30%)
4)但就内存延时来比8163的内存延时其实较2682改进不大,内存时延发展一直追不上 CPU 的速度(图中蓝色线是2682、橙色是8163,灰色是8269)

以后别动不动就吊打Nginx,我只测试到一个Server在一堆限定的场景下比Nginx好了5%。

E5-2682 NUMA on E5-2682 off 8163 off (2 ab 压)
1号单核 49991 us:37% 0.96 IPC 45722 us:35% 0.90 IPC 82720 us:36% 1.27 IPC
HT(1/33) 65469 us:31% 0.65 IPC 62734 us:37% 0.65 IPC 120100 us:38% 0.92 IPC
0号单核 29881 us:27% si:29% 0.90 IPC 28551 us:28% si:27% 0.88 IPC 65615 us:32% si:17% 1.20IPC

#程序员的螺丝刀# tc(traffic control)
模拟丢包率、设置时延等等简直太香了。我不知道不会用的程序员是怎么搞的
延时设置:
give packets from eth0 a delay of 2ms
bash$ tc qdisc add dev eth0 root netem delay 2ms

change the delay to 300ms
bash$ tc qdisc change dev eth0 root netem delay 3ms

display eth0 delay setting
bash$ tc qdisc show dev eth0

stop the delay
bash$ tc qdisc del dev eth0 root

设置1%丢包率
tc qdisc add dev eth0 root netem loss 1%

高级版,指定ip和端口延时

智商过滤器:0)等额本金比等额本息更合算;1)相互宝好不好;2)如何看待中医中药,中成药、中药注射剂好不好?

长期有耐心放到程序员身上也一样管用,进到好的公司、碰到好的领路人、赶上各种案例都是小概率事件,但是保证自己抓住机会的能力。

比如身边有高人,就好好多学习,哪怕是干点累活脏活(你要认为被PUA就无救了);

看到好的文章就把整个博客都翻翻;

碰到奇怪的问题要像平头哥一样死咬不放,多问几个为什么,搞清楚所有背后的未解之谜(每一个未解之谜都是你的一个盲点);

多学点实用的,少在公司搞些花架子,本事才是自己的(有些技术文章一看就是水文,各种框架图、结构图);

少把自己绑死在特定的技术上,尤其是公司自己发明的特殊轮子上。

最后时间才是最重要的,刚毕业急不来,长期有耐心。你看到的那些只是特别优秀–机会好、平台好、智商高……等中间的一例,大多都是跟你一样的普通人,不要过于焦虑

鸡汤

Do More, Do Better, Do exercise口号和实践

image.png

有个 Nginx 间歇性卡死的分析案例我追着看了三年,作者三年后也进步一更新的最根本的原因和优美的fix方法,简直太过瘾了。三年前就找到原因了,以及很多很多疑问点,剩下两三个小疑问,三年后终于也填补完美了。可惜不能分享。比如卡死的那个阶段所有 rt 都不对了容易导致分析跑偏

想搞个案例分析集,要求案例典型普适性强,代表基础组件基本原理等知识。分析手段尽量通用,重现容易的更好,分析过程一定要逻辑合理每个疑问都能回答清晰。有没有想要贡献案例的同学?这种案例搞清楚一个基本能横扫一个领域,比如上一条说的Nginx案例就让我这个从没用过Nginx的人学会了 惊群、epoll条件触发等之类的知识点 #拍案惊奇# 案例首先会去掉敏感信息,然后在分享过的同学之间内部共享,然后再开放。如果你们在网上看过已经发布过的案例更好,我先去学习下

趁着热点写下iptables+ipset的组合拳
如果有1万个白名单IP/CIDR, 往iptables里写1万条规则不现实也严重影响性能,这个时候可以把1万个ip、CIDR放到一个ipset里面,然后再在iptables里添加一条规则就可以了。动态增删白名单只需要动态修改ipset就可以了,iptables规则不需要修改

案例:
#timeout 259200是集合内新增的IP有三天的寿命
ipset create myset hash:net timeout 259200 //myset 还是空的

ipset add myset 100.1.2.0/24 //从set中增加ip段,也可以是一个ip,可以反复添加不同ip

//iptables 添加规则,对myset里面的所有ip访问端口1234 放行
iptables -N white_rule
iptables -A white_rule -m set –match-set myset src -p tcp –dport 1234 -j ACCEPT

限制:要求对所有ip规则一样才适用

#程序员的螺丝刀# wget

wget –limit-rate=2.5k 限制下载速度,进行测试, 挺有用的,比如你想模拟网络慢的场景下会不会出现什么问题;让 数据堆在接收窗口、发送窗口里面也很好玩;这个时候抓包看看输出的时候在干啥就更有意思了

用 Wget 的递归方式下载整个网站:wget –random-wait -r -p -e robots=off -U Mozilla www.example.com

7.0.0.0/8,11.0.0.0/8,21.0.0.0/8,22.0.0.0/8,30.0.0.0/8 这些虽然不像192.168一样是私网地址,但是常被大家用来做内部地址,这是因为公网上只有美国国防部使用,所以不会和公网上冲突

全链路性能分析套路:
1) 先看监控,有各种鹰眼、狼眼最好;
2) 没有的话就要徒手上了,先要权限,只要每个节点有权限就好搞了,按着 https://www.weibo.com/1667773473/Lsb7CkVed 这里的方法徒手撸,从客户端一直撸到最后面的数据库;
3)如果没有权限是最悲惨的,一般外包都没权限,但是又要干活,比如我。那么就只能用 ping 到处围着蹭蹭,不进去,谁让你是外包没有权限的,参考这三个案例 https://www.weibo.com/1667773473/LAjwUldTrhttps://www.weibo.com/1667773473/LzYwyaIB4 下面这个是cloudflare的(高手的做法都差不多)https://www.weibo.com/1667773473/LAJ5mnp7b
4)最后啥都没有肯定要有钱,出钱找我就行(案例典型可以不要钱)

#套路+案例#

感觉推上的技术氛围更浓厚啊,这个要超赞,图一这个问题居然讨论这么热闹。默认500个time_wait确实是四元组要唯一(6万个可用端口除以120等于500),如果是探活一个服务的话src_ip dest_ip dest_port固定了,只剩下src_port可变,反过来说探活120秒一般不会超过500次,所以应该是够的。探活一般是connect,也就是随机选择(其实不是随机,有算法的)src_port, 所以是500. 如果创建 socket 的时候做了bind也就是写死src_port了那就120秒只能有一个time_wait. 当然还可以是图三 reuse time_wait

贴个文章:https://plantegg.github.io/2020/11/30/一台机器上最多能创建多少个TCP连接/

https://15721.courses.cs.cmu.edu/spring2016/papers/p743-leis.pdf 这篇2014年的论文给了一个很牛逼的结论,通过 morsel-driver 和 numa-aware 对TPC-H性能有数量级的提升,按理这个数据很牛逼了,但是我好奇为啥没有大规模上生产呢?

但是文章中对 numa-aware的理解还是很赞的,一般搞数据库的理解这些算是跨行业,有点难度很正常。论文里都给了非常专业的数据和理解。

其实论文不应该把morsel-driver和numa-aware混到一起,最后不知道是谁的功劳

https://plantegg.github.io/2017/01/01/top_linux_commands/

#拍案惊奇# 我最喜欢的Nginx卡顿案例终于整理完毕

简述就是:总有耗时任务造成worker进程卡死,就是某个任务总是总用worker,导致worker没法响应新连接(用户感知连不上Nginx)、没法更新woker计时(导致nginx日志时间失真,排查麻烦)、普通正在处理的请求也变成了慢请求。

涉及到:TCP连接、Nginx进程模型、惊群、边缘触发和条件触发、网络buffer等,真是一个让我爱不释手的case,这几天我都不想上班只想好好把玩

类似的案例可以参考:
Why does one NGINX worker take all the load?
The story of one latency spike

但比我这个案例有趣性差得太远了

作为极客时间的企业用户(所有课程随便看,但是我看的不算多)来给你们推荐几个课程。MySQL45讲,趣谈网络一定是值得推荐的;网络案例也可以看看(不想花钱就看我的博客网络部分,嘿嘿其实比这课要好);芯片那个有兴趣可以看看,算是比较偏门了。

其实企业用户超级不值当,包年性质,过期了就不能看了,还不如买下来的,不推荐购买企业用户。

要想省钱3/5个人组团,每个人买3-5门课,然后互相学习

还行吧,2个小时把一个系统优化性能翻了一倍,主要还是CPU、网络都比较了解,容易出成绩

image-20220813173113345

优化前 800MB
1 开numa
1.2GB
2 使用irqbalance,自动将irq 绑定到对应的位置的numa 核心
1.6GB
3 软件绑核,各绑定到2 个相邻的核心。将CPU 和numa 分开。
2.0GB
4 更改网卡中断队列数。
2.6GB

内核 TCP 协议栈 bug 导致应用卡死的 case:http://t.cn/A6SYWIsh 还得会看包,就不用走这么多弯路了,教训总结得不够[微笑],kernel上的修复patch:github.com/torvalds/linux/commit/b617158dc096709d8600c53b6052144d12b89fab (5月引入的bug:http://t.cn/A6SYHu9W 7月修复)
为啥 Databricks 不直接follow kernel而是follow ubuntu,吃二手消息呢?
redhat对这个 bug的描述:http://t.cn/A6SYQ29h
图三中 红色代码为了修 CVE-2019-11478 添加的,引入了这个卡死 的bug,绿色部分增加了更严格的条件又修复了卡死的 bug

双网卡下的 kubernetes 集群:1)控制面走外网网卡;2)flannel等overlay走内网网卡

实现:

控制面指定外网网卡ip:kubeadm init –control-plane-endpoint 192.168.0.21:6443

Flannel yaml配置中指定网卡:–iface=enp33s0f0

默认路由选择外网网卡

比较不忍看到大多新同事刚一进来被丢到了错误的位置上,新同事还不敢拒绝。第一种是要面对新公司一大坨内部产品和术语,没有人带,这种太不人道;第二种因为错配而要面对新技术领域,大体还是能 Google 到,不过对他们压力太大,这种我一般会带一次,再多也没精力,毕竟跨了团队;几个月下来新同事肯定觉得被 PUA了

这次看了一篇排查分析两条TCP流互窜的问题。我两年前碰到过,一抓包就把问题KO了,但这只是老法师的经验丰富而已,再看新同学抓包、会话分析、IPVS debug等一套组合拳下来也把问题解决了,我更喜欢这种清纯、处男手法。油腻老法师反而不值得学习, #无招胜有招

最近看了几个 golang 的排查分析,一个是DNS,一个是延时增大。golang 挺能整事的,自己新搞了一堆DNS 解析逻辑,结果带来一堆问题。延时增大分析那个我看老法师把golang 的pprof 使用的真流畅,废了一大波力气终于找到了延时增加是某个同步写日志等待时间太久了,难道 golang 就没有 arthas 之类的工具吗?

CRUD Boy 最喜欢说我的日志没问题!但是你的线程不会被调度到你怎么记时间?就连 Nginx 记录的access time都不可靠,不出问题的时候一个个贼精确,一出问题全完蛋。Nginx 经常出现worker卡了导致这个worker不去更新时间最终输出的 RT 比实际小很多

也有你抓了包确实慢,但是 CRUD 头铁:可能是网卡坏了、机器故障,反正我的日志没问题。给了两个选择:要不切换一台机器;要不调用方重启一下。都被我否决了,我就是不相信你的日志,最后发现是业务线程排队了,日志还没机会开始记。如果真切换机器(100%问题恢复)、业务重启(小概率问题恢复) CRUD 下次还头铁,这次被我坚决按住并全网广而告之你的日志不可靠!这是解决这个问题的最大价值

三年前碰到过一次MySQL线程池某个group卡顿的问题,当时调大 thread_pool_oversubscribe 就恢复了,最近又碰到了,同样是调大问题就解决了。但这次经过分析后我认为不是 thread_pool_oversubscribe 太小,而是 group 里的线程有泄漏

几乎所有互联网公司对内想把员工当小偷一样的安全管控都是幌子,除了老板自我感觉良好、给员工带来麻烦外,墙都能翻还弄不了你这点安全把戏

比如菊花司,早年物理隔离,工作本不能连外网,要连外网请换个本本,或者买一块硬盘,物理切换

0%