plantegg

java tcp mysql performance network docker Linux


  • 首页

  • 分类

  • 归档

  • 标签

  • 站点地图

  • 关于

AMD Zen CPU 架构以及不同CPU性能大PK

发表于 2021-08-13 | 分类于 CPU

AMD Zen CPU 架构

前言

本文先介绍AMD Zen 架构,结合前一篇文章《CPU的生产和概念》一起来看效果会更好,在CPU的生产和概念中主要是以Intel方案来介绍,CPU的生产和概念中的 多核和多个CPU方案2 就是指的AMD Zen2架构。

Zen1 和 Intel 还比较像,只是一个CPU会封装多个小的Die来得到多核能力,导致NUMA node比较多。

AMD 从Zen2开始架构有了比较大的变化,Zen2架构改动比较大,将IO从Core Die中抽离出来,形成一个专门的IO Die,这个IO Die可以用上一代的工艺实现来提升成品率降低成本。剩下的core Die 专注在core和cache的实现上,同时可以通过最新一代的工艺来提升性能。并且在一个CPU上封装一个 IO Die + 8个 core Die这样一块CPU做到像Intel一样就是一个大NUMA,但是成本低了很多,也许在云计算时代这么搞比较合适。当然会被大家笑话为胶水核(用胶水把多个Die拼在一起),性能肯定是不如一个大Die好,但是挡不住便宜啊。这估计就是大家所说的 AMD YES!吧

比如Core Die用7nm工艺,IO Die用14nm工艺,一块CPU封装8个Core Die+1个IO Die的话既能得到一个多核的CPU成本有非常低,参考 《CPU的生产和概念》中的良品率和成品部分。

介绍完AMD架构后,会拿海光7280这块CPU(实际是OEM的AMD Zen1 架构,一块芯片封装4个die)和 Intel的CPU用MySQL 来对比一下实际性能。

网上Intel CPU架构、技术参数等各种资料还是很丰富的,但是AMD EPYC就比较少了,所以先来学习一下EPYC的架构特点。

image-20220331120118117

AMD EPYC CPU演进路线

img

后面会针对 第二代的 EPYC来做一个对比测试。

AMD Accelerated Computing FAD 2020

AMD EPYC CPU Families:

Family Name AMD EPYC Naples AMD EPYC Rome AMD EPYC Milan AMD EPYC Genoa
Family Branding EPYC 7001 EPYC 7002 EPYC 7003 EPYC 7004?
Family Launch 2017 2019 2021 2022
CPU Architecture Zen 1 Zen 2 Zen 3 Zen 4
Process Node 14nm GloFo 7nm TSMC 7nm TSMC 5nm TSMC
Platform Name SP3 SP3 SP3 SP5
Socket LGA 4094 LGA 4094 LGA 4094 LGA 6096
Max Core Count 32 64 64 96
Max Thread Count 64 128 128 192
Max L3 Cache 64 MB 256 MB 256 MB 384 MB?
Chiplet Design 4 CCD’s (2 CCX’s per CCD),4 Die 8 CCD’s (2 CCX’s per CCD) + 1 IOD ,9 Die 8 CCD’s (1 CCX per CCD) + 1 IOD 12 CCD’s (1 CCX per CCD) + 1 IOD
Memory Support DDR4-2666 DDR4-3200 DDR4-3200 DDR5-5200
Memory Channels 8 Channel 8 Channel 8 Channel 12 Channel
PCIe Gen Support 64 Gen 3 128 Gen 4 128 Gen 4 128 Gen 5
TDP Range 200W 280W 280W 320W (cTDP 400W)

命名规范:

image-20220721174306194

Zen1

hygon 5280封装后类似下图(一块CPU封装了2个Die,还有封装4个Die的,core更多更贵而已)

image-20210812204437220

或者4个Die封装在一起

image-20210813085044786

Zen1 Die

下面这块Die集成了两个CCX(每个CCX四个物理core), 同时还有IO接口

Блоки CCX

img

Quad-Zeppelin Configuration, as found in EPYC.

img

Zen CPU Complex(CCX)

hygon 5280使用这个结构, There are 4 cores per CCX and 2 CCXs per die for 8 cores.

  • 44 mm² area
  • L3 8 MiB; 16 mm²
  • 1,400,000,000 transistors

amd zen ccx.png

amd zen ccx 2

封装后的Zen1(4Die)

image-20210813085044786

4个Die的内部关系

AMD Naples SoC.svg

详实数据和结构

Топология процессора

Zen2 Rome

Zen2开始最大的变化就是将IO从Core Die中抽离出来,形成一个专门的IO Die。封装后如下图:

AMD Rome package with card

以上结构的CPU在2路服务器下的内部结构:

img

跨socket的内存访问的数据流跟互联有关,如上图标示,比如从左边的CCD0到右边的CCD0的内存,大概需要经过10跳。

node0 node1 node2 node3 node4 node5 node6 node7
node0 89.67 99.357 108.11 110.54 181.85 187.71 179.507 179.463
node1 90.983 111.65 106.11 188.77 194.7 188.179 189.512
node2 91.2 98.272 180.95 190.53 184.865 186.088
node3 89.971 186.81 193.43 192.459 192.615
node4 89.566 97.943 108.19 109.942
node5 90.927 111.123 108.046
node6 91.212 103.719
node7 89.692

上面表格是3 xGMI互联的情况下,测试出来的访存时延,可以看到在某些node间访存时延会有一些的突增,不够均匀,比如node1到node 5、node2到node5;上述latency跨socket如果用默认BIOS值在280左右

以下表格是厂商默认值和优化值对比(用优化值能将latency从280下降到180左右):

参数 可选项 默认值 (milan:V260 rome:V26.02) 优化值 备注
xGMI Link Width Control Manual/Auto Auto Manual
xGMI Force Link Width Control Unforce/Force Unforce Force
xGMI Force Link Width 0/1/2 2 2 2 = Force xGMI link width to x16
3-link xGMI max speed [00]6.4Gbps …… [0A]16Gbps ……[13]25Gbps *[FF]Auto Auto 16Gbps IEC的rome和milan都是16Gbs,其他产品要与硬件确认

另外发现启用透明大页后测试内存时延能降低20%(通过perf发现没开THP的tlb miss很高)

AMD Rome layout

img

Zen2 Core Complex Die

  • TSMC 7-nanometer process
  • 13 metal layers[1]
  • 3,800,000,000 transistors[2]
  • Die size: 74 mm²
  • CCX size: 31.3 mm², 4core per CCX // 16M L3 perf CCX
  • 2 × 16 MiB L3 cache: 2 × 16.8 mm² (estimated) // 中间蓝色部分是L3 16M,一个Die封装两个CCX的情况下

AMD Zen 2 CCD.jpg

img

在Zen2/Rome架构中,一个CCD由两个CCX构成,一个CCX包含4个物理核,共享16MB的L3 cache。

Zen3

img

在Zen3/Milan架构中,抛弃了两个CCX组成一个CCD的概念,一个CCD直接由8个物理核构成,共享整个Die上的32MB L3 cache。

再就是可以选择增加 v-cache,3D封装更大的L3 cache,如下图,一个CCD 默认是32M L3,但是 v-cache 可以增加一块 64 MB的L3进去(TSMC的SOIC封装在一起),这块 L3 Die 可以单独生产

image-20220923162521398

AMD 3D V-Cache

img

img

Milan-X芯片面积及定价策略

TDP (W) Cores Base Freq (GHz) Max. Freq (GHz) L3(MB) Channels DDR Max DDR Freq PCIeLane
7763 280 64 2.45 3.5 256 8 3200 x128
7773X 280 64 2.2 3.5 768 8 3200 x128

比如上表中 7773X 相对 7763 封装了更大的L3,同时降低了主频来控制发热

下表为标品的芯片面积和售价数据,对比可以看出,扩容2倍L3的芯片整体硅面积增加了31%,售价提升了12%

area mm^2 price 1KU($)
7763 IOD 416+CCD 81*8=1064 7890
7773x +add L3D 41*8=1392 8800

AMD PPOG文档中摘录的关于CPU的micro-bench相关的数据:

1,访存时延上, Vcache普遍有2~6ns的延迟优化;访存带宽上二者基本一致;

2,spec CPU上,整形跑分基本持平,vcache的容量增加部分被主频的降低抵消;浮点跑分提升10%,mem-intensive类型的HPC/AI类应用,将得到比较明显的提升;

3,spec JBB上,vcache的改善明显,critical和max jOPS均得到了10%以上的提升;

Workloads 7763 7773X vcache
NPS4 Core0 Node0 (ns) 85 83
NPS4 Core0 Node1 (ns) 97 92
NPS4 Core0 Node2 (ns) 106 100
NPS4 Core0 Node3 (ns) 109 104
STREAM Add (GBps) 100% 99.9%
STREAM Copy(GBps) 100% 99.9%
STREAM Scale(GBps) 100% 100.1%
STREAM Triad(GBps) 100% 99.8%
SPEC CPU2017 FP Rate Base 100% 109.8%
SPEC CPU2017 Int Rate Base 100% 100.9%
SPECjbb2015-MultiJVM Critical-Jops 100% 111.6%
SPECjbb2015-MultiJVM Max-jOPS 100% 116.7%

Zen1 VS Zen2

Here is what the Naples and Rome packages look like from the outside:

img

numa

image-20210813091455662

zen1 numa distance:

img

hygon numa distance:

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
# numactl -H  //Zen1 hygon 7280  2 socket enable die interleaving
available: 2 nodes (0-1)
node 0 cpus: 0 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 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
node 0 size: 257578 MB
node 0 free: 115387 MB
node 1 cpus: 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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
node 1 size: 257005 MB
node 1 free: 221031 MB
node distances:
node 0 1
0: 10 22
1: 22 10

#numactl -H //Zen1 hygon 5280 2 socket disable die interleaving
available: 4 nodes (0-3)
node 0 cpus: 0 1 2 3 4 5 6 7 32 33 34 35 36 37 38 39
node 0 size: 128854 MB
node 0 free: 89350 MB
node 1 cpus: 8 9 10 11 12 13 14 15 40 41 42 43 44 45 46 47
node 1 size: 129019 MB
node 1 free: 89326 MB
node 2 cpus: 16 17 18 19 20 21 22 23 48 49 50 51 52 53 54 55
node 2 size: 128965 MB
node 2 free: 86542 MB
node 3 cpus: 24 25 26 27 28 29 30 31 56 57 58 59 60 61 62 63
node 3 size: 129020 MB
node 3 free: 98227 MB
node distances:
node 0 1 2 3
0: 10 16 28 22
1: 16 10 22 28
2: 28 22 10 16
3: 22 28 16 10

看完这些结构上的原理,让我们实际来看看AMD的性能怎么样。

hygon 7280 PCM数据

hygon pcm(performance counter monitor) 工具由芯片公司提供

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
[root@hygon3 16:58 /root/PCM]
#./pcm.x -r -topdown -i=1 -nc -ns -l2

Processor Counter Monitor (2019-08-21 17:07:31 +0800 ID=378f2fc)

Number of physical cores: 64
Number of logical cores: 128
Number of online logical cores: 128
Threads (logical cores) per physical core: 2
Num sockets: 2
Physical cores per socket: 32
Core PMU (perfmon) version: 3
Number of core PMU generic (programmable) counters: 6
Width of generic (programmable) counters: 64 bits
Ccxs per Node: 8
Logical cores per Ccx: 8
Physical Cores per Ccx: 4
Nodes per socket: 4
Number of core PMU fixed counters: 0
Width of fixed counters: 0 bits
Nominal core frequency: 2000000000 Hz
Package thermal spec power: -1 Watt; Package minimum power: -1 Watt; Package maximum power: -1 Watt;

Resetting PMU configuration
Zeroed PMU registers

Detected Hygon C86 7280 32-core Processor "Hygon(r) microarchitecture codename DHYANA" stepping 1

EXEC : instructions per nominal CPU cycle
IPC : instructions per CPU cycle
FREQ : relation to nominal CPU frequency='unhalted clock ticks'/'invariant timer ticks' (includes Intel Turbo Boost)
AFREQ : relation to nominal CPU frequency while in active state (not in power-saving C state)='unhalted clock ticks'/'invariant timer ticks while in C0-state' (includes Intel Turbo Boost)
L3MISS: L3 (read) cache misses
L3MPKI: L3 misses per kilo instructions
L3HIT : L3 (read) cache hit ratio (0.00-1.00)
L2DMISS:L2 data cache misses
L2DHIT :L2 data cache hit ratio (0.00-1.00)
L2DMPKI:number of L2 data cache misses per kilo instruction
L2IMISS:L2 instruction cache misses
L2IHIT :L2 instructoon cache hit ratio (0.00-1.00)
L2IMPKI:number of L2 instruction cache misses per kilo instruction
L2MPKI :number of both L2 instruction and data cache misses per kilo instruction

Core (SKT) | EXEC | IPC | FREQ | AFREQ | L2DMISS| L2DHIT | L2DMPKI| L2IMISS| L2IHIT | L2IMPKI| L2MPKI | L3MISS | L3MPKI | L3HIT | TEMP

---------------------------------------------------------------------------------------------------------------
TOTAL * 1.29 1.20 1.08 1.00 12 M 0.73 0.04 10 M 0.87 0.03 0.07 19 M 0.00 0.55 N/A

Instructions retired: 336 G ; Active cycles: 281 G ; Time (TSC): 2082 Mticks ; C0 (active,non-halted) core residency: 107.90 %


PHYSICAL CORE IPC : 2.39 => corresponds to 34.14 % utilization for cores in active state
Instructions per nominal CPU cycle: 2.58 => corresponds to 36.84 % core utilization over time interval
---------------------------------------------------------------------------------------------------------------

Cleaning up
Zeroed PMU registers

在本地启动benchmarksql压力,并将进程绑定到0-8core,然后采集到数据:

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
#./pcm.x -r -topdown -i=1 -l2

Processor Counter Monitor (2019-08-21 17:07:31 +0800 ID=378f2fc)

Number of physical cores: 64
Number of logical cores: 128
Number of online logical cores: 128
Threads (logical cores) per physical core: 2
Num sockets: 2
Physical cores per socket: 32
Core PMU (perfmon) version: 3
Number of core PMU generic (programmable) counters: 6
Width of generic (programmable) counters: 64 bits
Ccxs per Node: 8
Logical cores per Ccx: 8
Physical Cores per Ccx: 4
Nodes per socket: 4
Number of core PMU fixed counters: 0
Width of fixed counters: 0 bits
Nominal core frequency: 2000000000 Hz
Package thermal spec power: -1 Watt; Package minimum power: -1 Watt; Package maximum power: -1 Watt;

Resetting PMU configuration
Zeroed PMU registers

Detected Hygon C86 7280 32-core Processor "Hygon(r) microarchitecture codename DHYANA" stepping 1

EXEC : instructions per nominal CPU cycle
IPC : instructions per CPU cycle
FREQ : relation to nominal CPU frequency='unhalted clock ticks'/'invariant timer ticks' (includes Intel Turbo Boost)
AFREQ : relation to nominal CPU frequency while in active state (not in power-saving C state)='unhalted clock ticks'/'invariant timer ticks while in C0-state' (includes Intel Turbo Boost)
L3MISS: L3 (read) cache misses
L3MPKI: L3 misses per kilo instructions
L3HIT : L3 (read) cache hit ratio (0.00-1.00)
L2DMISS:L2 data cache misses
L2DHIT :L2 data cache hit ratio (0.00-1.00)
L2DMPKI:number of L2 data cache misses per kilo instruction
L2IMISS:L2 instruction cache misses
L2IHIT :L2 instructoon cache hit ratio (0.00-1.00)
L2IMPKI:number of L2 instruction cache misses per kilo instruction
L2MPKI :number of both L2 instruction and data cache misses per kilo instruction

Core (SKT) | EXEC | IPC | FREQ | AFREQ | L2DMISS| L2DHIT | L2DMPKI| L2IMISS| L2IHIT | L2IMPKI| L2MPKI | L3MISS | L3MPKI | L3HIT | TEMP

0 0 1.34 1.26 1.06 1.00 8901 K 0.72 3.15 15 M 0.68 5.43 8.58 71 M 4.00 0.60 N/A
1 0 1.42 1.33 1.06 1.00 8491 K 0.73 2.83 14 M 0.68 4.67 7.50 71 M 4.00 0.60 N/A
2 0 1.41 1.33 1.06 1.00 8206 K 0.74 2.75 12 M 0.72 4.25 7.00 71 M 4.00 0.60 N/A
3 0 1.46 1.38 1.06 1.00 7464 K 0.75 2.40 11 M 0.68 3.81 6.21 71 M 4.00 0.60 N/A
4 0 1.31 1.24 1.06 1.00 9118 K 0.71 3.28 15 M 0.69 5.61 8.88 70 M 4.00 0.61 N/A
5 0 1.41 1.33 1.06 1.00 8700 K 0.74 2.92 13 M 0.69 4.66 7.57 70 M 4.00 0.61 N/A
6 0 1.41 1.33 1.06 1.00 8094 K 0.74 2.79 12 M 0.70 4.40 7.18 70 M 4.00 0.61 N/A
7 0 1.43 1.35 1.06 1.00 7873 K 0.74 2.68 12 M 0.71 4.13 6.81 70 M 4.00 0.61 N/A
8 0 1.44 1.36 1.06 1.00 8544 K 0.73 2.79 14 M 0.67 4.87 7.66 20 M 1.00 0.61 N/A
9 0 1.24 1.16 1.06 1.00 524 K 0.51 0.21 86 K 0.94 0.03 0.24 20 M 1.00 0.61 N/A
10 0 1.26 1.18 1.07 1.00 379 K 0.50 0.15 60 K 0.95 0.02 0.17 20 M 1.00 0.61 N/A
11 0 1.24 1.16 1.07 1.00 533 K 0.50 0.20 96 K 0.94 0.04 0.24 20 M 1.00 0.61 N/A
12 0 1.22 1.14 1.07 1.00 1180 K 0.34 0.47 98 K 0.94 0.04 0.51 3872 K 0.12 0.46 N/A
13 0 1.24 1.16 1.07 1.00 409 K 0.49 0.16 64 K 0.94 0.03 0.19 3872 K 0.12 0.46 N/A

---------------------------------------------------------------------------------------------------------------
SKT 0 1.18 1.11 1.06 1.00 113 M 0.67 0.73 139 M 0.71 0.89 1.62 186 M 1.12 0.59 N/A
SKT 1 1.23 1.14 1.08 1.00 33 M 0.53 0.21 11 M 0.89 0.07 0.28 38 M 0.12 0.45 N/A
---------------------------------------------------------------------------------------------------------------
TOTAL * 1.21 1.13 1.07 1.00 147 M 0.65 0.46 150 M 0.74 0.47 0.93 224 M 0.62 0.57 N/A

Instructions retired: 319 G ; Active cycles: 283 G ; Time (TSC): 2108 Mticks ; C0 (active,non-halted) core residency: 107.12 %


PHYSICAL CORE IPC : 2.25 => corresponds to 32.18 % utilization for cores in active state
Instructions per nominal CPU cycle: 2.41 => corresponds to 34.48 % core utilization over time interval
---------------------------------------------------------------------------------------------------------------

Cleaning up
Zeroed PMU registers

Apple M1

M1, M1 Pro, and M1 Max chips are shown next to each other.

The M1

The critically-acclaimed M1 processor delivers:

  • 16 billion transistors and a 119mm squared-die size.
  • 4 performance cores, 12MB L2 Cache.
  • 4 efficiency cores ith 4MB L2 cache.
  • 8 GPU Cores.
  • 16GB DDR4x memory at 68GB/s.

The M1 Pro

The M1 Pro takes this higher, with:

  • 33.7 billion transistors on a 240mm squared die.
  • 8 performance cores, 24MB L2 Cache.
  • 2 efficiency cores with 4MB L2 cache.
  • 16 GPU Cores.
  • 32GB DDR5 memory at 200GB/s.

对比下 i9-12000,i9也有GPU只是没有说多少个,它的GPU频率在0.3到1.55GHz之间

alder lake die 2.png

ISA x86-64 (x86)
Microarchitecture Alder Lake, Golden Cove, Gracemont
Process Intel 7
Die 215.25 mm²” 20.5 mm × 10.5 mm
MCP No (1 dies)
Cores 16
Threads 24
l1$ size 0.75 MiB (768 KiB, 786,432 B, 7.324219e-4 GiB) + and 0.625 MiB (640 KiB, 655,360 B, 6.103516e-4 GiB) +
l1d$ size 0.25 MiB (256 KiB, 262,144 B, 2.441406e-4 GiB) + and 0.375 MiB (384 KiB, 393,216 B, 3.662109e-4 GiB) +
l1i$ size 0.5 MiB (512 KiB, 524,288 B, 4.882812e-4 GiB) + and 0.25 MiB (256 KiB, 262,144 B, 2.441406e-4 GiB) +
l2$ size 4 MiB (4,096 KiB, 4,194,304 B, 0.00391 GiB) + and 10 MiB (10,240 KiB, 10,485,760 B, 0.00977 GiB) +
l3$ size 6 MiB (6,144 KiB, 6,291,456 B, 0.00586 GiB) + and 24 MiB (24,576 KiB, 25,165,824 B, 0.0234 GiB) +

The M1 Max

The M1 Max provides:

  • 57 billion transistors on a 420mm squared die.
  • 8 performance cores, 24MB L2 Cache.
  • 2 efficiency cores with 4MB L2 cache.
  • 32 GPU Cores.
  • 64GB DDR5 memory at 400GB/s.

And the new M1 Ultra

The M1 Ultra brings you:

  • 114 billion transistors on a 840mm squared die.
  • 16 performance cores, 48MB L2 Cache.
  • 4 efficiency cores with 4MB L2 cache.
  • 64 GPU Cores.
  • Up to 128GB DDR5 memory at 800GB/s.

倚天710

一个die有64core,每两个core是一个cluster,一块cpu封装两个die

一个die大小是314平方毫米,600亿晶体管

image-20211205130348832

平头哥的几款芯片:

preview

总结

AMD和Intel在服务器领域CPU设计上走了两个不同的方向,Intel通过RingBus、Mesh等方案在一块Die上集成多个core,成本高,在多核场景下性能好。

AMD则是通过设计小的Die来降低成本,然后将多个Die封装到一块CPU上来售卖,Zen1架构的多个Die之间延迟高,于是Zen2将IO抽离出来用一块单独的IO Die来负责IO,这样多核之间的时延比Zen1好了很多。

而在云计算场景下AMD的设计非常有竞争优势,因为云计算大部分时候是要把一块大的CPU分拆售卖,从架构上AMD对分拆售卖非常友好。

整体来说AMD用领先了一代的工艺(7nm VS 14nm),在MySQL查询场景中终于可以接近Intel了,但是海光、鲲鹏、飞腾还是不给力。

参考资料

CPU的制造和概念

[CPU 性能和Cache Line](/2021/05/16/CPU Cache Line 和性能/)

[Perf IPC以及CPU性能](/2021/05/16/Perf IPC以及CPU利用率/)

Intel、海光、鲲鹏920、飞腾2500 CPU性能对比

飞腾ARM芯片(FT2500)的性能测试

十年后数据库还是不敢拥抱NUMA?

一次海光物理机资源竞争压测的记录

[Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的](/2019/12/16/Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的/)

lmbench测试要考虑cache等

CPU性能和CACHE

发表于 2021-07-19 | 分类于 CPU

CPU性能和CACHE

为了让程序能快点,特意了解了CPU的各种原理,比如多核、超线程、NUMA、睿频、功耗、GPU、大小核再到分支预测、cache_line失效、加锁代价、IPC等各种指标(都有对应的代码和测试数据)都会在这系列文章中得到答案。当然一定会有程序员最关心的分支预测案例、Disruptor无锁案例、cache_line伪共享案例等等。

这次让我们从最底层的沙子开始用8篇文章来回答各种疑问以及大量的实验对比案例和测试数据。

大的方面主要是从这几个疑问来写这些文章:

  • 同样程序为什么CPU跑到800%还不如CPU跑到200%快?
  • IPC背后的原理和和程序效率的关系?
  • 为什么数据库领域都爱把NUMA关了,这对吗?
  • 几个国产芯片的性能到底怎么样?

系列文章

CPU的制造和概念

[Perf IPC以及CPU性能](/2021/05/16/Perf IPC以及CPU利用率/)

[CPU 性能和Cache Line](/2021/05/16/CPU Cache Line 和性能/)

十年后数据库还是不敢拥抱NUMA?

[Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的](/2019/12/16/Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的/)

Intel、海光、鲲鹏920、飞腾2500 CPU性能对比

一次海光物理机资源竞争压测的记录

飞腾ARM芯片(FT2500)的性能测试

image-20210802161558248

CPU中为什么要L1/L2等各级cache

因为CPU的速度和访问内存速度差异太大,导致CPU在计算的时候90%以上的时间花在等待从内存中取数据、写数据而此时CPU处于闲置状态,也就导致了所谓的 内存墙

cpu的速度大概50-60%每年的增长率,内存只有7%每年增长率:

A 1000× Improvement of the Processor-Memory Gap | SpringerLink

CPU访问内存慢的案例参考:Gallery of Processor Cache Effects

在数据使用前加载到CPU内更快的缓存中,最快的一级缓存等待时间是1~3个时钟周期。限制在于对于不在缓存中的数据,还是要等待数十上百个周期——按50周期算的话,不考虑并发和指令执行时间,缓存命中率达到98%,才能发挥一半的理论性能。然而实际情况中,大部分应用都无法达到这个命中率。

image-20211110174606037

CPU中的cache变迁历史

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

486 motherboard with CPU location and 2nd level cache marked

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

Picture of a pentium Pro CPU, 256KB cache model

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

modernCPUwithL3.png

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

image-20211110174810752

下图是目前一个主流的Die中CACHE的构成:

img

cache对速度的影响:

  • 一个方面是物理速度,如果要更大的容量就需要更多的晶体管,除了芯片的体积会变大,更重要的是大量的晶体管会导致速度下降,因为访问速度和要访问的晶体管所在的位置成反比,也就是当信号路径变长时,通信速度会变慢。这部分是物理问题。
  • 另外一个问题是,多核技术中,数据的状态需要在多个CPU中进行同步,并且,我们可以看到,cache和RAM的速度差距太大,所以,多级不同尺寸的缓存有利于提高整体的性能。

cache 大小查看

1
2
3
4
5
6
7
8
9
[root@bugu88 cpu0]# cd /sys/devices/system/
[root@bugu88 cpu0]# cat cache/index0/size
32K
[root@bugu88 cpu0]# cat cache/index1/size
32K
[root@bugu88 cpu0]# cat cache/index2/size
512K
[root@bugu88 cpu0]# cat cache/index3/size
32768K

不同型号CPU的cache、内存时延

测试命令:

1
numactl --membind=0 --cpunodebind=0 ./bin/lat_mem_rd 2000 64 //从结果看L3/memory latency不符合常识

image-20220304104859770

调整测试参数,增加 -t 参数

1
numactl -C 0 -m 0 ./bin/lat_mem_rd -W 5 -N 5 -t 2000M

内存基准测试命令 lat_mem_rd 的 -t 参数指定测试集以制造 TLB miss, Cache miss的压力场景,以测试 TLB miss,Cache miss对内存访问延迟的影响

image-20220304152056740

从上图可以看到的一些测试结论

  • 添加 -t 后(第二组测试),L2和L3的延时比较正常了
  • 倒数第三图hygon 7280 2node VS 8node(橙色) , 可以看到8node 内存延时降低了25%
  • 飞腾没开numa内存延时抖动非常大(倒数图二,灰色线),基本不可用,整体延时也比其它CPU高很多
  • hygon L3大小比较特殊,一个socket下多个Die之间没有共享
  • intel E5时延表现很优秀,intel E5 CPU开启numa后内存延时有30%以上的减少(图三)
  • 鲲鹏数据比较中规中矩,接近intel
  • stride参数、-t参数对整体数据影响比较大,x86、arm不同参数下也不一样

E5机器内存速度为2133 MT/S, 8163和8269则是2666 MT/S, 所以说E5的时延表现很优秀

矩阵乘法案例

不做任何处理,最直白的矩阵乘法运算,在Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz 运行情况

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
#cat simple.c
#include <stdlib.h>
#include <stdio.h>
#include <emmintrin.h>
#define N 2000
double res[N][N] __attribute__ ((aligned (64)));
double mul1[N][N] __attribute__ ((aligned (64)));
double mul2[N][N] __attribute__ ((aligned (64)));
#define SM (CLS / sizeof (double))

//compile:gcc -o simd -DCLS=$(getconf LEVEL1_DCACHE_LINESIZE) ./simd.c
//
int main (void)
{
// ... Initialize mul1 and mul2
int i, i2, j, j2, k, k2;

for (i = 0; i < N; ++i)
for (j = 0; j < N; ++j)
for (k = 0; k < N; ++k)
res[i][j] += mul1[i][k] * mul2[k][j]; //mul2[k][j]是先列后行,对cache不友好;

// ... use res matrix
return 0;
}

如果现将矩阵转置一下

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
#include <stdlib.h>
#include <stdio.h>
#include <emmintrin.h>
#define N 2000
double res[N][N] __attribute__ ((aligned (64)));
double mul1[N][N] __attribute__ ((aligned (64)));
double mul2[N][N] __attribute__ ((aligned (64)));
double tmp[N][N] __attribute__ ((aligned (64)));
#define SM (CLS / sizeof (double))

//compile:gcc -o simd -DCLS=$(getconf LEVEL1_DCACHE_LINESIZE) ./simd.c
//
int main (void)
{
// ... Initialize mul1 and mul2
int i, i2, j, j2, k, k2;

for (i = 0; i < N; ++i)
for (j = 0; j < N; ++j)
tmp[i][j] = mul2[j][i]; //先转置
for (i = 0; i < N; ++i)
for (j = 0; j < N; ++j)
for (k = 0; k < N; ++k)
res[i][j] += mul1[i][k] * tmp[j][k]; //转置后按行访问,对内存友好

// ... use res matrix
return 0;
}

执行结果

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
//未做任何优化,直接矩阵乘法
#taskset -c 1 perf stat ./simple
47192.640339 task-clock (msec) # 1.001 CPUs utilized
88 context-switches # 0.002 K/sec
1 cpu-migrations # 0.000 K/sec
31,392 page-faults # 0.665 K/sec
117,866,224,774 cycles # 2.498 GHz
<not supported> stalled-cycles-frontend
<not supported> stalled-cycles-backend
264,254,238,724 instructions # 2.24 insns per cycle
8,052,145,218 branches # 170.623 M/sec
4,573,572 branch-misses # 0.06% of all branches

47.151498977 seconds time elapsed

//转置后都是按行取数据,但是需要额外的空间
#taskset -c 0 perf stat ./simp2
30457.259168 task-clock (msec) # 1.001 CPUs utilized
137 context-switches # 0.004 K/sec
7 cpu-migrations # 0.000 K/sec
86,081 page-faults # 0.003 M/sec
76,068,232,551 cycles # 2.498 GHz
<not supported> stalled-cycles-frontend
<not supported> stalled-cycles-backend
264,385,818,470 instructions # 3.48 insns per cycle
8,072,001,639 branches # 265.027 M/sec
4,414,867 branch-misses # 0.05% of all branches

30.437018792 seconds time elapsed

//按cache line 运算
#taskset -c 1 perf stat ./s3
29767.847109 task-clock (msec) # 1.001 CPUs utilized
41 context-switches # 0.001 K/sec
1 cpu-migrations # 0.000 K/sec
31,454 page-faults # 0.001 M/sec
74,346,857,277 cycles # 2.498 GHz
<not supported> stalled-cycles-frontend
<not supported> stalled-cycles-backend
253,099,702,393 instructions # 3.40 insns per cycle
11,450,804,877 branches # 384.670 M/sec
16,043,642 branch-misses # 0.14% of all branches

29.742025067 seconds time elapsed

//使用simd指令,按理应该最快,实际效果很差 :(
#taskset -c 1 perf stat ./simd
140224.550539 task-clock (msec) # 1.001 CPUs utilized
243 context-switches # 0.002 K/sec
2 cpu-migrations # 0.000 K/sec
70,569 page-faults # 0.503 K/sec
350,218,614,852 cycles # 2.498 GHz
<not supported> stalled-cycles-frontend
<not supported> stalled-cycles-backend
717,191,577,191 instructions # 2.05 insns per cycle
25,161,922,136 branches # 179.440 M/sec
54,411,349 branch-misses # 0.22% of all branches

140.101635085 seconds time elapsed

On ARM Kunpeng 920-4826:

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
#taskset -c 1 perf stat ./simple
150,242.52 msec task-clock # 1.000 CPUs utilized
943 context-switches # 0.006 K/sec
0 cpu-migrations # 0.000 K/sec
31,289 page-faults # 0.208 K/sec
390,626,613,178 cycles # 2.600 GHz
432,396,482,134 instructions # 1.11 insn per cycle
<not supported> branches
11,348,599 branch-misses

150.249408485 seconds time elapsed

#taskset -c 1 perf stat ./simp2
69,008.66 msec task-clock # 1.000 CPUs utilized
426 context-switches # 0.006 K/sec
0 cpu-migrations # 0.000 K/sec
39,104 page-faults # 0.567 K/sec
179,417,225,187 cycles # 2.600 GHz
432,409,078,894 instructions # 2.41 insn per cycle
<not supported> branches
11,122,131 branch-misses

69.014491453 seconds time elapsed

#taskset -c 1 perf stat ./s3
50,251.34 msec task-clock # 1.000 CPUs utilized
315 context-switches # 0.006 K/sec
0 cpu-migrations # 0.000 K/sec
31,289 page-faults # 0.623 K/sec
130,652,187,736 cycles # 2.600 GHz
291,261,746,765 instructions # 2.23 insn per cycle
<not supported> branches
160,585,583 branch-misses

50.254025852 seconds time elapsed

如果在aarch编译开启gcc -O3 优化选项:

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
//aarch gcc -O3 on
#taskset -c 1 perf stat ./simple //开O3后 优化器走了simd指令
67,897.93 msec task-clock # 1.000 CPUs utilized
414 context-switches # 0.006 K/sec
0 cpu-migrations # 0.000 K/sec
31,289 page-faults # 0.461 K/sec
176,532,812,062 cycles # 2.600 GHz
28,214,139,367 instructions # 0.16 insn per cycle
<not supported> branches
3,250,598 branch-misses


#perf stat ./s2 //s2代码直接按行访问mul2,不考虑结果对错,运算量一样,相当于整体转置
15,963.30 msec task-clock # 1.000 CPUs utilized
20 context-switches # 0.001 K/sec
0 cpu-migrations # 0.000 K/sec
31,288 page-faults # 0.002 M/sec
41,504,239,031 cycles # 2.600 GHz
56,108,176,644 instructions # 1.35 insn per cycle
<not supported> branches
4,586,197 branch-misses


#taskset -c 1 perf stat ./s3
5,695.85 msec task-clock # 1.000 CPUs utilized
35 context-switches # 0.006 K/sec
0 cpu-migrations # 0.000 K/sec
31,289 page-faults # 0.005 M/sec
14,808,977,314 cycles # 2.600 GHz
24,281,358,553 instructions # 1.64 insn per cycle
<not supported> branches
2,006,221 branch-misses


s3.c反编译后的汇编:
bc: 913a0060 add x0, x3, #0xe80
c0: eb04001f cmp x0, x4
c4: 1f584010 fmadd d16, d0, d24, d16
c8: 1f571c07 fmadd d7, d0, d23, d7 //参数 d7-精度,d0
cc: 1f561806 fmadd d6, d0, d22, d6
d0: 1f551405 fmadd d5, d0, d21, d5
d4: 1f541004 fmadd d4, d0, d20, d4
d8: 1f530c03 fmadd d3, d0, d19, d3
dc: 1f520802 fmadd d2, d0, d18, d2
e0: 1f510401 fmadd d1, d0, d17, d1
e4: 54fffd81 b.ne 94 <main+0x94>
e8: 91400c22 add x2, x1, #0x3, lsl #12
ec: fd000030 str d16, [x1]

FMADD指令

Floating-point fused Multiply-Add (scalar). This instruction multiplies the values of the first two SIMD&FP source registers, adds the product to the value of the third SIMD&FP source register, and writes the result to the SIMD&FP destination register.

一些对比解释:

编译优化选项设置-O2 级别及以上时,Kunpeng 处理器将对连续的浮点数乘法、加法融 合为乘加运算,以提升性能和精度。在-O2 级以上编译选项,x86 处理器不会将乘法和 加法做融合乘加运算,因此两种处理器在连续的浮点数乘法、加法运算后,小数点后 16 位存在差异。

cache对CPU性能的影响

CPU访问内存是非常慢的,所以我们在CPU中增加了多级缓存来匹配CPU和内存的速度。主频这20年基本都没怎么做高了,但是工艺提升了两个数量级,也就是集成的晶体管数量提升了2个数量级,工艺提升的能力主要给了cache,从而整体CPU性能提升了很多。

缓存对Oceanbase ,MySQL, ODPS的性能影响

以下测试数据主要来源于真实的业务场景:OB/MySQL/ODPS

img

x86 Skylake之前,L1 I/D 32KB, L2 256KB, L3 2.5MB/core, 2.5MB/core的L3(LLC)芯片面积相当于1/2 CPU core 的尺寸

  1. 关闭L3(2.5MB),关闭L2(256KB),此时性能CPI(越小越好)是4.25
  2. 关闭L3,打开L2(256KB),此时性能CPI为2.23
  3. 关闭L3,打开L2同时增加256KB,L2尺寸到512KB,性能CPI为1.38
  4. 打开L3(2.5MB),打开L2(256KB),性能为1.28 ,该状态就是intel CPU出厂的状态
  5. 打开L3,增加到16MB,打开L2(256KB),性能为1.25

上面的数据显示当L3关闭之后,从case 3 开始,L2仅仅增加256KB,L2芯片面积相对于CPU core 增加 5%(0.5 /2.5M * 025M),性能相对于case 2 提升1.61倍(2.23/1.38),而使用case 4 ,L3 2.5MB打开,相对于case 3,增加2.3MB(2.5MB - 256KB),芯片面积相对于CPU core 增加 46%(0.5/2.5M * 2.3M), 而性能仅仅提升 1.07倍(1.38/1.28),所以14年给Intel提议需要增加L2尺寸降低L3尺寸,这些数据促使Intel开始重新考虑对于数据中心缓存新的设计。

2014年的 Broadwell 的第五代智能酷睿处理器,是 Haswell 的 14nm 升级版($1745.00 - $1749.00):

image-20210719102039296

E5一个Die有16个物理core(上面截图是两个Socket, 每个Socket一个Die,每个物理core两个超线程),所以每core的L3大小:40M/16=2.5M/core

2015年则推出 SkyLake 架构的Platinum 8269CY($4702.00), 每core的L3大小:36M/26=1.38M/core:

image-20210719102112331

Intel 2015年 发表论文《High Performing Cache Hierarchies for Server Workloads》证明了阿里提出的建议的正确性,从Skylake架构开始将L2 cache 由 256KB 升级到 1MB, L3由2.5MB /core 压缩到 1.375MB / core, Intel之所以没有完全去掉L3的原因是希望这样设计的CPU对于 使用 CPU2006的workload性能仍然能够做到不受影响。

image-20210716102624566

上图是不同业务场景下,CPI 随cache大小的变化,可以看到随着cache增加性能基本不增加了。

CPU L2, Last Level Cache (LLC) 缓存的演变

Last Level Cache(L3) 在2016年之前都是2MB/core 或者 2.5MB/core, 这个原因取决于在此之前行业都是使用CPU2006作为设计CPU的benchmark,如下图所示:

img

根据上图中CPU2006的MPKI数据显示如果LLC在4MB的时候非常好,LLC在2.5MB之后MKPI提升10%性能只有1~3%的提升,2.5MB LLC cache是 CPU core 1/2 的芯片面积,因此若将LLC 由2.5MB升级到4MB,换算成CPU core的芯片面积是增长30%(1/2 * 1.5M/2.5M),但性能仅仅提升最多3%,这就是为什么基于CPU2006的benchmark条件下,intel将LLC设定为2~2.5MB的原因。

Cache的缺点

缓存有两大缺点:

  • 当数据集非常大的时候,时间空间局部性较低时缓存的工作效率很低;
  • 当缓存工作效率高的时候,局部性非常高,这意味着,根据定义,大多数缓存在大多数时间都处于空闲状态。

Hardware Memory Models 顺序一致性

对存储在内存中数据更改的可见性和一致性,所以这个契约被称为内存一致性模型(memory consistency model)或仅仅是内存模型(memory model)

r1/r2是线程本地变量,如下代码的可能结果是哪些?

1
2
3
4
5
6
Litmus Test: Message Passing
Can this program see r1 = 1, r2 = 0?

// Thread 1 // Thread 2
x = 1 r1 = y
y = 1 r2 = x

如果该litmus test的执行顺序一致,则只有六种可能的交替:

img

因为没有交替执行的结果会产生r1 = 1, r2 = 0,所以这个结果是不允许的。也就是说,在顺序执行的硬件上,litmus test执行结果出现r1 = 1, r2 = 0是不可能的。

顺序一致性的一个很好的思维模型是想象所有处理器直接连接到同一个共享内存,它可以一次处理一个线程的读或写请求。 不涉及缓存,因此每次处理器需要读取或写入内存时,该请求都会转到共享内存。 一次使用一次的共享内存对所有内存访问的执行施加了顺序顺序:顺序一致性。

img

x86 Total Store Order (x86-TSO) 总存储有序

所有处理器仍然连接到一个共享内存,但是每个处理器都将对该内存的写入(write)放入到本地写入队列中。处理器继续执行新指令,同时写操作(write)会更新到这个共享内存。一个处理器上的内存读取在查询主内存之前会查询本地写队列,但它看不到其他处理器上的写队列。其效果就是当前处理器比其他处理器会先看到自己的写操作。但是——这一点非常重要——==所有处理器都保证写入(存储store)到共享内存的(总)顺序,所以给这个模型起了个名字:总存储有序,或TSO==。当一个写操作到达共享内存时,任何处理器上的任何未来读操作都将看到它并使用该值(直到它被以后的写操作覆盖,或者可能被另一个处理器的缓冲写操作覆盖)。

img

针对前文的litmus test案例,写队列保证线程1在y之前将x写入内存,关于内存写入顺序(总存储有序)的系统级协议保证线程2在读y的新值之前读x的新值。因此,r1 = y在r2 = x看不到新的x之前不可能看到新的y。存储顺序至关重要:线程1在写入y之前先写入x,因此线程2在看到x的写入之前不可能看到y的写入。

但是对于TSO系统下,以下case能看到r1 = 0, r2 = 0, 如果在顺序一致性的协议下这是不可能发生的

1
2
3
4
5
6
7
8
Litmus Test: Write Queue (also called Store Buffer)
Can this program see r1 = 0, r2 = 0?

// Thread 1 // Thread 2
x = 1 y = 1
r1 = y r2 = x
On sequentially consistent hardware: no.
On x86 (or other TSO): yes!

为了让TSO和顺序一致性协议保持一致,我们需要依赖于更强的内存排序,非顺序一致的硬件提供了称为内存屏障(或栅栏)的显式指令,可用于控制排序。我们可以添加一个内存屏障,以确保每个线程在开始读取之前都会刷新其先前对内存的写入:

1
2
3
4
// Thread 1           // Thread 2
x = 1 y = 1
barrier barrier
r1 = y r2 = x

加上正确的障碍,r1 = 0,r2 = 0也是不可能的了。内存屏障有很多种,它的存在给了程序员或语言实现者一种在程序的关键时刻强制顺序一致行为的方法。

ARM/POWER Relaxed Memory Model

ARM和POWER系统的概念模型是,每个处理器从其自己的完整内存副本中读取和向其写入,每个写入独立地传播到其他处理器,随着写入的传播,允许重新排序。

img

这里没有总存储顺序。虽然没有描述,但是每个处理器都被允许推迟读取(read),直到它等到它需要结果:读取(read)可以被延迟到稍后的写入(write)之后。在这个宽松的(relaxed)模型中,我们迄今为止所看到的每一个litmus test的答案都是“yes,这真的可能发生。”

在这个内存模型下,对于前文中的 Litmus Test: Message Passing case是可以看到r1=1,r2=0的(TSO保证不会),但是可以保证 Litmus Test: Store Buffering case 和TSO一致。

最后再附加几个Latency数据,让大家比较起来更有体感一些

各级IO延迟数字

Cache、内存、磁盘、网络的延迟比较

假设主频2.6G的CPU,每个指令只需要 0.38ns

每次内存寻址需要 100ns

一次 CPU 上下文切换(系统调用)需要大约 1500ns,也就是 1.5us(这个数字参考了这篇文章,采用的是单核 CPU 线程平均时间)

SSD 随机读取耗时为 150us

从内存中读取 1MB 的连续数据,耗时大约为 250us

同一个数据中心网络上跑一个来回需要 0.5ms

从 SSD 读取 1MB 的顺序数据,大约需要 1ms (是内存速度的四分之一)

磁盘寻址时间为 10ms

从磁盘读取 1MB 连续数据需要 20ms

如果 CPU 访问 L1 缓存需要 1 秒,那么访问主存需要 3 分钟、从 SSD 中随机读取数据需要 3.4 天、磁盘寻道需要 2 个月,网络传输可能需要 1 年多的时间。

内存和cache的latency对比

latency

各级cache的Latency:

Cycle times

2012 年延迟数字对比表:

Work Latency
L1 cache reference 0.5 ns
Branch mispredict 5 ns
L2 cache reference 7 ns
Mutex lock/unlock 25 ns
Main memory reference 100 ns
持久内存 300 ns
Compress 1K bytes with Zippy 3,000 ns
Send 1K bytes over 1 Gbps network 10,000 ns
Read 4K randomly from SSD* 150,000 ns
Read 1 MB sequentially from memory 250,000 ns
Round trip within same datacenter 500,000 ns
Read 1 MB sequentially from SSD* 1,000,000 ns
Disk seek 10,000,000 ns
Read 1 MB sequentially from disk 20,000,000 ns
Send packet CA->Netherlands->CA 150,000,000 ns

一个比较有体感的比较:如果 CPU 访问 寄存器需要 1 秒,那么访问主存需要 3 分钟、从 SSD 中随机读取数据需要 3.4 天、磁盘寻道需要 2 个月,网络传输可能需要 1 年多的时间。

img

当然更古老一点的年代给出来的数据可能又不一样一点,但是基本比例差异还是差不多的:

Memory Hierarchy

img

测试Inte E5 L1 、L2、L3的cache延时图来加深印象,可以看到在每级cache大小附近时延有个跳跃(纵坐标是纳秒,横坐标是大小 M):

image-20220321172431647

推荐从这里看延时,拖动时间轴可以看到随着技术、工艺的改变Latency每一年的变化

image-20210613123006681

查看cpu cache数据

cat /proc/cpuinfo |grep -i cache
image.png

L1C、L2C、L3C、DDR 的Latency测试数据

下图从左至右响应时间分别是L1C、L2C、L3C、DDR,可以看出这四个Latency变化还是非常明显的,泾渭分明。

img

image-20210511160107225

image.png

测试memory latency

memory latency逻辑:

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <unistd.h>

#define ONE p = (char **)*p;
#define FIVE ONE ONE ONE ONE ONE
#define TEN FIVE FIVE
#define FIFTY TEN TEN TEN TEN TEN
#define HUNDRED FIFTY FIFTY

static void usage()
{
printf("Usage: ./mem-lat -b xxx -n xxx -s xxx\n");
printf(" -b buffer size in KB\n");
printf(" -n number of read\n\n");
printf(" -s stride skipped before the next access\n\n");
printf("Please don't use non-decimal based number\n");
}


int main(int argc, char* argv[])
{
unsigned long i, j, size, tmp;
unsigned long memsize = 0x800000; /* 1/4 LLC size of skylake, 1/5 of broadwell */
unsigned long count = 1048576; /* memsize / 64 * 8 */
unsigned int stride = 64; /* skipped amount of memory before the next access */
unsigned long sec, usec;
struct timeval tv1, tv2;
struct timezone tz;
unsigned int *indices;

while (argc-- > 0) {
if ((*argv)[0] == '-') { /* look at first char of next */
switch ((*argv)[1]) { /* look at second */
case 'b':
argv++;
argc--;
memsize = atoi(*argv) * 1024;
break;

case 'n':
argv++;
argc--;
count = atoi(*argv);
break;

case 's':
argv++;
argc--;
stride = atoi(*argv);
break;

default:
usage();
exit(1);
break;
}
}
argv++;
}

char* mem = mmap(NULL, memsize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
// trick3: init pointer chasing, per stride=8 byte
size = memsize / stride;
indices = malloc(size * sizeof(int));

for (i = 0; i < size; i++)
indices[i] = i;

// trick 2: fill mem with pointer references
for (i = 0; i < size - 1; i++)
*(char **)&mem[indices[i]*stride]= (char*)&mem[indices[i+1]*stride];
*(char **)&mem[indices[size-1]*stride]= (char*)&mem[indices[0]*stride];

register char **p = (char **) mem;
//char **p = (char **) mem;
tmp = count / 100;

gettimeofday (&tv1, &tz);
for (i = 0; i < tmp; ++i) {
HUNDRED; //trick 1
}
gettimeofday (&tv2, &tz);
char **touch = p;
if (tv2.tv_usec < tv1.tv_usec) {
usec = 1000000 + tv2.tv_usec - tv1.tv_usec;
sec = tv2.tv_sec - tv1.tv_sec - 1;
} else {
usec = tv2.tv_usec - tv1.tv_usec;
sec = tv2.tv_sec - tv1.tv_sec;
}

printf("Buffer size: %ld KB, stride %d, time %d.%06d s, latency %.2f ns\n",
memsize/1024, stride, sec, usec, (sec * 1000000 + usec) * 1000.0 / (tmp *100));
munmap(mem, memsize);
free(indices);
}

分别在intel 8163和arm 鲲鹏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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
$cat run_mem_lat.sh
#!/bin/sh
#set -x

work=./mem-lat
buffer_size=1
node=$1
mem=$2

for i in `seq 1 15`; do
#echo $i
#echo $buffer_size
taskset -ac 1 $work -b $buffer_size -s 64
buffer_size=$(($buffer_size*2))
done

#sh run_mem_lat.sh
Buffer size: 1 KB, stride 64, time 0.001682 s, latency 1.60 ns
Buffer size: 2 KB, stride 64, time 0.001685 s, latency 1.61 ns
Buffer size: 4 KB, stride 64, time 0.001687 s, latency 1.61 ns
Buffer size: 8 KB, stride 64, time 0.001682 s, latency 1.60 ns
Buffer size: 16 KB, stride 64, time 0.001688 s, latency 1.61 ns
Buffer size: 32 KB, stride 64, time 0.001817 s, latency 1.73 ns
Buffer size: 64 KB, stride 64, time 0.005842 s, latency 5.57 ns
Buffer size: 128 KB, stride 64, time 0.005838 s, latency 5.57 ns
Buffer size: 256 KB, stride 64, time 0.005838 s, latency 5.57 ns
Buffer size: 512 KB, stride 64, time 0.005841 s, latency 5.57 ns
Buffer size: 1024 KB, stride 64, time 0.006056 s, latency 5.78 ns
Buffer size: 2048 KB, stride 64, time 0.006175 s, latency 5.89 ns
Buffer size: 4096 KB, stride 64, time 0.006203 s, latency 5.92 ns
Buffer size: 8192 KB, stride 64, time 0.006383 s, latency 6.09 ns
Buffer size: 16384 KB, stride 64, time 0.007345 s, latency 7.01 ns

[root@x86.170 /root]
#lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 96
On-line CPU(s) list: 0-95
Thread(s) per core: 2
Core(s) per socket: 24
Socket(s): 2
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 85
Model name: Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz
Stepping: 4
CPU MHz: 2500.390
CPU max MHz: 3100.0000
CPU min MHz: 1000.0000
BogoMIPS: 4998.87
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 1024K
L3 cache: 33792K
NUMA node0 CPU(s): 0-95

//鲲鹏920
#sh run_mem_lat.sh
Buffer size: 1 KB, stride 64, time 0.001628 s, latency 1.55 ns
Buffer size: 2 KB, stride 64, time 0.001623 s, latency 1.55 ns
Buffer size: 4 KB, stride 64, time 0.001613 s, latency 1.54 ns
Buffer size: 8 KB, stride 64, time 0.001613 s, latency 1.54 ns
Buffer size: 16 KB, stride 64, time 0.001622 s, latency 1.55 ns
Buffer size: 32 KB, stride 64, time 0.001613 s, latency 1.54 ns
Buffer size: 64 KB, stride 64, time 0.001637 s, latency 1.56 ns
Buffer size: 128 KB, stride 64, time 0.003749 s, latency 3.58 ns
Buffer size: 256 KB, stride 64, time 0.003320 s, latency 3.17 ns
Buffer size: 512 KB, stride 64, time 0.003779 s, latency 3.60 ns
Buffer size: 1024 KB, stride 64, time 0.004310 s, latency 4.11 ns
Buffer size: 2048 KB, stride 64, time 0.004655 s, latency 4.44 ns
Buffer size: 4096 KB, stride 64, time 0.005032 s, latency 4.80 ns
Buffer size: 8192 KB, stride 64, time 0.005721 s, latency 5.46 ns
Buffer size: 16384 KB, stride 64, time 0.006470 s, latency 6.17 ns

[root@ARM 15:58 /root]
#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

为什么CACHE比内存快?

首先肯定是距离的原因,另外这两种存储结构的制造工艺不同导致的速度差异也很大,从上面可以看到一块4000刀的CPU有一半的面积是cache,也就是40M CACHE花了2000刀,如果用来买内存条能卖一大堆吧。

接下来说下CACHE(SRAM) 和内存(DRAM)制造的工艺差异

SRAM(Static Random-Access Memory,静态随机存取存储器)的芯片

CPU Cache 用的是一种叫作 SRAM(Static Random-Access Memory,静态随机存取存储器)的芯片。

SRAM 之所以被称为”静态”存储器,是因为只要处在通电状态,里面的数据就可以保持存在。而一旦断电,里面的数据就会丢失了。在 SRAM 里面,一个比特的数据,需要 6~8 个晶体管。所以 SRAM 的存储密度不高。同样的物理空间下,能够存储的数据有限。不过,因为 SRAM 的电路简单,所以访问速度非常快。

L1和L2一般是SRAM, L1的容量通常比L2小,容量大的SRAM访问时间就越长,同样制程和设计的情况下,访问延时与容量的开方大致是成正比的。

另外工作原理不同速度差异也不一样,L1就是讲究快,比如L1是N路组相联,N路阻相联的意思就是N个Cache单元同时读取数据(有点类似RAID0)。

L3用的还是SRAM,但是在考虑换成STT-MRAM,这样容量更大。

DRAM(Dynamic Random Access Memory,动态随机存取存储器)的芯片

为磁芯存储器画上句号的是集成电路随机存储器件。1966年,IBM Thomas J. Watson研究中心的Dr. Robert H. Dennard开发出了单个单元的动态随机存储器DRAM,DRAM每个单元包含一个开关晶体管和一个电容,利用电容中的电荷存储数据。因为电容中的电荷会泄露,需要每个周期都进行刷新重新补充电量,所以称其为动态随机存储器。

内存用的芯片和 Cache 有所不同,它用的是一种叫作 DRAM(Dynamic Random Access Memory,动态随机存取存储器)的芯片,比起 SRAM 来说,它的密度更高,有更大的容量,而且它也比 SRAM 芯片便宜不少。

动态随机存取存储器(DRAM)是一种半导体存储器,主要的作用原理是利用电容内存储电荷的多寡来代表一个二进制比特(bit)是1还是0。由于在现实中晶体管会有漏电电流的现象,导致电容上所存储的电荷数量并不足以正确的判别数据,而导致数据毁损。因此对于DRAM来说,周期性地充电是一个无可避免的要件。由于这种需要定时刷新的特性,因此被称为“动态”存储器。相对来说,静态存储器(SRAM)只要存入数据后,纵使不刷新也不会丢失记忆。

DRAM 的一个比特,只需要一个晶体管和一个电容就能存储。所以,DRAM 在同样的物理空间下,能够存储的数据也就更多,也就是存储的”密度”更大。DRAM 的数据访问电路和刷新电路都比 SRAM 更复杂,所以访问延时也就更长。

img

SRAM是比DRAM更为昂贵,但更为快速、非常低功耗(特别是在空闲状态)。 因此SRAM首选用于带宽要求高,或者功耗要求低,或者二者兼而有之。 SRAM比起DRAM更为容易控制,也更是随机访问。 由于复杂的内部结构,SRAM比DRAM的占用面积更大,因而不适合用于更高储存密度低成本的应用,如PC内存。

SRAM和DRAM原理比较

简单说DRAM只有一个晶体管和一个电容,SRAM就复杂多了,需要6个晶体管

What is the difference between SRAM and DRAM

图左边的 DRAM 的状态是保持在电容器C中。晶体管M用来控制访问。如果要读取状态,拉升访问线AL,这时,可能会有电流流到数据线DL上,也可能没有,取决于电容器是否有电。如果要写入状态,先设置DL,然后升起AL一段时间,直到电容器充电或放电完毕。

由于读取状态时需要对电容器放电,所以这一过程不能无限重复,不得不在某个点上对它重新充电。更糟糕的是,为了容纳大量单元(现在一般在单个芯片上容纳109以上的RAM单元),电容器的容量必须很小(0.000000000000001法拉以下)。这样,完整充电后大约持有几万个电子。即使电容器的电阻很大(若干兆欧姆),仍然只需很短的时间就会耗光电荷,称为「泄漏」。

这种泄露就是现在的大部分DRAM芯片每隔64ms就必须进行一次刷新的原因。在刷新期间,对于该芯片的访问是不可能的,这甚至会造成半数任务的延宕。(相关内容请察看【highperfdram】一章)

这个问题的另一个后果就是无法直接读取芯片单元中的信息,而必须通过信号放大器将0和1两种信号间的电势差增大,才能分辨出来。

DRAM 主要靠电容充放电来识别0和1,但是充放电是一个持续过程,需要耗时,这也是导致内存延时大的主要原因

image-20220730161825538

不像SRAM可以即刻读取数据,当要读取DRAM的时候,必须花一点时间来等待电容的冲放电完全。这一点点的时间最终限制了DRAM的速度。

SRAM 需要注意以下问题:

  • 一个单元需要6个晶体管。也有采用4个晶体管的SRAM,体积大、贵、结构复杂。
  • 维持状态需要恒定的电源。
  • 升起WL后立即可以读取状态。信号与其它晶体管控制的信号一样,是直角的(快速在两个状态间变化)。
  • 状态稳定,不需要刷新循环。

SRAM也有其它形式,不那么费电,但比较慢。由于我们需要的是快速RAM,因此不在关注范围内。这些较慢的SRAM的主要优点在于接口简单,比动态RAM更容易使用。

详细比较:

Difference Between SRAM and DRAM - YouTube

SRAM 也有其它形式,不那么费电,但比较慢。由于我们需要的是快速RAM,因此其它形式的 SRAM 不在关注范围内。这些较慢的SRAM的主要优点在于接口简单,比动态RAM更容易使用。CPU cache用的是快速 SRAM,本文提到的 SRAM 都是指快速 SRAM

DRAM 刷新

DRAM内存内部使用电容来存储数据,由于电容有漏电现象,经过一段时间电荷会泄放,导致数据不能长时间存储。因此需要不断充电,这个充电的动作叫做刷新。自动刷新是以“行”为单位进行刷新,刷新操作与读写访问无法同时进行,即刷新时会对内存的性能造成影响。同时温度越高电容泄放越快,器件手册通常要求芯片表面温度在0℃-85℃时,内存需要按照64ms的周期刷新数据,在85℃~95℃时,按照32ms的周期刷新数据。

BIOS中内存刷新速率选项提供了auto选项,可以根据工作温度自动调节内存刷新速率。相比默认32ms配置可以提升内存性能,同时确保工作温度在85℃~95℃时内存数据的可靠性。

DRAM 频率

内存实际有3种频率:

  • 核心频率
  • 时钟频率(IO控制器频率)
  • 等效频率(有效数据传输频率)

核心频率就是内存的Cell阵列(内存电容)的刷新频率,只与内存本身物理特性有关,目前频率基本都在133MHz~200MH之间

我们俗称DDR4-2666实际指的是等效频率,是通过上升下降沿进行数据预取放大后的实际数据传输频率,DDR4 prefetch是8,通过bank group提升到核心频率的16倍,所以DDR4的最低起频是133.333MHz*16=2133MHz。DDR(Double Data Rate)因为是在一个时钟周期的上升沿和下降沿个执行预取,所以时钟频率=等效频率/2

Persistence memory

左边是在32G物理内存的基础上挂了128G pmem, 然后系统通过free能看到 154G内存,用 lat_mem_rd 实际测试速度可以看到左边的机器抖动比较大

image-20220607154156826

系列文章

CPU的制造和概念

[CPU 性能和Cache Line](/2021/05/16/CPU Cache Line 和性能/)

[Perf IPC以及CPU性能](/2021/05/16/Perf IPC以及CPU利用率/)

Intel、海光、鲲鹏920、飞腾2500 CPU性能对比

飞腾ARM芯片(FT2500)的性能测试

十年后数据库还是不敢拥抱NUMA?

一次海光物理机资源竞争压测的记录

[Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的](/2019/12/16/Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的/)

参考资料

Gallery of Processor Cache Effects

7个示例科普CPU CACHE

与程序员相关的CPU缓存知识

45-year CPU evolution: one law and two equations

揭秘 cache 访问延迟背后的计算机原理

业务与芯片垂直整合的一点思考

What Every Programmer Should Know About Main Memory by Ulrich Drepper 中文版:https://zhuanlan.zhihu.com/p/611133924

做了一道数学几何题

发表于 2021-06-23 | 分类于 技巧

做了一道数学几何题

John Hattie的visible learning,这本书集成了两亿多学生的数据,然后得到了哪些品质能够决定一个人学习好坏,想通过一道几何题目来验证下。先看下John Hattie的结论

哪些品质能够决定一个人学习好坏

排在第一名的品质是复盘、总结能力

简单的说,这个能力就是这个孩子心里是否有个“小教练”,能够每次跳脱出当前任务,帮助自己分析,失败在哪里,成功在哪里,如何进阶,如何训练等等。

举几个例子:

  1. 如果写不出作文,这个“小教练”能告诉孩子,是没有素材,还是文字能力不强。

    如果是文字能力不强,应该如何训练(是造句,还是拆段落)

  2. 如果数学题做不出来,这个“小教练”能告诉孩子,我的弱点在哪里,哪个类型题我有重大问题,是因为哪里没有理解和打通。

有内化的“自我教练”,这个能力系数是1.67。也就是其他能力相当,学习效果可以翻1.67倍。

排在第二名的是建构能力。简单的说是逻辑推理,做事顺序等等

在内心“小教练”能把问题进行拆解之后,建构能力能把这些问题进行排序,应该怎么做更合理。

最终怎么把训练步骤整合。遇见一个问题,先干什么,再干什么等等。

有很强的顺序能力,系数为1.44。也就是其他能力相当,学习效果可以翻1.44倍。

排在第三名的能力是智商和过去成绩

这个毋庸置疑,聪明做事就会简单一些。平均效应系数为0.67。

也就是说,其他能力相当,智商高和过去成绩好,学习效果提升67%

智商重要程度应该比这里更高,但是实际高智商的太少,大多都是因为基础知识好给人产生了智商高的误解!

排在第四名的能力是自我驱动力

简单的说,知道自己为什么学习,能够自我鼓励,遇见失败能抗挫,有很强的心理驱动力。

平均效应系数为0.48。也就是说,其他能力相当,有自我驱动力的人,学习效果提升48%。

排在第五名的才是集中注意力

也就是说,注意力强。注意力对学习影响,并没有很多家长想象的那么大。

注意力的平均效应系数为0.44

也就是说,其他能力相当,注意力好的孩子,效果能提升44%

———总结一下——-

学习提升的个人因素:

自我分析,自我教练的元认知能力 》 逻辑排序与制定计划的建构能力》 智商和过去成绩 》自我驱动力》 集中注意力。

很多家长痴迷于“专注力”。当然专注力是一个效应量很强的学习力,但是从整体数据看,对学习的提升效果,仅仅排到第五名。

帮助孩子建立元认知能力和建构能力的培训,才能给他们对终身学习有帮助的技能包

以上缺少了对方法的落地执行能力的评估,实际这是影响最大的

案例数学几何题

题目如下(图中红色、绿色线是我绘上去的辅助线)

image-20210623121704887

目标分解

求得四个阴影部分的面积相加;

求得白色部分面积即可以得到三角形内部两块阴影部分面积;

求得红色三角形面积记得通过圆面积减得三角形面积得到三角形外部阴影面积。红色辅助线、绿色辅助线

题目给出的条件

大三角形是等腰直角三角形(两个角都是45度,直角90度);

两圆相切:圆心连线经过切点,连线长度为两圆半径相加。大圆半径为2

这里关于两圆相切我完全不记得有啥特性了,所以去Google了一把得到如下两圆相切的特性:

image-20210623130815151

想了一下在我丢开课本几十年后我看到等要直接三角形我能得到:45度、两条边相等这两个结论;但是看到两圆相切我完全想不起来这是什么东西了。相信这两个知识点在我中学的时候肯定无比熟练。

但是两圆相切完全不会出现在我的生活和应用中,但是等要直接三角形太常见了,它反复出现在我的生活中,所以我只要没得老年痴呆应该会一直记得。

解题关键

  1. 如何求得小圆半径;
  2. 如何求红色辅助线构成的右下角三角形的面积(是否是个等腰直角三角形?),求得这个三角形的面积就能算出右下角阴影面积
  3. 从1的小圆半径和2的方法同理可得左上角的外部阴影面积
  4. 从2、3的阴影面积可以求得大三角形内部两块白色区域面积
  5. 这样的到了全部阴影的面积

详细步骤

  1. 求解小圆半径:相切是切入点,两圆圆心连线经过想切点(连线长=大圆半径+小圆半径)(绿线)
  2. 利用等腰直接三角形得到左下角绿色新直角三角形由勾股定理算出小圆半径(绿线)
  3. 大圆交点到圆心作辅助线得到右下角等腰三角形,由一个角是45度,和等腰三角形的特点退出另外一个角也为45度,从而得到右下角红色三角形是等腰直角三角形(红线)
  4. 通过大圆四分之一面积减掉右下角红线等腰三角形面积得到右下阴影部分面积,同理可得右上阴影面积。
  5. 通过半圆面积减掉阴影面积可分别得到两个半圆内部的白色部分面积
  6. 大三角形面积减掉6中的两个白色面积得到两个小阴影面积
  7. 到此分别得到了四个阴影部分面积

大圆半径很容易求得为2,设小圆半径为r,根据左下角绿色直角三角形的勾股定理有:(4-r)(4-4)+2X2=(2+r)(2+r),可得r=4/3

右下角的红色三角形是个等腰三角形,且一个底角为45度,可得这个三角形是等腰直角三角形,求得大圆半径为2

复盘求解过程

教练在哪里?

教练就是日益自我训练的过程,教练在事后复盘上述过程,解题过程中没有教练参与

如果做不出来,那么教练就来问:

是题目没读懂得到的信息不够(仔细多读题,提高阅读能力);

还是由题目中的已知条件得不到相关的直接推理(比如两圆相切因为不了解特性,所以得不到连线就是两个圆半径之和—-这种只能多看书);

或者推不出来右下角的直角等腰三角形(对等腰三角形理解不够)……

如果做得过于绕,那么教练就来问:

还能简化(这个简化不是要奇技淫巧的快解),而是要既解决问题又不啰嗦,同时又是自己掌握知识的恰切运用!

教练就是复盘上述过程,得到方法进步或者知识缺陷或者理解缺陷,而不是得到答案。

逻辑推理,做事顺序

先求什么再求什么,怎么样从已知条件得到简单结论,然后再分步得到阶段性小结论(各个小块面积)到最终目标,这就是John Hattie提到的逻辑推理做事顺序能力

基础知识的运用

勾股定理、等腰三角形、三角形三角之和、相切特性、圆面积、等腰三角形面积计算

没有任何一个复杂的基础知识,也没有任何需要几次的推导,全是定理带来的直接特性,对智力要求极低

自我驱动和集中注意力是个长期过程,自我驱动决定了之前的学习欲望和基本知识点的掌握

其他总结

你有更巧妙的解法?对不起,我不需要,我要的就是这种学渣也完全能掌握,只需要简单地1+1+1+1就得到4的方法,我不需要2*2得到4,因为1+1+1+1对不会乘法的学渣也能掌握,我要的就是这种普适的方法。

学渣也能掌握这个方法,然后用这个方法训练自己解决其他类似题目。对学渣来说考80分就很开心了,等他们有了考80分的能力就有野心向90进发。

学渣更重要的是追求容易的全部解决,复杂的直接战略性放弃,只有在容易的全部掌握后才能慢慢挑战复杂题目。容易的基础题都解决不好只是追求奇技淫巧的解法或者复杂题目容易迷失自己和快速遗忘。

受过训练的中学生如何解这题

做如下BD和HE两条辅助线,梯形BDEH的面积就是要求的面积。

也就是ABC三角形的面积减掉AEH和BCD两个三角形的面积。

那么需要证明 BD=CD,因为DF=CF,角FCD为45度,所以CDF为等腰三角形,接下来证明过程和上面一样。同时同理可证AEH也是等腰直角三角形。

可以看到受过训练的中学生的解题方法更为犀利一些,但是前面文章的方法最为朴素和直接。训练过的方法效率更高,当然两个方法基本知识的运用没有差别。

比如受过训练的方法还是需要下图红色、绿色两条辅助线。这就是职业和业余的差距。

image-20210625175006683

最后如果你发现我题目做错了,也别较真了,我就是借案例说明下学习方法,不代表我现在还有初中数学能力

Intel、海光、鲲鹏920、飞腾2500 CPU性能对比

发表于 2021-06-18 | 分类于 CPU

Intel 海光 鲲鹏920 飞腾2500 CPU性能对比

为了让程序能快点,特意了解了CPU的各种原理,比如多核、超线程、NUMA、睿频、功耗、GPU、大小核再到分支预测、cache_line失效、加锁代价、IPC等各种指标(都有对应的代码和测试数据)都会在这系列文章中得到答案。当然一定会有程序员最关心的分支预测案例、Disruptor无锁案例、cache_line伪共享案例等等。

这次让我们从最底层的沙子开始用8篇文章来回答各种疑问以及大量的实验对比案例和测试数据。

大的方面主要是从这几个疑问来写这些文章:

  • 同样程序为什么CPU跑到800%还不如CPU跑到200%快?
  • IPC背后的原理和和程序效率的关系?
  • 为什么数据库领域都爱把NUMA关了,这对吗?
  • 几个国产芯片的性能到底怎么样?

系列文章

CPU的制造和概念

[Perf IPC以及CPU性能](/2021/05/16/Perf IPC以及CPU利用率/)

CPU性能和CACHE

[CPU 性能和Cache Line](/2021/05/16/CPU Cache Line 和性能/)

十年后数据库还是不敢拥抱NUMA?

[Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的](/2019/12/16/Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的/)

Intel、海光、鲲鹏920、飞腾2500 CPU性能对比

一次海光物理机资源竞争压测的记录

飞腾ARM芯片(FT2500)的性能测试

本篇是收尾篇,横向对比一下x86和ARM芯片,以及不同方案权衡下的性能比较

CPU基本信息

image-20210723161314138

海光

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
#lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 64
On-line CPU(s) list: 0-63
Thread(s) per core: 2 //每个物理core有两个超线程
Core(s) per socket: 16 //每路16个物理core
Socket(s): 2 //2路
NUMA node(s): 4
Vendor ID: HygonGenuine
CPU family: 24
Model: 1
Model name: Hygon C86 5280 16-core Processor
Stepping: 1
CPU MHz: 2455.552
CPU max MHz: 2500.0000
CPU min MHz: 1600.0000
BogoMIPS: 4999.26
Virtualization: AMD-V
L1d cache: 32K
L1i cache: 64K
L2 cache: 512K
L3 cache: 8192K
NUMA node0 CPU(s): 0-7,32-39
NUMA node1 CPU(s): 8-15,40-47
NUMA node2 CPU(s): 16-23,48-55
NUMA node3 CPU(s): 24-31,56-63
Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl nonstop_tsc cpuid extd_apicid amd_dcm aperfmperf pni pclmulqdq monitor ssse3 fma cx16 sse4_1 sse4_2 movbe popcnt xsave avx f16c rdrand lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw skinit wdt tce topoext perfctr_core perfctr_nb bpext perfctr_llc mwaitx cpb hw_pstate sme ssbd sev ibpb vmmcall fsgsbase bmi1 avx2 smep bmi2 MySQLeed adx smap clflushopt sha_ni xsaveopt xsavec xgetbv1 xsaves clzero irperf xsaveerptr arat npt lbrv svm_lock nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold avic v_vmsave_vmload vgif overflow_recov succor smca

#numactl -H
available: 4 nodes (0-3)
node 0 cpus: 0 1 2 3 4 5 6 7 32 33 34 35 36 37 38 39
node 0 size: 128854 MB
node 0 free: 89350 MB
node 1 cpus: 8 9 10 11 12 13 14 15 40 41 42 43 44 45 46 47
node 1 size: 129019 MB
node 1 free: 89326 MB
node 2 cpus: 16 17 18 19 20 21 22 23 48 49 50 51 52 53 54 55
node 2 size: 128965 MB
node 2 free: 86542 MB
node 3 cpus: 24 25 26 27 28 29 30 31 56 57 58 59 60 61 62 63
node 3 size: 129020 MB
node 3 free: 98227 MB
node distances:
node 0 1 2 3
0: 10 16 28 22
1: 16 10 22 28
2: 28 22 10 16
3: 22 28 16 10

这CPU据说是胶水核,也就是把两个die拼一块封装成一块CPU,所以一块CPU内跨die之间延迟还是很高的。

64 个 core 的分配策略

1
2
3
4
5
physical         core      processor
0 0~15 0~15
1 0~15 16~31
0 0~15 32~47
1 0~15 48~63

image-20210805085715353

Intel CPU

cascade lake naming scheme.svg

Cascade Lake架构相对Broadwell L1没变,L2从256K增加到1M增加了4倍,L3从2.5下降到1.38M每core

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
#lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 104
On-line CPU(s) list: 0-103
Thread(s) per core: 2
Core(s) per socket: 26
座: 2
NUMA 节点: 1
厂商 ID: GenuineIntel
CPU 系列: 6
型号: 85
型号名称: Intel(R) Xeon(R) Platinum 8269CY CPU @ 2.50GHz
步进: 7
CPU MHz: 1200.000
CPU max MHz: 2501.0000
CPU min MHz: 1200.0000
BogoMIPS: 5000.00
虚拟化: VT-x
L1d 缓存: 32K
L1i 缓存: 32K
L2 缓存: 1024K
L3 缓存: 36608K
NUMA 节点0 CPU: 0-103
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 art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg 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 epb cat_l3 cdp_l3 intel_ppin intel_pt ssbd mba ibrs ibpb stibp ibrs_enhanced tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm cqm mpx rdt_a avx512f avx512dq rdseed adx smap clflushopt clwb avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local dtherm ida arat pln pts pku ospke avx512_vnni spec_ctrl intel_stibp flush_l1d arch_capabilities

# numactl -H
available: 1 nodes (0)
node 0 cpus: 0 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 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
node 0 size: 785826 MB
node 0 free: 108373 MB
node distances:
node 0
0: 10

//志强E5
#lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 64
On-line CPU(s) list: 0-63
Thread(s) per core: 2
Core(s) per socket: 16
Socket(s): 2
NUMA node(s): 2
Vendor ID: GenuineIntel
CPU family: 6
Model: 79
Model name: Intel(R) Xeon(R) CPU E5-2682 v4 @ 2.50GHz
Stepping: 1
CPU MHz: 2500.000
CPU max MHz: 3000.0000
CPU min MHz: 1200.0000
BogoMIPS: 5000.06
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 256K
L3 cache: 40960K
NUMA node0 CPU(s): 0-15,32-47
NUMA node1 CPU(s): 16-31,48-63
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 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 rdt rdseed adx smap xsaveopt cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local cat_l3

#numactl -H
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
node 0 size: 262008 MB
node 0 free: 240846 MB
node 1 cpus: 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
node 1 size: 262144 MB
node 1 free: 242774 MB
node distances:
node 0 1
0: 10 21
1: 21 10

鲲鹏920

鲲鹏920-4826的L1比8269C 大一倍,但是L2小一倍。L3鲲鹏为1M/core 8269为1.38M/core(物理core)

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
#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): 1
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: 49152K
NUMA node0 CPU(s): 0-95
Flags: fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma dcpop asimddp asimdfhm

#numactl -H
available: 4 nodes (0-3)
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
node 0 size: 192832 MB
node 0 free: 187693 MB
node 1 cpus: 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
node 1 size: 193533 MB
node 1 free: 191827 MB
node 2 cpus: 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
node 2 size: 193533 MB
node 2 free: 192422 MB
node 3 cpus: 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
node 3 size: 193532 MB
node 3 free: 193139 MB
node distances:
node 0 1 2 3
0: 10 12 20 22
1: 12 10 22 24
2: 20 22 10 12
3: 22 24 12 10

#dmidecode -t processor | grep Version
Version: Kunpeng 920-4826
Version: Kunpeng 920-4826


以上四个鲲鹏920的四个NUMA node之间的距离描述如下:
node 0 <------------ socket distance ------------> node 2
| (die distance) | (die distance)
node 1 node 3
要注意node1到node3比node0到node3要大,猜测Socket之间的UPI只接上了node1和node2

鲲鹏920架构参考这里

img

Though Huawei has been keeping a tight lip on the chip design itself, the Hi1620 is actually a multi-chip design. Actually, we believe are three dies. The chip itself comprise two compute dies called the Super CPU cluster (SCCL), each one packing 32 cores. It’s also possible the SCCL only have 24 cores, in which case there are three such dies with a theoretical maximum core count of 72 cores possible but are not offered for yield reasons. Regardless of this, there are at least two SCCL dies for sure. Additionally, there is also an I/O die called the Super IO Cluster (SICL) which contains all the high-speed SerDes and low-speed I/Os.

下图是6426型号,我测试用的是4826型号,也就是一个CPU内是48core,一个CPU封装3个Die,两个Die是 core,还有一个是Super IO Cluster

taishan v110 soc details.svg

鲲鹏命令规范:

img

鲲鹏 RoadMap

img

鲲鹏 Kunpeng 920-4826 跨numa性能比较

绑24core,跨numa0、numa3,是numactl -H看到的比较远距离。两分钟的 Current tpmC: 69660

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
#taskset -a -cp  12-23,72-83 20799

#perf stat -e branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,stalled-cycles-backend,stalled-cycles-frontend,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,cpu-migrations -p 20799
^C
Performance counter stats for process id '20799':

2,866,418,154 branch-misses (59.84%)
549,673,215,827 bus-cycles (59.89%)
2,179,816,578 cache-misses # 2.360 % of all cache refs (59.93%)
92,377,674,343 cache-references (60.04%)
549,605,057,475 cpu-cycles (65.05%)
229,958,980,614 instructions # 0.42 insn per cycle
# 1.31 stalled cycles per insn (65.05%)
146,201,062,116 stalled-cycles-backend # 26.60% backend cycles idle (65.08%)
301,814,831,043 stalled-cycles-frontend # 54.91% frontend cycles idle (65.08%)
2,177,062,319 L1-dcache-load-misses # 2.35% of all L1-dcache hits (65.04%)
92,481,797,426 L1-dcache-loads (65.11%)
2,175,030,428 L1-dcache-store-misses (65.15%)
92,507,474,710 L1-dcache-stores (65.14%)
9,299,812,249 L1-icache-load-misses # 12.47% of all L1-icache hits (65.20%)
74,579,909,037 L1-icache-loads (65.16%)
2,862,664,443 branch-load-misses (65.08%)
52,826,930,842 branch-loads (65.04%)
3,729,265,130 dTLB-load-misses # 3.11% of all dTLB cache hits (64.95%)
119,896,014,498 dTLB-loads (59.90%)
1,350,782,047 iTLB-load-misses # 1.83% of all iTLB cache hits (59.84%)
74,005,620,378 iTLB-loads (59.82%)
510 cpu-migrations

9.483137760 seconds time elapsed

绑72-95core,在同一个numa下,但是没有重启进程,导致有一半内存仍然在numa0上,2分钟的Current tpmC: 75900

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
#taskset -a -cp  72-95 20799

#perf stat -e branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,stalled-cycles-backend,stalled-cycles-frontend,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,cpu-migrations -p 20799
^C
Performance counter stats for process id '20799':

2,665,583,722 branch-misses (59.90%)
500,184,789,050 bus-cycles (59.95%)
1,997,726,097 cache-misses # 2.254 % of all cache refs (59.94%)
88,628,013,529 cache-references (59.93%)
500,111,712,450 cpu-cycles (64.98%)
221,098,464,920 instructions # 0.44 insn per cycle
# 1.35 stalled cycles per insn (65.02%)
105,957,124,479 stalled-cycles-backend # 21.19% backend cycles idle (65.02%)
298,186,439,955 stalled-cycles-frontend # 59.62% frontend cycles idle (65.02%)
1,996,313,908 L1-dcache-load-misses # 2.25% of all L1-dcache hits (65.04%)
88,701,699,646 L1-dcache-loads (65.09%)
1,997,851,364 L1-dcache-store-misses (65.10%)
88,614,658,960 L1-dcache-stores (65.10%)
8,635,807,737 L1-icache-load-misses # 12.30% of all L1-icache hits (65.13%)
70,233,323,630 L1-icache-loads (65.16%)
2,665,567,783 branch-load-misses (65.10%)
50,482,936,168 branch-loads (65.09%)
3,614,564,473 dTLB-load-misses # 3.15% of all dTLB cache hits (65.04%)
114,619,822,486 dTLB-loads (59.96%)
1,270,926,362 iTLB-load-misses # 1.81% of all iTLB cache hits (59.97%)
70,248,645,721 iTLB-loads (59.94%)
128 cpu-migrations

8.610934700 seconds time elapsed

#/root/numa-maps-summary.pl </proc/20799/numa_maps
N0 : 8220658 ( 31.36 GB)
N1 : 38620 ( 0.15 GB)
N2 : 480619 ( 1.83 GB)
N3 : 8281759 ( 31.59 GB)
active : 28797 ( 0.11 GB)
anon : 17015902 ( 64.91 GB)
dirty : 16990615 ( 64.81 GB)
kernelpagesize_kB: 9076 ( 0.03 GB)
mapmax : 760 ( 0.00 GB)
mapped : 5754 ( 0.02 GB)

重启进程后继续绑72-95core,在同一个numa下,先进成充分热身,然后2分钟的 Current tpmC: 77880

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
#perf stat -e branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,stalled-cycles-backend,stalled-cycles-frontend,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,cpu-migrations -p 49512
^C
Performance counter stats for process id '49512':

1,849,313,199 branch-misses (59.99%)
319,122,053,367 bus-cycles (60.02%)
1,319,212,546 cache-misses # 2.238 % of all cache refs (59.95%)
58,950,581,370 cache-references (60.02%)
319,088,767,311 cpu-cycles (65.01%)
146,580,891,374 instructions # 0.46 insn per cycle
# 1.32 stalled cycles per insn (65.01%)
61,109,919,226 stalled-cycles-backend # 19.15% backend cycles idle (65.04%)
193,963,590,196 stalled-cycles-frontend # 60.79% frontend cycles idle (65.06%)
1,319,593,051 L1-dcache-load-misses # 2.24% of all L1-dcache hits (65.03%)
58,967,303,454 L1-dcache-loads (65.04%)
1,318,842,690 L1-dcache-store-misses (65.13%)
58,988,059,583 L1-dcache-stores (65.07%)
5,769,871,870 L1-icache-load-misses # 12.25% of all L1-icache hits (65.12%)
47,085,299,316 L1-icache-loads (65.10%)
1,850,419,802 branch-load-misses (65.03%)
33,687,548,636 branch-loads (65.08%)
2,375,028,039 dTLB-load-misses # 3.12% of all dTLB cache hits (65.08%)
76,113,084,244 dTLB-loads (60.01%)
825,388,210 iTLB-load-misses # 1.75% of all iTLB cache hits (59.99%)
47,092,738,092 iTLB-loads (59.95%)
49 cpu-migrations

#/root/numa-maps-summary.pl </proc/49512/numa_maps
N0 : 5765 ( 0.02 GB)
N1 : 41599 ( 0.16 GB)
N2 : 566 ( 0.00 GB)
N3 : 16955491 ( 64.68 GB)
active : 30430 ( 0.12 GB)
anon : 16997663 ( 64.84 GB)
dirty : 16989252 ( 64.81 GB)
kernelpagesize_kB: 9020 ( 0.03 GB)
mapmax : 745 ( 0.00 GB)
mapped : 5758 ( 0.02 GB)

IPC从0.42到0.44再到0.46,tpmC也不断增加,整体压力都不大只压了25%的CPU,所以跨NUMA大概有10%的性能差异. IPC也是0.42 VS 0.46 。测试场景是DRDS Server服务。

如果跨4core绑定core的话最好和最差绑法性能会下降25-30%,四个core绑不同numa的性能比较

被压进程绑定的core id tpmC
72,73,74,75 14460
48,49,72,73 13800
24,25,72,73 11760
0,1,72,73 11940
0,24,48,72 10800

飞腾2500

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
86
87
88
89
#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

node distances:
node 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
0: 10 20 40 30 20 30 50 40 100 100 100 100 100 100 100 100
1: 20 10 30 40 50 20 40 50 100 100 100 100 100 100 100 100
2: 40 30 10 20 40 50 20 30 100 100 100 100 100 100 100 100
3: 30 40 20 10 30 20 40 50 100 100 100 100 100 100 100 100
4: 20 50 40 30 10 50 30 20 100 100 100 100 100 100 100 100
5: 30 20 50 20 50 10 50 40 100 100 100 100 100 100 100 100
6: 50 40 20 40 30 50 10 30 100 100 100 100 100 100 100 100
7: 40 50 30 50 20 40 30 10 100 100 100 100 100 100 100 100
8: 100 100 100 100 100 100 100 100 10 20 40 30 20 30 50 40
9: 100 100 100 100 100 100 100 100 20 10 30 40 50 20 40 50
10: 100 100 100 100 100 100 100 100 40 30 10 20 40 50 20 30
11: 100 100 100 100 100 100 100 100 30 40 20 10 30 20 40 50
12: 100 100 100 100 100 100 100 100 20 50 40 30 10 50 30 20
13: 100 100 100 100 100 100 100 100 30 20 50 20 50 10 50 40
14: 100 100 100 100 100 100 100 100 50 40 20 40 30 50 10 30
15: 100 100 100 100 100 100 100 100 40 50 30 50 20 40 30 10

#dmidecode -t processor
# dmidecode 3.0
Getting SMBIOS data from sysfs.
SMBIOS 3.2.0 present.
# SMBIOS implementations newer than version 3.0 are not
# fully supported by this version of dmidecode.

Handle 0x0004, DMI type 4, 48 bytes
Processor Information
Socket Designation: BGA3576
Type: Central Processor
Family: <OUT OF SPEC>
Manufacturer: PHYTIUM
ID: 00 00 00 00 70 1F 66 22
Version: FT2500
Voltage: 0.8 V
External Clock: 50 MHz
Max Speed: 2100 MHz
Current Speed: 2100 MHz
Status: Populated, Enabled
Upgrade: Other
L1 Cache Handle: 0x0005
L2 Cache Handle: 0x0007
L3 Cache Handle: 0x0008
Serial Number: 1234567
Asset Tag: No Asset Tag
Part Number: NULL
Core Count: 64
Core Enabled: 64
Thread Count: 64
Characteristics:
64-bit capable
Multi-Core
Hardware Thread
Execute Protection
Enhanced Virtualization
Power/Performance Control

申威3231

申威系列微处理器的开发主要是被中华人民共和国用于军事方面[来源请求]。根据部分公开信息表明,此系列的微体系架构基于DEC Alpha派生而来。[1][2]而SW-3/SW1600处理器则是基于Alpha 21164。[3]

不过申威系列最新的SW26010处理器,目前没有详细的信息表明它是基于DEC Alpha微架构的派生品。[4][5]不过处理器的处理器核心结构布局,则是类似于基于POWER指令集架构的Cell微架构。

申威 3231处理器是基于第三代“申威 64” 二次优化版核心(C3B)的国产高性能多核处理器。3231的内核与1621属于同一代,采用新一代工艺,最高主频2.5Ghz,32核心,3231基本上可以视为1621换工艺后的32核版本,主要面向高性能计算和高端服务器应用。

申威 3231采用“申威64”自主指令系统;

基于第三代“申威 64”二次优化版核心(C3B)的32核64位通用处理器;

采用CC-NUMA多核结构和SoC技术,片内包含8路DDR4存储控制器接口以及40lane的PCI-E 4.0标准I/O接口;

集成3路直连接口,可构建2路或4路服务器系统;

计算性能:双精度浮点性能可达1280GFlops,整数性能可达880Gops;

访存性能:最大传输率为3200Mbps,最大总存储器容量2TB;

I/O性能:双向聚合有效带宽可达到160GB/s,支持I/O虚拟化。

img

3232推出的时间会比3231迟一些,采用新一代CPU核,IPC会非常惊人,保底10/G,争取12/G,考虑倒申威团队一向严谨,以及过去基本没有让大家失望过,因而对3232的IPC,可以采用就高原则。

申威 3231架构

image.png

申威 6B 芯片结构的主要特点如下:

  • 全芯片集成 32 个物理核心,每个物理核心支持 1 个线程,软件可见 32 个逻辑核心;

  • 每个物理核心集成 32KB L1 指令 Cache(ICache)、32KB L1 数据 Cache(DCache)和 512KB 的 L2 Cache(SCache),核心内的所有 Cache 为核心私有 Cache;

  • 全芯片集成 64MB 的 L3 Cache(TCache),本芯片内所有核心分布共享,TCache 由16 个体组成,每个体跟2 个物理核心及其对应的管理部件(LCPM)一起组成一个核组,连接在环网节点上,核心访问不同 TCache 体中的副本延迟略有不同;

  • 存储器接口:全芯片集成 8 个 DDR4 存储器通道,每个通道数据宽度为 72bit(含 8 位 ECC),支持 UDIMM、RDIMM 和 LRDIMM,单通道内存容量最大支持 256GB 容量,单通道带宽可达 25.6GB/s(DDR4-3200);每4 个存储器通道对应一个主存代理部件(GCPM),所有核心和 IO 设备都可访问;

  • PCIe 接口:全芯片集成 40 Lane 的 PCIe 4.0 链路,支持 x4、x8 和 x16 灵活配置,最大支持 6 个 RC;

  • 直连接口:全芯片集成 3 路直连接口,可构建 2 路或 4 路服务器系统,每路直连接口为9 个lane的serdes 接口,接口速率为28Gbps;

  • 维护调试测试接口:维护控制部件实现芯片配置、初始引导以及提供各种维护和调试支持。维护控制部件支持芯片的上电初始化、配置加载、存储器读写或 IO 读写、维护中断以及内部状态的扫描观测等。支持外部维护通过 Jtag 接口进行初始引导;支持通过 SPI Master 接口从 SPI Flash中进行自举引导;

  • 集成三套 I2C 接口、一套 Uart、GPIO 和 LPC 低速接口。

申威1621处理器是基于第三代“申威64”核心(增强版)的国产高性能多核处理器,主要面向高性能计算和中高端服务器应用。目前,该处理器已经实现量产。

img 申威1621采用对称多核结构和SoC技术,单芯片集成了16个64位RISC结构的申威处理器核心,目标设计主频为2GHz。芯片还集成八路DDR3存储控制器和双路PCI-E3.0标准I/O接口。

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
86
87
88
89
90
91
92
#dmidecode -t processor
# dmidecode 3.0
Getting SMBIOS data from sysfs.
SMBIOS 3.2.0 present.
# SMBIOS implementations newer than version 3.0 are not
# fully supported by this version of dmidecode.

Handle 0x0022, DMI type 4, 48 bytes
Processor Information
Socket Designation: CPU 0
Type: Central Processor
Family: Other
Manufacturer: SW3231
ID: 28 00 C8 80 01 00 00 00
Version: Product
Voltage: 3.3 V
External Clock: 200 MHz
Max Speed: 2400 MHz
Current Speed: 2400 MHz
Status: Unpopulated
Upgrade: Other
L1 Cache Handle: 0x2000
L2 Cache Handle: 0x2002
L3 Cache Handle: 0x2003
Serial Number: .......
Asset Tag: Asset Tag#To Be Filled By O.E.M.
Part Number: Part Number#To Be Filled By O.E.M.
Core Count: 32
Core Enabled: 32
Thread Count: 0
Characteristics:
64-bit capable

Handle 0x0023, DMI type 4, 48 bytes
Processor Information
Socket Designation: CPU 1
Type: Central Processor
Family: Other
Manufacturer: SW3231
ID: 28 00 C8 80 01 00 00 00
Version: Product
Voltage: 3.3 V
External Clock: 200 MHz
Max Speed: 2400 MHz
Current Speed: 2400 MHz
Status: Unpopulated
Upgrade: Other
L1 Cache Handle: 0x2000
L2 Cache Handle: 0x2002
L3 Cache Handle: 0x2003
Serial Number: .......
Asset Tag: Asset Tag#To Be Filled By O.E.M.
Part Number: Part Number#To Be Filled By O.E.M.
Core Count: 32
Core Enabled: 32
Thread Count: 0
Characteristics:
64-bit capable


[root@d22b04001.cloud.b04.amtest11 /root] 193E_OPS1
#numactl -H
available: 2 nodes (0-1)
node 0 cpus: 0 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
node 0 size: 259482 MB
node 0 free: 121171 MB
node 1 cpus: 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
node 1 size: 260091 MB
node 1 free: 88564 MB
node distances:
node 0 1
0: 10 20
1: 20 10

#lscpu
Architecture: sw_64
CPU op-mode(s): 64-bit
Byte Order: Little Endian
CPU(s): 64
On-line CPU(s) list: 0-63
Thread(s) per core: 1
Core(s) per socket: 32
Socket(s): 2
NUMA node(s): 2
Vendor ID: sw
CPU family: 6
Model: 6
Model name: sw
CPU MHz: 2400.00
BogoMIPS: 4800.00
NUMA node0 CPU(s): 0-31
NUMA node1 CPU(s): 32-63

openssl speed aes-256-ige性能比较

测试脚本

openssl speed aes-256-ige -multi 1

单核能力

Intel (52物理core) aes-256 ige 89602.86k 97498.37k 98271.49k 98399.91k 89101.65k
海光(32物理core) aes-256 ige 76919.66k 77935.81k 79201.88k 79529.30k 79555.24k
鲲鹏920(96物理core) aes-256 ige 133174.89k 140578.99k 142156.46k 142663.34k 143196.16k

测试32个线程并行

Intel (52物理core) aes-256 ige 2642742.25k 2690638.98k 2703860.74k 2734114.82k 2680422.40
海光(32物理core) aes-256 ige 2464568.75k 2499381.80k 2528665.34k 2544845.14k 2550723.93k
鲲鹏920(96物理core) aes-256 ige 4261589.92k 4501245.55k 4552731.56k 4570456.75k 4584330.58k

将所有核跑满包括HT

Intel (52物理core) aes-256 ige 4869950.82k 5179884.71k 5135412.14k 5211367.08k 5247858.60k
海光(32物理core) aes-256 ige 2730195.74k 2836759.53k 2865252.35k 2857900.71k 2884302.17k
鲲鹏920(96物理core) aes-256 ige 12788358.79k 13502288.53k 13657385.98k 13710908.76k 13751432.53k

单核计算 7^999999” 的性能对比

测试命令:bash -c ‘echo “7^999999” | bc > /dev/null’

执行时间(秒) IPC 主频
海光 26.729972414 0.92 2.5G
鲲鹏920 24.604603640 1.84 2.6G
飞腾2500 39.654819568 0.43 2.1G
Intel 18.603323495 2.19 2.5G
710 15.832394912 2.64 2.75G

当然也可以通过计算pi值来测试

bash -c ‘ echo “scale=5000; 4*a(1)” | bc -l -q >/dev/null ‘

执行时间(秒) 主频
海光 31.061s 2.5G
鲲鹏920 23.521s 2.6G
飞腾2500 2.1G
Intel 22.979s(8163) 2.5G
710 15.570s 2.75G

多核一起跑的话可以这样:

for i in {0..95}; do time echo “scale=5000; 4*a(1)” | bc -l -q >/dev/null & done

perf stat -e branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,stalled-cycles-backend,stalled-cycles-frontend,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 –

710

耗时15.83秒,ipc 2.64

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 -- bash -c 'echo "7^999999" | bc > /dev/null'

Performance counter stats for 'bash -c echo "7^999999" | bc > /dev/null':

985,496,277 branch-misses (29.97%)
43,509,183,948 bus-cycles # 2748.210 M/sec (29.97%)
7,068,868 cache-misses # 0.020 % of all cache refs (29.96%)
35,165,185,942 cache-references # 2221.170 M/sec (29.97%)
43,508,579,063 cpu-cycles # 2.748 GHz (34.97%)
114,779,081,188 instructions # 2.64 insn per cycle
# 0.04 stalled cycles per insn (34.99%)
4,913,750,141 stalled-cycles-backend # 11.29% backend cycles idle (35.02%)
4,255,139,235 stalled-cycles-frontend # 9.78% frontend cycles idle (35.02%)
0 alignment-faults # 0.000 K/sec
0 bpf-output # 0.000 K/sec
24 context-switches # 0.002 K/sec
15,831.82 msec cpu-clock # 1.000 CPUs utilized

intel

耗时18.60秒,ipc 2.19

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
# sudo perf stat -e branch-instructions,branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,ref-cycles,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 -- bash -c 'echo "7^999999" | bc > /dev/null'

Performance counter stats for 'bash -c echo "7^999999" | bc > /dev/null':

25,130,886,211 branch-instructions (10.72%)
1,200,086,175 branch-misses # 4.78% of all branches (14.29%)
460,824,074 bus-cycles (14.29%)
1,983,459 cache-misses # 46.066 % of all cache refs (14.30%)
4,305,730 cache-references (14.30%)
58,626,314,801 cpu-cycles (17.87%)
128,284,870,917 instructions # 2.19 insn per cycle (21.45%)
46,040,656,499 ref-cycles (25.02%)
22,821,794 L1-dcache-load-misses # 0.10% of all L1-dcache hits (25.02%)
23,041,732,649 L1-dcache-loads (25.01%)
5,386,243,625 L1-dcache-stores (25.00%)
12,443,154 L1-icache-load-misses (25.00%)
178,790 LLC-load-misses # 30.52% of all LL-cache hits (14.28%)
585,724 LLC-loads (14.28%)
469,381 LLC-store-misses (7.14%)
664,865 LLC-stores (7.14%)
1,201,547,113 branch-load-misses (10.71%)
25,139,625,428 branch-loads (14.28%)
63,334 dTLB-load-misses # 0.00% of all dTLB cache hits (14.28%)
23,023,969,089 dTLB-loads (14.28%)
17,355 dTLB-store-misses (14.28%)
5,378,496,562 dTLB-stores (14.28%)
341,119 iTLB-load-misses # 119.92% of all iTLB cache hits (14.28%)
284,445 iTLB-loads (14.28%)
151,608 node-load-misses (14.28%)
37,553 node-loads (14.29%)
434,537 node-store-misses (7.14%)
65,709 node-stores (7.14%)

18.603323495 seconds time elapsed

18.525904000 seconds user
0.015197000 seconds sys

鲲鹏920

耗时24.6秒, IPC 1.84

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
#perf stat -e branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,stalled-cycles-backend,stalled-cycles-frontend,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 -- bash -c 'echo "7^999999" | bc > /dev/null'

Performance counter stats for 'bash -c echo "7^999999" | bc > /dev/null':

1,467,769,425 branch-misses (59.94%)
63,866,536,853 bus-cycles (59.94%)
6,571,273 cache-misses # 0.021 % of all cache refs (59.94%)
30,768,754,927 cache-references (59.96%)
63,865,354,560 cpu-cycles (64.97%)
117,790,453,518 instructions # 1.84 insns per cycle
# 0.07 stalled cycles per insn (64.98%)
833,090,930 stalled-cycles-backend # 1.30% backend cycles idle (65.00%)
7,918,227,782 stalled-cycles-frontend # 12.40% frontend cycles idle (65.01%)
6,962,902 L1-dcache-load-misses # 0.02% of all L1-dcache hits (65.03%)
30,804,266,645 L1-dcache-loads (65.05%)
6,960,157 L1-dcache-store-misses (65.06%)
30,807,954,068 L1-dcache-stores (65.06%)
1,012,171 L1-icache-load-misses (65.06%)
45,256,066,296 L1-icache-loads (65.04%)
1,470,467,198 branch-load-misses (65.03%)
27,108,794,972 branch-loads (65.01%)
475,707 dTLB-load-misses # 0.00% of all dTLB cache hits (65.00%)
35,159,826,836 dTLB-loads (59.97%)
912 iTLB-load-misses # 0.00% of all iTLB cache hits (59.96%)
45,325,885,822 iTLB-loads (59.94%)

24.604603640 seconds time elapsed

海光

耗时 26.73秒, IPC 0.92

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
sudo perf stat -e branch-instructions,branch-misses,cache-references,cpu-cycles,instructions,stalled-cycles-backend,stalled-cycles-frontend,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 -a -- bash -c 'echo "7^999999" | bc > /dev/null'

Performance counter stats for 'system wide':

57,795,675,025 branch-instructions (27.78%)
2,459,509,459 branch-misses # 4.26% of all branches (27.78%)
12,171,133,272 cache-references (27.79%)
317,353,262,523 cpu-cycles (27.79%)
293,162,940,548 instructions # 0.92 insn per cycle
# 0.19 stalled cycles per insn (27.79%)
55,152,807,029 stalled-cycles-backend # 17.38% backend cycles idle (27.79%)
44,410,732,991 stalled-cycles-frontend # 13.99% frontend cycles idle (27.79%)
4,065,273,083 L1-dcache-load-misses # 3.58% of all L1-dcache hits (27.79%)
113,699,208,151 L1-dcache-loads (27.79%)
1,351,513,191 L1-dcache-prefetches (27.79%)
2,091,035,340 L1-icache-load-misses # 4.43% of all L1-icache hits (27.79%)
47,240,289,316 L1-icache-loads (27.79%)
2,459,838,728 branch-load-misses (27.79%)
57,855,156,991 branch-loads (27.78%)
69,731,473 dTLB-load-misses # 20.40% of all dTLB cache hits (27.78%)
341,773,319 dTLB-loads (27.78%)
26,351,132 iTLB-load-misses # 15.91% of all iTLB cache hits (27.78%)
165,656,863 iTLB-loads (27.78%)

26.729972414 seconds time elapsed

飞腾

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
time perf stat -e branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,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 -a -- bash -c 'echo "7^999999" | bc > /dev/null'

Performance counter stats for 'system wide':

2552812813 branch-misses (38.08%)
602038279874 bus-cycles (37.54%)
1742826523 cache-misses # 2.017 % of all cache refs (37.54%)
86400294181 cache-references (37.55%)
612467194375 cpu-cycles (43.79%)
263691445872 instructions # 0.43 insns per cycle (43.79%)
1706247569 L1-dcache-load-misses # 2.00% of all L1-dcache hits (43.78%)
85122454139 L1-dcache-loads (43.77%)
1711243358 L1-dcache-store-misses (39.38%)
86288158984 L1-dcache-stores (37.52%)
2006641212 L1-icache-load-misses (37.51%)
146380907111 L1-icache-loads (37.51%)
2560208048 branch-load-misses (37.52%)
63127187342 branch-loads (41.38%)
768494735 dTLB-load-misses (43.77%)
124424415 iTLB-load-misses (43.77%)

39.654819568 seconds time elapsed

real 0m39.763s
user 0m39.635s
sys 0m0.127s

perf 数据对比

Intel

intel的cpu随着线程的增加,ipc稳定减少,但不是线性的

image.png

image.png

image.png

image.png

海光

如下数据可以看到在用满32个物理core之前,ipc保持稳定,超过32core后随着并发增加ipc相应减少,性能再也上不去了。

image.png

image.png

image.png

image.png

image.png

鲲鹏920

可以看到鲲鹏920多核跑openssl是没有什么争抢的,所以还能保证完全线性

image.png

image.png

小结

intel的流水线适合跑高带宽应用,不适合跑密集计算应用,也就是intel的pipeline数量少,但是内存读写上面优化好,乱序优化好。跑纯计算,不是intel的强项。

数据库场景下鲲鹏920大概相当于X86的70%的能力

prime计算一般走的fpu,不走cpu

intel x86 cpu bound和memory bond数据

测试代码

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
#include <stdlib.h>
#include <emmintrin.h>
#include <stdio.h>
#include <signal.h>

char a = 1;

void memory_bound() {
register unsigned i=0;
register char b;

for (i=0;i<(1u<<24);i++) {
// evict cacheline containing a
_mm_clflush(&a);
b = a;
}
}
void cpu_bound() {
register unsigned i=0;
for (i=0;i<(1u<<31);i++) {
__asm__ ("nop\nnop\nnop");
}
}
int main() {
int i=0;
for(i=0;i<10; ++i){
//cpu_bound();
memory_bound();
}
return 0;
}

测试结果

cpu_bound部分飞腾只有intel性能的30%

如下测试perf数据可以看到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
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
# sudo perf stat -e branch-instructions,branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,ref-cycles,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 -a ./memory_bound

Performance counter stats for 'system wide':

36,162,872,212 branch-instructions (14.21%)
586,644,153 branch-misses # 1.62% of all branches (12.95%)
4,632,787,085 bus-cycles (14.40%)
476,189,785 cache-misses # 17.714 % of all cache refs (14.38%)
2,688,284,129 cache-references (14.35%)
258,946,713,506 cpu-cycles (17.93%)
181,069,328,200 instructions # 0.70 insn per cycle (21.51%)
456,889,428,341 ref-cycles (22.31%)
3,928,434,098 L1-dcache-load-misses # 7.46% of all L1-dcache hits (14.21%)
52,656,559,902 L1-dcache-loads (14.31%)
26,711,751,387 L1-dcache-stores (14.30%)
2,618,739,340 L1-icache-load-misses (18.05%)
154,326,888 LLC-load-misses # 8.60% of all LL-cache hits (19.84%)
1,795,112,198 LLC-loads (9.81%)
66,802,375 LLC-store-misses (10.19%)
206,810,811 LLC-stores (11.16%)
586,120,789 branch-load-misses (14.28%)
36,121,237,395 branch-loads (14.29%)
114,927,298 dTLB-load-misses # 0.22% of all dTLB cache hits (14.29%)
52,902,163,128 dTLB-loads (14.29%)
7,010,297 dTLB-store-misses (14.29%)
26,587,353,417 dTLB-stores (18.00%)
106,209,281 iTLB-load-misses # 174.17% of all iTLB cache hits (19.33%)
60,978,626 iTLB-loads (21.53%)
117,197,042 node-load-misses (19.71%)
35,764,508 node-loads (11.65%)
57,655,994 node-store-misses (7.80%)
11,563,328 node-stores (9.45%)

16.700731355 seconds time elapsed

# sudo perf stat -e branch-instructions,branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,ref-cycles,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 -a ./cpu_bound

Performance counter stats for 'system wide':

43,013,055,562 branch-instructions (14.33%)
436,722,063 branch-misses # 1.02% of all branches (11.58%)
3,154,327,457 bus-cycles (14.31%)
306,977,772 cache-misses # 17.837 % of all cache refs (14.42%)
1,721,062,233 cache-references (14.39%)
176,119,834,487 cpu-cycles (17.98%)
276,038,539,571 instructions # 1.57 insn per cycle (21.55%)
309,334,354,268 ref-cycles (22.31%)
2,551,915,790 L1-dcache-load-misses # 6.78% of all L1-dcache hits (13.12%)
37,638,319,334 L1-dcache-loads (14.32%)
19,132,537,445 L1-dcache-stores (15.73%)
1,834,976,400 L1-icache-load-misses (18.90%)
131,307,343 LLC-load-misses # 11.46% of all LL-cache hits (19.94%)
1,145,964,874 LLC-loads (16.60%)
45,561,247 LLC-store-misses (8.11%)
140,236,535 LLC-stores (9.60%)
423,294,349 branch-load-misses (14.27%)
46,645,623,485 branch-loads (14.28%)
73,377,533 dTLB-load-misses # 0.19% of all dTLB cache hits (14.28%)
37,905,428,246 dTLB-loads (15.69%)
4,969,973 dTLB-store-misses (17.21%)
18,729,947,580 dTLB-stores (19.71%)
72,073,313 iTLB-load-misses # 167.86% of all iTLB cache hits (20.60%)
42,935,532 iTLB-loads (19.16%)
112,306,453 node-load-misses (15.35%)
37,239,267 node-loads (7.44%)
37,455,335 node-store-misses (10.00%)
8,134,155 node-stores (8.87%)

10.838808208 seconds time elapsed

飞腾

ipc 大概是intel的30%,加上主频也要差一些,

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
#time perf stat -e branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,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 -a ./cpu_bound

Performance counter stats for 'system wide':

10496356859 branch-misses (37.60%)
2813170983911 bus-cycles (37.58%)
17604745519 cache-misses # 3.638 % of all cache refs (37.55%)
483878256161 cache-references (37.54%)
2818545529083 cpu-cycles (43.78%)
1280497827941 instructions # 0.45 insns per cycle (43.78%)
17623592806 L1-dcache-load-misses # 3.65% of all L1-dcache hits (43.78%)
482429613337 L1-dcache-loads (41.83%)
17604561232 L1-dcache-store-misses (37.53%)
484126081882 L1-dcache-stores (37.52%)
17774514325 L1-icache-load-misses (37.50%)
641046300400 L1-icache-loads (37.50%)
10574973722 branch-load-misses (39.45%)
273851009656 branch-loads (43.76%)
9457594390 dTLB-load-misses (43.77%)
1813954093 iTLB-load-misses (43.77%)

31.172754504 seconds time elapsed

real 0m31.284s
user 0m31.096s
sys 0m0.165s

unixBench 5.1.3 性能对比

测试命令: ./Run -c 1 -c 4

芯片 架构 逻辑核数 单核能力 4核能力 单核比值 4核比值 整机对比
Intel 4114 x86 40 1150 3095 100% 100% 100%
海光 7165 x86 48 1586 2533 138% 82% 98%
华为鲲鹏920 arm 96 1168 2066 102% 67% 160%
飞腾2000 arm 64 731 1902 64% 61% 98%
申威1621 alpha 16 445 1065 39% 34% 14%

以上CPU除了Intel,其它都没有HT,也就是Intel 4114实际是20个物理核。以上数据来自ata,仅供参考

ARM 和 X86的总结

对比硬件:

ARM:泰山ARM 双路 128核心64核心/路),2.5G,4指令/周期,8个内存通道/路,mips体系架构。
X86: intel 8163服务器 双路 48核心(24核心/路),2.5GHZ, 6指令/周期,96smt, 6个内存通道

用 Geabase(C++) 测试所得 ARM是X86 性能的1.36倍,接近理论值的1.4倍

理论值的计算公式:

CPU性能验证公式:频率 x 核数 x 发射数/周期 x 1.3/1.5(smt2/smt4) (smt是指超线程数量)

ARM 优势的来源主要是工艺领先一代(7nm VS 14nm)

总结

  • 对纯CPU 运算场景,并发不超过物理core时,比如Prime运算,比如DRDS(CPU bound,IO在网络,可以加并发弥补)
    • 海光的IPC能保持稳定;
    • intel的IPC有所下降,但是QPS在IPC下降后还能完美线性
  • 在openssl和MySQL oltp_read_only场景下
    • 如果并发没超过物理core数时,海光和Intel都能随着并发的翻倍性能能增加80%
    • 如果并发超过物理core数后,Intel还能随着并发的翻倍性能增加50%,海光增加就只有20%了
    • 简单理解在这两个场景下Intel的HT能发挥半个物理core的作用,海光的HT就只能发挥0.2个物理core的作用了
  • 海光zen1的AMD 架构,每个core只有一个fpu,综上在多个场景下HT基本上都可以忽略
  • 飞腾2500性能比较差
  • 国产CPU:飞腾、鲲鹏、龙芯、申威、海光(AMD授权)、兆芯(威盛via 授权x86)
  • CPU性能验证公式:频率 x 核数 x 发射数/周期 x 1.3/1.5(smt2/smt4) (smt是指超线程数量)
  • 大吞吐量计算由多核CPU数量决定,多核CPU数量由制程工艺决定,制程工艺由资本决定,制程工艺资本由主流消费电子决定, 摩尔定律仍在持续

系列文章

CPU的制造和概念

[CPU 性能和Cache Line](/2021/05/16/CPU Cache Line 和性能/)

[Perf IPC以及CPU性能](/2021/05/16/Perf IPC以及CPU利用率/)

Intel、海光、鲲鹏920、飞腾2500 CPU性能对比

飞腾ARM芯片(FT2500)的性能测试

十年后数据库还是不敢拥抱NUMA?

一次海光物理机资源竞争压测的记录

[Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的](/2019/12/16/Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的/)

参考资料

Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的

华为TaiShan服务器ARMNginx应用调优案例 大量绑核、中断、Numa等相关调优信息

CPU的制造和概念

发表于 2021-06-01 | 分类于 CPU

CPU的制造和概念

为了让程序能快点,特意了解了CPU的各种原理,比如多核、超线程、NUMA、睿频、功耗、GPU、大小核再到分支预测、cache_line失效、加锁代价、IPC等各种指标(都有对应的代码和测试数据)都会在这系列文章中得到答案。当然一定会有程序员最关心的分支预测案例、Disruptor无锁案例、cache_line伪共享案例等等。

这次让我们从最底层的沙子开始用8篇文章来回答各种疑问以及大量的实验对比案例和测试数据。

大的方面主要是从这几个疑问来写这些文章:

  • 同样程序为什么CPU跑到800%还不如CPU跑到200%快?
  • IPC背后的原理和和程序效率的关系?
  • 为什么数据库领域都爱把NUMA关了,这对吗?
  • 几个国产芯片的性能到底怎么样?

系列文章

CPU的制造和概念

[Perf IPC以及CPU性能](/2021/05/16/Perf IPC以及CPU利用率/)

CPU性能和CACHE

[CPU 性能和Cache Line](/2021/05/16/CPU Cache Line 和性能/)

十年后数据库还是不敢拥抱NUMA?

[Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的](/2019/12/16/Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的/)

AMD Zen CPU 架构 以及 AMD、海光、Intel、鲲鹏的性能对比

Intel、海光、鲲鹏920、飞腾2500 CPU性能对比

一次海光物理机资源竞争压测的记录

飞腾ARM芯片(FT2500)的性能测试

image-20210802161410524

几个重要概念

为了增加对文章的理解先解释下几个高频概念

Wafer:晶圆,一片大的纯硅圆盘,新闻里常说的12寸、30寸晶圆厂说的就是它,光刻机在晶圆上蚀刻出电路

Die:从晶圆上切割下来的CPU(通常一个Die中包含多个core、L3cache、内存接口、GPU等,core里面又包含了L1、L2cache),Die的大小可以自由决定,得考虑成本和性能, Die做成方形便于切割和测试,服务器所用的Intel CPU的Die大小一般是大拇指指甲大小。

封装:将一个或多个Die封装成一个物理上可以售卖的CPU

路:就是socket、也就是封装后的物理CPU

node:同一个Die下的多个core以及他们对应的内存,对应着NUMA

售卖的CPU实物

购买到的CPU实体外观和大小,一般是40mm X 50mm大小,可以看出一个CPU比一个Die大多了。

How to Perform a CPU Stress Test and Push It to the Limit | AVG

Coffee Lake-Refresh Desktop CPU List Surfaces: 35W Core i9-9900T & 8-Core  Xeon E-2200 Confirmed

enter image description here

裸片Die 制作

晶圆为什么总是圆的呢?生产过程就是从沙子中提纯硅,硅晶柱生长得到晶圆,生长是以圆柱形式的,所以切割下来的晶圆就是圆的了:

img

硅晶柱切片:

img

直径为 300 毫米的纯硅晶圆(从硅柱上切割下来的圆片),俗称 12 寸晶圆,大约是 400 美金。但尺寸并不是衡量硅晶圆的最重要指标,纯度才是。日本的信越公司可以生产 13 个 9 纯度的晶圆。

高纯硅的传统霸主依然是德国Wacker和美国Hemlock(美日合资),中国任重而道远。太阳能级高纯硅要求是99.9999%,低纯度的硅全世界超过一半是中国产的,但是不值钱。而芯片用的电子级高纯硅要求99.999999999%,几乎全赖进口,直到2018年江苏鑫华公司才实现量产,目前年产0.5万吨,而中国一年进口15万吨。核心材料技术这块毫无疑问“外国仍然把中国摁在地上摩擦”。

芯片设计

主要依赖EDA, EDA工具是电子设计自动化(Electronic Design Automation)的简称,从计算机辅助设计(CAD)、计算机辅助制造(CAM)、计算机辅助测试(CAT)和计算机辅助工程(CAE)的概念发展而来的,是IC基础设计能力。利用EDA工具,工程师将芯片的电路设计、性能分析、设计出IC版图的整个过程交由计算机自动处理完成。

EDA软件方面早已形成了三巨头——Synopsys、Cadence、Mentor。Synopsys是EDA三巨头之首,国内从事EDA软件开发的华大九天和这三家比起来不是一个数量级。国内IC设计公司几乎100%采用国外EDA工具,在未来的相当长的一段时间里,我们应该看不到缩小和Synopsys、Cadence、Mentor技术差距的可能性。

光刻

使用特定波长的光,透过光罩(类似印炒里面的母版),照射在涂有光刻胶的晶圆上,光罩上芯片的设计图像,就复制到晶圆上了,这就是光刻,这一步是由光刻机完成的,光刻机是芯片制造中光刻环节的核心设备。你可以把光刻理解为,就是用光罩这个母版,一次次在晶圆上印电路的过程。

img

光刻是最贵的一个环节,一方面是光罩越来越多,越来越贵,另一方面光刻机也很贵。光刻机是半导体制造设备中价格占比最大,也是最核心的设备。2020 年荷兰公司 ASML 的极紫外光源(EUV)光刻机每台的平均售价是 1.45 亿欧元,而且全世界独家供货,年产量 31 台,有钱也未必能买得到。

image-20210601160424815

短波长光源是提高光刻机分辨力的有效方,光刻机的发展历史,就从紫外光源(UV)、深紫外光源(DUV),发展到了现在的极紫外光源(EUV)。

回顾光刻机的发展历史,从 1960 年代的接触式光刻机、接近式光刻机,到 1970 年代的投影式光刻机,1980 年代的步进式光刻机,到步进式扫描光刻机、浸入式光刻机和现在的深紫外光源(DUV)和极紫外光源(EUV)光刻机,一边是设备性能的不断提高,另一边是价格逐年上升,且供应商逐渐减少。到了 EUV 光刻机,ASML(阿斯麦) 就是独家供货了。英特尔有阿斯麦15%的股份,台积电有5%,三星有3%,另外美国弄了一个《瓦森纳协定》,敏感技术不能卖,中国、朝鲜、伊朗、利比亚均是被限制国家。

品质合格的die切割下去后,原来的晶圆成了下图的样子,是挑剩下的Downgrade Flash Wafer。残余的die是品质不合格的晶圆。黑色的部分是合格的die,会被原厂封装制作为成品NAND颗粒,而不合格的部分,也就是图中留下的部分则当做废品处理掉。

从晶圆上切割检测合格的Die(螺片),所以Die跟Wafer不一样不是圆的,而是是方形的,因为方形的在切割封测工艺上最简单

img

一个大晶圆,拿走了合格的Die后剩下的次品:

img

可见次品率不低,后面会谈到怎么降低次品率,次品率决定了CPU的价格。

台积电一片 5nm 晶圆的加工费高达 12500 美金。根据台积电的财报推算,台积电平均每片晶圆可以产生近 4000 美金(300mm 晶圆)的利润。无论是哪个数字,对比 400 美金的纯硅晶圆原料来说,这都是一个至少增值 10 倍的高价值的加工过程。

随着Die的缩小,浪费的比例也从36%缩小成为12.6%。根据极限知识,我们知道如果Die的大小足够小,我们理论上可以100%用上所有的Wafer大小。从中我们可以看出越小的Die,浪费越小,从而降低CPU价格,对CPU生产者和消费者都是好事。

光刻机有一个加工的最大尺寸,一般是 858mm²,而 Cerebras 和台积电紧密合作,做了一个 46255mm²,1.2T 个晶体管的世界第一大芯片。这也是超摩尔定律的一个突破。

AMD在工艺落后Intel的前提下,又想要堆核,只能采取一个Package封装4个独立Die的做法,推出了Zen1 EPYC服务器芯片,即不影响良率,又可以核心数目好看,可谓一举两得。

可惜连接四个Die的片外总线终归没有片内通信效率高,在好些benchmark中败下阵来,可见没有免费的午餐。

img

Intel的Pakcage内部是一个Die, Core之间原来是Ring Bus,在Skylake后改为Mesh。AMD多Die封装的目的是省钱和增加灵活性!AMD每个Zeppelin Die都比Intel的小,这对良品率提高很大,节约了生产费用。

这种胶水核强行将多个die拼一起是没考虑跨die之间的延迟,基本上跨die跟intel跨socket(numa)时延一样了。

一颗芯片的 1/3 的成本,是花在封测阶段的

img

一个晶体管(纳米尺度),注意三个黄色的导电铜点

image-20230621183939476

对应的一个逻辑意义上的NPMOS 晶体管:

image-20230621184113927

MOS :金属-氧化物-半导体,而拥有这种结构的晶体管我们称之为MOS晶体管。 MOS晶体管有P型MOS管和N型MOS管之分。 由MOS管构成的集成电路称为MOS集成电路,由NMOS组成的电路就是NMOS集成电路,由PMOS管组成的电路就是PMOS集成电路,由NMOS和PMOS两种管子组成的互补MOS电路,即CMOS电路

Die和core

One die with multiple cores,下图是一个Die内部图:

enter image description here

或者Skylake:

skylake sp mesh core tile zoom with client shown.png

将两个Die封装成一块CPU(core多,成本低):

data f1

第4代酷睿(Haswell)的die:

image-20210601162558479

第4代酷睿(Haswell)的die主要分为几个部分:GPU、4个core、System Agent(uncore,类似北桥)、cache和内存控制器和其他小部件。比如我们发现core 3和4有问题,我们可以直接关闭3和4。坏的关掉就是i5, 都是好的就当i7来卖。

北桥和南桥

早期CPU core和内存硬盘的连接方式(FSB 是瓶颈):

image-20210602113401202

个人PC主板实物图:

img

由于FSB变成了系统性能的瓶颈和对多CPU的制约,在台式机和笔记本电脑中,MCH(Memory Control Hub)被请进CPU中,服务器市场虽然短暂的出现了IOH。

Image

集成北桥后的内存实物图:

image-20210602114931825

北桥已经集成到CPU中,南桥还没有,主要是因为:集成后Die增大不少,生产良品率下降成本上升;不集成两者采用不同的工艺;另外就是CPU引脚不够了!

Image

SoC(System on Chip):南桥北桥都集成在CPU中,单芯片解决方案。ATOM就是SoC

现代CPU的基本架构

下图是一个两路的服务器结构,每路4个内存channel

image-20220711110145506

一个Core的典型结构

Intel skylake 架构图

skylake server block diagram.svg

iTLB:instruct TLB

dTLB:data TLB

多个core加上L3等组成一个Die:

img

多核和多个CPU

如果要实现一台48core的计算能力的服务器,可以有如下三个方案

方案1:一个大Die集成48core:Intel Skylake SP Mesh Architecture Conceptual Diagram

方案2:一个CPU封装8个Die,也叫MCM(Multi-Chip-Module),每个Die 6个core

image-20210602165525641

四个Die之间的连接方法:

image-20210602172555232

上图最下面的方案为Intel采用的EMIB(Embedded Multi-die Interconnect Bridge)方案,cost 最低。中间的方案是使用“硅中介层”(Interposer,AMD采用的方案)。这意味着你能在两枚主要芯片的下面放置和使用第三枚芯片。这枚芯片的目的是使得多个设备的连接更加容易,但是也带来了更高的成本。

方案3:四个物理CPU(多Socket),每个物理CPU(Package)里面一个Die,每个Die12个core:

image-20210602171352551

三者的比较:

性能肯定是大Die最好,但是良品率低、成本高;

方案2的多个Die节省了主板上的大量布线和VR成本,总成本略低,但是方案3更容易堆出更多的core和内存

image-20210602170727459

面积和性能

我们使用了当时Intel 用在数据中心计算的大核CPU IvyBridge与当时用于 存储系列的小核CPU Avoton(ATOM), 分别测试阿里巴巴的workload,得到性能吞吐如下:

Intel 大小CPU 核心 阿里 Workload Output(QPS)
Avoton(8 cores) 2.4GHZ 10K on single core
Ivy Bridge(2650 v2 disable HT) 2.6GHZ 20K on single core
Ivy Bridge(2650 v2 enable HT) 2.4GHZ 25K on single core
Ivy Bridge(2650 v2 enable HT) 2.6GHZ 27K on single core
  1. 大小核心直观比较:超线程等于将一个大核CPU 分拆成两个小核,Ivy Bridge的数据显示超线程给 Ivy Bridge 1.35倍(27K/20K) 的提升
  2. 性能与芯片面积方面比较:现在我们分别评判 两种CPU对应的性能密度 (performance/core die size) ,该数据越大越好,根据我们的计算和测量发现 Avoton(包含L1D, L1I, and L2 per core)大约是 34平方毫米,Ivy Bridge (包含L1D, L1I, L2 )大约是1213平方毫米, L3/core是 6~7平方毫米, 所以 Ivy Bridge 单核心的芯片面积需要18 ~ 20平方毫米。基于上面的数据我们得到的 Avoton core的性能密度为 2.5 (10K/4sqmm),而Ivy Bridge的性能密度是1.35 (27K/20sqmm),因此相同的芯片面积下 Avoton 的性能是 Ivy Bridge的 1.85倍(2.5/1.35).
  3. 性能与功耗方面比较: 从功耗的角度看性能的提升的对比数据,E5-2650v2(Ivy Bridge) 8core TDP 90w, Avoton 8 core TDP 20瓦, 性能/功耗 Avoton 是 10K QPS/20瓦, Ivy Bridge是 27KQPS/90瓦, 因此 相同的功耗下 Avoton是 Ivy Bridge的 1.75倍(10K QPS/20)/ (27KQPS/95)
  4. 性能与价格方面比较: 从价格方面再进行比较,E5-2650v2(Ivy Bridge) 8core 官方价格是1107美元, Avoton 8 core官方价格是171美元性能/价格 Avoton是 10KQPS/171美元,Ivy Bridge 是 27KQPS/1107美元, 因此相同的美元 Avoton的性能是 Ivy Bridge 的2.3倍(1 10KQPS/171美元)/ (27KQPS/1107美元)

总结:在数据中心的场景下,由于指令数据相关性较高,同时由于内存访问的延迟更多,复杂的CPU体系结构并不能获得相应性能提升,该原因导致我们需要的是更多的小核CPU,以达到高吞吐量的能力,因此2014 年我们向Intel提出数据中心的CPU倾向“小核”CPU,需要将现有的大核CPU的超线程由 2个升级到4个/8个, 或者直接将用更多的小核CPU增加服务器的吞吐能力,经过了近8年,最新数据表明Intel 会在每个大核CPU中引入4个超线程,和在相同的芯片面积下单socket CPU 引入200多个小核CPU,该方案与我们的建议再次吻合

为什么这20年主频基本没有提升了

今天的2.5G CPU性能和20年前的2.5G比起来性能差别大吗?

因为能耗导致CPU的主频近些年基本不怎么提升了,不是技术上不能提升,是性价比不高.

在提升主频之外可以提升性能的有:提升跳转预测率,增加Decoded Cache,增加每周期的并发读个数,增加执行通道,增加ROB, RS,Read & Write buffer等等,这些主要是为了增加IPC,当然增加core数量也是提升整体性能的王道。另外就是优化指令所需要的时钟周期、增加并行度更好的指令等等指令集相关的优化。

img

the industry came up with many different solution to create better computers w/o (or almost without) increasing the clock speed.

比较两代CPU性能变化

Intel 最新的CPU Ice Lake(8380)和其上一代(8280)的性能对比数据:

img

img

上图最终结果导致了IPC提升了20%

But tock Intel did with the Ice Lake processors and their Sunny Cove cores, and the tock, at 20 percent instructions per clock (IPC) improvement on integer work

img

ICE Lake在网络转发上的延时更小、更稳定了:

img

两代CPU整体性能差异:

img

指令集优化

新增等效于某种常见指令组合的指令。原来多个指令执行需要多个时钟周期,合并后的单条指令可以在一个时钟周期执行完成。例如FMA指令,就是一条指令计算A×B+C,而无需分两个时钟周期计算。这种指令一般来说现有程序直接就能用上,无需优化。限制在于只对特定代码有效,还是以FMA为例,更普遍的普通加法、乘法运算都不能从中获益。

案例, ssse3(Supplemental Streaming SIMD Extensions 3 ) 是simd的一种,在libc-2.17.so中就有使用到,如下是mysqld进程中采集到的

1
2
3
4
5
2.79%  mysqld                [.] MYSQLparse                                   
2.27% libc-2.17.so [.] __memcpy_ssse3_back //ssse3
2.19% mysqld [.] ha_insert_for_fold_func
1.95% mysqld [.] rec_get_offsets_func
1.35% mysqld [.] malloc

AVX(Advanced Vector Extension,高级矢量扩展指令集)

英特尔在1996年率先引入了MMX(Multi Media eXtensions)多媒体扩展指令集,也开创了SIMD(Single Instruction Multiple Data,单指令多数据)指令集之先河,即在一个周期内一个指令可以完成多个数据操作,MMX指令集的出现让当时的MMX Pentium处理器大出风头。

SSE(Streaming SIMD Extensions,流式单指令多数据扩展)指令集是1999年英特尔在Pentium III处理器中率先推出的,并将矢量处理能力从64位扩展到了128位。

AVX 所代表的单指令多数据(Single Instruction Multi Data,SIMD)指令集,是近年来 CPU 提升 IPC(每时钟周期指令数)上为数不多的重要革新。随着每次数据宽度的提升,CPU 的性能都会大幅提升,但同时晶体管数量和能耗也会有相应的提升。因此在对功耗有较高要求的场景,如笔记本电脑或服务器中,CPU 运行 AVX 应用时需要降低频率从而降低功耗。

2013 年, 英特尔 发布了AVX-512 指令集,其指令宽度扩展为512bit,每个时钟周期内可打包32 次双精度或64 次单精度浮点运算,因此在图像/ 音视频处理、数据分析、科学计算、数据加密和压缩和 深度学习 等应用场景中,会带来更强大的性能表现,理论上浮点性能翻倍,整数计算则增加约33% 的性能。

Linus Torvalds :

AVX512 有很明显的缺点。我宁愿看到那些晶体管被用于其他更相关的事情。即使同样是用于进行浮点数学运算(通过 GPU 来做,而不是通过 AVX512 在 CPU 上),或者直接给我更多的核心(有着更多单线程性能,而且没有 AVX512 这样的垃圾),就像 AMD 所做的一样。

我希望通过常规的整数代码来达到自己能力的极限,而不是通过 AVX512 这样的功率病毒来达到最高频率(因为人们最终还是会拿它来做 memory-to-memory copy),还占据了核心的很大面积。

关于性能提升的小结

所以今天的2.6G单核skylake,能秒掉20年前2.6G的酷睿, 尤其是复杂场景。

image-20210715094527563

image-20210715094637227

CPU能耗公式:

P = C V*V * f

C是常数,f就是频率,V 电压。 f频率加大后因为充放电带来的Gate Delay,也就是频率增加,充放电时间短,为了保证信号的完整性就一定要增加电压来加快充放电。

所以最终能耗和f频率是 f^3 的指数关系。

The successive nodes of CMOS technologies lead to x1.4 decrease of the gate delays. It led to a 25% increase per year of clock frequencies from 740 kHz (Intel 4004) to 3 GHz (Intel Xeons with 45-nm nodes).

每一代光刻工艺的改进可以降低1.4倍的门延迟

即使不考虑散热问题,Core也没法做到无限大,目前光刻机都有最大加工尺寸限制。光刻机加工的最大尺寸,一般是 858mm²,而 Cerebras 和台积电紧密合作,做了一个 46255mm²,1.2T 个晶体管的世界第一大芯片。这也是超摩尔定律的一个突破。

image-20210715100609552

主频和外频

主频=外频×倍频系数

不只是CPU需要一个切换频率,像GPU、cache、内存都需要一个外频来指导他们的电压脉冲的切换频率。CPU的发展比其它设备快,所以没法统一一个,于是就各自在外频的基础上X倍频系数。

超频:认为加大CPU的倍频系数,切换变快以后最大的问题是电容在短时间内充电不完整,这样导致信号失真,所以一般配套需要增加电压(充电更快),带来的后果是温度更高。

睿频:大多时候多核用不上,如果能智能地关掉无用的核同时把这些关掉的核的电源累加到在用的核上(通过增加倍频来实现),这样单核拥有更高的主频。也就是把其它核的电源指标和发热指标给了这一个核来使用。

img

多core通讯和NUMA

uma下cpu访问内存

早期core不多统一走北桥总线访问内存,对所有core时延统一

x86 UMA

NUMA

如下图,左右两边的是内存条,每个NUMA的cpu访问直接插在自己CPU上的内存必然很快,如果访问插在其它NUMA上的内存条还要走QPI,所以要慢很多。

undefined

如上架构是4路CPU,每路之间通过QPI相连,每个CPU内部8core用的是双Ring Bus相连,Memory Control Hub集成到了Die里面。一路CPU能连4个SMB,每个SMB有两个channel,每个channel最多接三个内存条(图中只画了2个)。

快速通道互联[1][2](英语:Intel QuickPath Interconnect,缩写:QPI)[3][4],是一种由英特尔开发并使用的点对点处理器互联架构,用来实现CPU之间的互联。英特尔在2008年开始用QPI取代以往用于至强、安腾处理器的前端总线(FSB),用来实现芯片之间的直接互联,而不是再通过FSB连接到北桥。Intel于2017年发布的SkyLake-SP Xeon中,用UPI(UltraPath Interconnect)取代QPI。

Ring Bus

2012年英特尔发布了业界期待已久的Intel Sandy Bridge架构至强E5-2600系列处理器。该系列处理器采用 Intel Sandy Bridge微架构和32nm工艺,与前一代的至强5600系列相比,具有更多的内核、更大的缓存、更多的内存通道,Die内采用的是Ring Bus。

Ring Bus设计简单,双环设计可以保证任何两个ring stop之间距离不超过Ring Stop总数的一半,延迟控制在60ns,带宽100G以上,但是core越多,ring bus越长性能下降迅速,在12core之后性能下降明显。

于是采用如下两个Ring Bus并列,然后再通过双向总线把两个Ring Bus连起来。

在至强HCC(High Core Count, 核很多版)版本中,又加入了一个ring bus。两个ring bus各接12个Core,将延迟控制在可控的范围内。俩个Ring Bus直接用两个双向Pipe Line连接,保证通讯顺畅。与此同时由于Ring 0中的模块访问Ring 1中的模块延迟明显高于本Ring,亲缘度不同,所以两个Ring分属于不同的NUMA(Non-Uniform Memory Access Architecture)node。这点在BIOS设计中要特别注意。

Intel Xeon E5-2600 V4 High Core Count Die

或者这个更清晰点的图:

03-05-Broadwell_HCC_Architecture

Mesh网络

Intel在Skylake和Knight Landing中引入了新的片内总线:Mesh。它是一种2D的Mesh网络:

Intel Skylake SP Mesh Architecture Conceptual Diagram

undefined

一个skylake 28core die的实现:

Skylake SP 28 Core Die Mesh

Mesh网络引入片内总线是一个巨大的进步,它有很多优点:

  1. 首先当然是灵活性。新的模块或者节点在Mesh中增加十分方便,它带来的延迟不是像ring bus一样线性增加,而是非线性的。从而可以容纳更多的内核。
  2. 设计弹性很好,不需要1.5 ring和2ring的委曲求全。
  3. 双向mesh网络减小了两个node之间的延迟。过去两个node之间通讯,最坏要绕过半个ring。而mesh整体node之间距离大大缩减。
  4. 外部延迟大大缩短

RAM延迟大大缩短:

Broadwell Ring V Skylake Mesh DRAM Example

上图左边的是ring bus,从一个ring里面访问另一个ring里面的内存控制器。最坏情况下是那条绿线,拐了一个大圈才到达内存控制器,需要310个cycle。而在Mesh网络中则路径缩短很多。

Mesh网络带来了这么多好处,那么缺点有没有呢?网格化设计带来复杂性的增加,从而对Die的大小带来了负面影响

CPU的总线为铜薄膜,虽然摩尔定律使单位面积晶体管的密度不断增加,但是对于连接导线的电阻却没有明显的下降,导线的RC延迟几乎决定现有CPU性能,因此数据传输在CPU的角度来看是个极为沉重的负担。 虽然2D-mesh为数据提供了更多的迁移路径减少了数据堵塞,但也同样为数据一致性带来更多问题,例如过去ring-bus 结构下对于存在于某个CPU私用缓存的数据争抢请求只有两个方向(左和右), 但是在2D-mesh环境下会来自于4个方向(上,下,左,右)

image-20210602104851803

SUB_NUMA Cluster(SNC)

在intel 8269的CPU中,core比较多,core之间通信采取的是mesh架构,实际在BIOS中的NUMA NODE设置上,还有个sub_numa的设置,开启后,一个Die拆成了两个node

image-20220418101937564

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
[root@registry Linux]# lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 104
On-line CPU(s) list: 0-103
Thread(s) per core: 2
Core(s) per socket: 26
座: 2
NUMA 节点: 4
厂商 ID: GenuineIntel
CPU 系列: 6
型号: 85
型号名称: Intel(R) Xeon(R) Platinum 8269CY CPU @ 2.50GHz
步进: 7
CPU MHz: 1200.000
CPU max MHz: 2501.0000
CPU min MHz: 1200.0000
BogoMIPS: 5000.00
虚拟化: VT-x
L1d 缓存: 32K
L1i 缓存: 32K
L2 缓存: 1024K
L3 缓存: 36608K
NUMA 节点0 CPU: 0-3,7-9,13-15,20-22,52-55,59-61,65-67,72-74
NUMA 节点1 CPU: 4-6,10-12,16-19,23-25,56-58,62-64,68-71,75-77
NUMA 节点2 CPU: 26-29,33-35,39-41,46-48,78-81,85-87,91-93,98-100
NUMA 节点3 CPU: 30-32,36-38,42-45,49-51,82-84,88-90,94-97,101-103

不过在8269上开启sub_numa对性能的影响不是特别大,mlc测试如下:

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
[root@registry Linux]# ./mlc
Intel(R) Memory Latency Checker - v3.9
Measuring idle latencies (in ns)...
Numa node
Numa node 0 1 2 3
0 77.3 81.6 129.8 136.1
1 82.1 78.1 134.1 137.6
2 129.8 135.8 73.5 81.7
3 134.4 137.7 81.7 78.5

Measuring Peak Injection Memory Bandwidths for the system
Bandwidths are in MB/sec (1 MB/sec = 1,000,000 Bytes/sec)
Using all the threads from each core if Hyper-threading is enabled
Using traffic with the following read-write ratios
ALL Reads : 232777.7
3:1 Reads-Writes : 216680.7
2:1 Reads-Writes : 213856.4
1:1 Reads-Writes : 197430.7
Stream-triad like: 194310.3

Measuring Memory Bandwidths between nodes within system
Bandwidths are in MB/sec (1 MB/sec = 1,000,000 Bytes/sec)
Using all the threads from each core if Hyper-threading is enabled
Using Read-only traffic type
Numa node
Numa node 0 1 2 3
0 58908.9 59066.0 50548.0 50479.6
1 59111.3 58882.6 50539.0 50479.3
2 50541.7 50495.8 58950.2 58934.0
3 50526.3 50492.4 59171.9 58701.5

Measuring Loaded Latencies for the system
Using all the threads from each core if Hyper-threading is enabled
Using Read-only traffic type
Inject Latency Bandwidth
Delay (ns) MB/sec
==========================
00000 242.78 232249.0
00002 242.90 232248.8
00008 242.63 232226.0
00015 247.47 233159.0
00050 250.26 233489.7
00100 245.88 233253.4
00200 109.72 183071.9
00300 93.95 128676.2
00400 88.51 98678.4
00500 85.15 80026.2
00700 83.74 58136.1
01000 82.16 41372.4
01300 81.59 32184.0
01700 81.14 24896.1
02500 80.80 17248.5
03500 80.32 12571.3
05000 79.58 9060.5
09000 78.27 5411.6
20000 76.09 2911.5

Measuring cache-to-cache transfer latency (in ns)...
Local Socket L2->L2 HIT latency 45.0
Local Socket L2->L2 HITM latency 45.1
Remote Socket L2->L2 HITM latency (data address homed in writer socket)
Reader Numa Node
Writer Numa Node 0 1 2 3
0 - 48.2 107.2 109.2
1 50.6 - 111.2 113.1
2 107.6 109.6 - 48.0
3 111.6 113.5 49.7 -
Remote Socket L2->L2 HITM latency (data address homed in reader socket)
Reader Numa Node
Writer Numa Node 0 1 2 3
0 - 48.6 169.1 175.0
1 46.3 - 167.9 172.1
2 171.4 175.3 - 48.6
3 169.7 173.6 45.1 -

[root@registry Linux]# numactl -H
available: 4 nodes (0-3)
node 0 cpus: 0 1 2 3 7 8 9 13 14 15 20 21 22 52 53 54 55 59 60 61 65 66 67 72 73 74
node 0 size: 64162 MB
node 0 free: 60072 MB
node 1 cpus: 4 5 6 10 11 12 16 17 18 19 23 24 25 56 57 58 62 63 64 68 69 70 71 75 76 77
node 1 size: 65536 MB
node 1 free: 63575 MB
node 2 cpus: 26 27 28 29 33 34 35 39 40 41 46 47 48 78 79 80 81 85 86 87 91 92 93 98 99 100
node 2 size: 65536 MB
node 2 free: 63834 MB
node 3 cpus: 30 31 32 36 37 38 42 43 44 45 49 50 51 82 83 84 88 89 90 94 95 96 97 101 102 103
node 3 size: 65536 MB
node 3 free: 63867 MB
node distances:
node 0 1 2 3
0: 10 11 21 21
1: 11 10 21 21
2: 21 21 10 11
3: 21 21 11 10
[root@registry Linux]# lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 104
On-line CPU(s) list: 0-103
Thread(s) per core: 2
Core(s) per socket: 26
座: 2
NUMA 节点: 4
厂商 ID: GenuineIntel
CPU 系列: 6
型号: 85
型号名称: Intel(R) Xeon(R) Platinum 8269CY CPU @ 2.50GHz
步进: 7
CPU MHz: 1200.000
CPU max MHz: 2501.0000
CPU min MHz: 1200.0000
BogoMIPS: 5000.00
虚拟化: VT-x
L1d 缓存: 32K
L1i 缓存: 32K
L2 缓存: 1024K
L3 缓存: 36608K
NUMA 节点0 CPU: 0-3,7-9,13-15,20-22,52-55,59-61,65-67,72-74
NUMA 节点1 CPU: 4-6,10-12,16-19,23-25,56-58,62-64,68-71,75-77
NUMA 节点2 CPU: 26-29,33-35,39-41,46-48,78-81,85-87,91-93,98-100
NUMA 节点3 CPU: 30-32,36-38,42-45,49-51,82-84,88-90,94-97,101-103
SKL-SP H0 SKL-SP H0 SKL-SP H0 SKL-SP H0
DDR4 speed MT/s (32GB RDIMMs) 2666 2666 2400 2400
Page Policy Adaptive Adaptive Adaptive Adaptive
SNC (sub-NUMA cluster) disabled enabled disabled enabled
Uncore frequency (Mhz) 2400 2400 2400 2400
L1 cache latency (nsec) 1.1 1.1 1.1 1.1
L2 cache latency (nsec) 4.7 4.6 4.7 4.6
L3 cache latency (nsec) 19.5 17.8 19.5 17.8
Local mem latency (nsec) 83 81 85 83
Remote mem latency (nsec) 143 139 145 141

uncore

“Uncore“ is a term used by Intel to describe the functions of a microprocessor that are not in the core, but which must be closely connected to the core to achieve high performance.[1] It has been called “system agent“ since the release of the Sandy Bridge microarchitecture.[2]

The core contains the components of the processor involved in executing instructions, including the ALU, FPU, L1 and L2 cache. Uncore functions include QPI controllers, L3 cache, snoop agent pipeline, on-die memory controller, on-die PCI Express Root Complex, and Thunderbolt controller.[3] Other bus controllers such as SPI and LPC are part of the chipset.[4]

一些Intel CPU NUMA结构参考

Intel Xeon Platinum 8163(Skylake)阿里云第四代服务器采用的CPU,Skylake架构,主频2.5GHz,计算性能问题。8163这款型号在intel官网上并没有相关信息,应该是阿里云向阿里云定制的,与之相近的Intel Xeon Platinum 8168,价格是$5890,约合¥38900元。

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
lscpu:
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 96
On-line CPU(s) list: 0-95
Thread(s) per core: 2
Core(s) per socket: 24
Socket(s): 2
NUMA node(s): 4
Vendor ID: GenuineIntel
CPU family: 6
Model: 85
Model name: Intel(R) Xeon(R) Platinum 8260 CPU @ 2.40GHz
Stepping: 6
CPU MHz: 2400.000
CPU max MHz: 3900.0000
CPU min MHz: 1000.0000
BogoMIPS: 4800.00
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 1024K
L3 cache: 36608K
NUMA node0 CPU(s): 0-3,7-9,13-15,19,20,48-51,55-57,61-63,67,68
NUMA node1 CPU(s): 4-6,10-12,16-18,21-23,52-54,58-60,64-66,69-71
NUMA node2 CPU(s): 24-27,31-33,37-39,43,44,72-75,79-81,85-87,91,92
NUMA node3 CPU(s): 28-30,34-36,40-42,45-47,76-78,82-84,88-90,93-95

Model: 85
Model name: Intel(R) Xeon(R) Platinum 8268 CPU @ 2.90GHz
Stepping: 6
CPU MHz: 3252.490
BogoMIPS: 5800.00
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 1024K
L3 cache: 36608K
NUMA node0 CPU(s):
0,4,8,12,16,20,24,28,32,36,40,44,48,52,56,60,64,68,72,76,80,84,88,92
NUMA node1 CPU(s):
1,5,9,13,17,21,25,29,33,37,41,45,49,53,57,61,65,69,73,77,81,85,89,93
NUMA node2 CPU(s):
2,6,10,14,18,22,26,30,34,38,42,46,50,54,58,62,66,70,74,78,82,86,90,94
NUMA node3 CPU(s):
3,7,11,15,19,23,27,31,35,39,43,47,51,55,59,63,67,71,75,79,83,87,91,95


lscpu:
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 192
On-line CPU(s) list: 0-191
Thread(s) per core: 1
Core(s) per socket: 24
Socket(s): 8 //每个物理CPU 24个物理core,这24个core应该是分布在2个Die中
NUMA node(s): 16
Vendor ID: GenuineIntel
CPU family: 6
Model: 85
Model name: Intel(R) Xeon(R) Platinum 8260 CPU @ 2.40GHz
Stepping: 7
CPU MHz: 2400.000
CPU max MHz: 3900.0000
CPU min MHz: 1000.0000
BogoMIPS: 4800.00
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 1024K
L3 cache: 36608K
NUMA node0 CPU(s): 0-3,7-9,13-15,19,20
NUMA node1 CPU(s): 4-6,10-12,16-18,21-23
NUMA node2 CPU(s): 24-27,31-33,37-39,43,44
NUMA node3 CPU(s): 28-30,34-36,40-42,45-47
NUMA node4 CPU(s): 48-51,55,56,60-62,66-68
NUMA node5 CPU(s): 52-54,57-59,63-65,69-71
NUMA node6 CPU(s): 72-75,79-81,85-87,91,92
NUMA node7 CPU(s): 76-78,82-84,88-90,93-95
NUMA node8 CPU(s): 96-99,103,104,108-110,114-116
NUMA node9 CPU(s): 100-102,105-107,111-113,117-119
NUMA node10 CPU(s): 120-123,127,128,132-134,138-140
NUMA node11 CPU(s): 124-126,129-131,135-137,141-143
NUMA node12 CPU(s): 144-147,151-153,157-159,163,164
NUMA node13 CPU(s): 148-150,154-156,160-162,165-167
NUMA node14 CPU(s): 168-171,175-177,181-183,187,188
NUMA node15 CPU(s): 172-174,178-180,184-186,189-191

//v62
#lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 104
On-line CPU(s) list: 0-103
Thread(s) per core: 2
Core(s) per socket: 26
Socket(s): 2
NUMA node(s): 2
Vendor ID: GenuineIntel
CPU family: 6
Model: 85
Model name: Intel(R) Xeon(R) Platinum 8269CY CPU @ 2.50GHz
Stepping: 7
CPU MHz: 3200.097
CPU max MHz: 3800.0000
CPU min MHz: 1200.0000
BogoMIPS: 4998.89
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 1024K
L3 cache: 36608K
NUMA node0 CPU(s): 0-25,52-77
NUMA node1 CPU(s): 26-51,78-103

//2016Intel开始出售Intel Xeon E5-2682 v4。 这是一种基于Broadwell架构的桌面处理器,主要为办公系统而设计。 它具有16 核心和32 数据流并使用, 售价约为7000人民币
#lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 64
On-line CPU(s) list: 0-63
Thread(s) per core: 2
Core(s) per socket: 16
Socket(s): 2
NUMA node(s): 2
Vendor ID: GenuineIntel
CPU family: 6
Model: 79
Model name: Intel(R) Xeon(R) CPU E5-2682 v4 @ 2.50GHz
Stepping: 1
CPU MHz: 2499.902
CPU max MHz: 3000.0000
CPU min MHz: 1200.0000
BogoMIPS: 5000.06
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 256K
L3 cache: 40960K
NUMA node0 CPU(s): 0-15,32-47
NUMA node1 CPU(s): 16-31,48-63
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 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 rdt rdseed adx smap xsaveopt cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local cat_l3

intel 架构迭代

Intel processor roadmap

2006年90、65纳米工艺酷睿core Yonah上市,32位架构,仍然算是奔腾Pro系列;2006推出酷睿处理器是介于NetBurst和Core之间,其实是NetBurst的改版,Core 2是第一个基于Core架构的原生双核处理器,65nm工艺,使得AMD K8架构优势全无,直接投入开发原生四核架构K10去了。

2006年7月酷睿2处理器代号为“Conroe”,采用x86-64指令集与65纳米双核心架构。该处理器基于全新的酷睿微架构,虽然时脉大大降低,但在效率方面和性能方面有了重大改进。从这一时期开始,在深度流水线和资源混乱的运行引擎上维持每个周期的高指令(IPC)

2008年的 Nehalem (酷睿i7)是采用 45nm 工艺的新架构,主要优势来自重新设计的I/O和存储系统,这些系统具有新的Intel QuickPath Interconnect和集成的内存控制器,可支持三通道的DDR3内存。引入片内4-12MB的L3 Cache;重新加入超线程;分支预测分级;取消北桥,IMC(集成内存控制器)从北桥挪到片内

2009年的 Westmere 升级到 32nm;退出第一代I5/I3,Xeon 系列也开始推出第一代E命名的E7-x8xx系列。

2010年的 Lynnfield/Clarkdale 基于 45nm/32nm 工艺的新架构,第一代智能酷睿处理器;

2011年的 Sandy Bridge ,基于 32nm 工艺的新架构,第二代智能酷睿处理器,增加AVX指令集扩展, 对虚拟化提供更好支持;实现了GPU和CPU的融合

2012年的 IVY Bridge,是 Sandy Bridge 的 22nm 升级版,第三代智能酷睿处理器,Tick级改进;

2013年的 Haswell ,基于 22nm 工艺的新架构,第四代智能酷睿处理器,Tock级改进;

2014年的 Broadwell,是 Haswell 的 14nm 升级版,第五代智能酷睿处理器;

2015年则推出 SkyLake,基于 14nm 工艺的新架构, Tock级改进,Ring-Bus改成了Mesh架构,第6代Core i系列,8163就是这款;socket之间UPI互联,内存频率通道增强。不再使用Xeon命名,而是改用Bronze/Silver/Gold/Platinum 4个系列。青铜和白银系列支持双路(原本的 E5-24xx、E7-28xx 系列),黄金系列支持四路(原本的 E5-46xx、E7-48xx 系列),白金系列支持八路(原本的 E7-88xx 系列);

2019年的Cascade Lake(X2XX命名)也是Skylake的优化,是Intel首个支持基于3D XPoint的内存模块的微体系结构。同年也正式宣布了十代酷睿处理器,即i9-10900k,还是Skylake微内核不变。

2020年的10nm Ice Lake自家工厂无能,改由台积电加工。

2023年 Intel 发布代号Sapphire Rapids(SPR)的第四代英特尔至强(Intel Xeon)可扩展处理器,其核心数最多可达60个,比代号Ice Lake(-SP)的第三代至强可扩展处理器高出50%。相应的,公开款的TDP指标上限,也从270瓦(W)一跃而至350瓦。这一波核数增长的关键是,大英(终于)从单片式(monolithic)的die,转为四等分的die拼接(跟随了 AMD 的策略)

Core 架构代号是 Yonah,把 NetBurst 做深了的流水线级数又砍下来了,主频虽然降下来了(而且即使后来工艺提升到 45nm 之后也没有超过 NetBurst 的水平),但是却提高了整个流水线中的资源利用率,所以性能还是提升了;把奔腾 4 上曾经用过的超线程也砍掉了;对各个部分进行了强化,双核共享 L2 cache 等等。

从 Core 架构开始是真的走向多核了,就不再是以前 “胶水粘的” 伪双核了,这时候已经有最高 4 核的处理器设计了。

Core 从 65nm 改到 45nm 之后,基于 45nm 又推出了新一代架构叫 Nehalem,新架构Nehalem采用 Intel QPI 来代替原来的前端总线,PCIE 和 DMI 控制器直接做到片内了,不再需要北桥。

2006年Intel也提出了Tick-Tock架构战略。Tick年改进制程工艺,微架构基本不做大改,重点在把晶体管的工艺水平往上提升;Tock年改进微架构设计,保持工艺水平不变,重点在用更复杂、更高级的架构设计。然后就是一代 Tick 再一代 Tock交替演进。

从2006年酷睿架构开始,基本是摁着AMD在地上摩擦,直到2017年的AMD Zen杀回来,性能暴增。img

Sandy Bridge 引入核间的ring bus

感觉Broadwell前面这几代都是在优化cache、通信;接下来的Broadwell和SkyLake就开始改进不大了,疯狂挤牙膏(唯一比较大的改进就是Ring bus到Mesh)

image-20210602154509596

命名规律

Intel E3、E5、E7代表了3个不同档次的至强CPU。EX是按性能和应用场景分的,以前是E3 E5 E7,E3核最少,轻负载应用,E5 核多均衡型,E7是超高性能,核最多。Xeon E5是针对高端工作站及服务器的处理器系列,此系列每年更新,不过架构落后Xeon E3一代。从skylake开始,不再使用EX(E3/E5/E7)了,而是铜、银、金、铂金四种组合。

V2 是ivy bridge,V3 是 haswell, V4 是broadwell,不带VX的是sandy bridge。所以2682是boradwell系列CPU。
然后到了4114,就是Silver,8186就是Platinum,81是skylake,82是cscadelake,再下一代是83。

cascade lake naming scheme.svg

不同的架构下的参数

image.png

UEFI和Bios

UEFI,全称Unified Extensible Firmware Interface,即“统一的可扩展固件接口”,是一种详细描述全新类型接口的标准,是适用于电脑的标准固件接口,旨在代替BIOS(基本输入/输出系统)

电脑中有一个BIOS设置,它主要负责开机时检测硬件功能和引导操作系统启动的功能。而UEFI则是用于操作系统自动从预启动的操作环境,加载到一种操作系统上从而节省开机时间。

UEFI启动是一种新的主板引导项,它被看做是bios的继任者。UEFI最主要的特点是图形界面,更利于用户对象图形化的操作选择。

img

UEFI 图形界面:

img

简单的来说UEFI启动是新一代的BIOS,功能更加强大,而且它是以图形图像模式显示,让用户更便捷的直观操作。

如今很多新产品的电脑都支持UEFI启动模式,甚至有的电脑都已抛弃BIOS而仅支持UEFI启动。这不难看出UEFI正在取代传统的BIOS启动。

UEFI固件通过ACPI报告给OS NUMA的组成结构,其中最重要的是SRAT(System Resource Affinity Table)和SLIT(System Locality Information Table)表。

socket

socket对应主板上的一个插槽,也可以简单理解为一块物理CPU。同一个socket对应着 /proc/cpuinfo 里面的physical id一样。

一个socket至少对应着一个或多个node/NUMA

GPU

GPU只处理有限的计算指令(主要是浮点运算–矩阵操作),不需要分支预测、乱序执行等,所以将Core里面的电路简化(如下图左边),同时通过SIMT(Single Instruction,Multiple Threads, 类似 SIMD)在取指令和指令译码的阶段,取出的指令可以给到后面多个不同的 ALU 并行进行运算。这样,我们的一个 GPU 的核里,就可以放下更多的 ALU,同时进行更多的并行运算了(如下图右边) 。 在 SIMD 里面,CPU 一次性取出了固定长度的多个数据,放到寄存器里面,用一个指令去执行。而 SIMT,可以把多条数据,交给不同的线程去处理。

img

GPU的core在流水线stall的时候和超线程一样,可以调度别的任务给ALU,既然要调度一个不同的任务过来,我们就需要针对这个任务,提供更多的执行上下文。所以,一个 Core 里面的执行上下文的数量,需要比 ALU 多。

img

在通过芯片瘦身、SIMT 以及更多的执行上下文,我们就有了一个更擅长并行进行暴力运算的 GPU。这样的芯片,也正适合我们今天的深度学习和挖矿的场景。

NVidia 2080 显卡的技术规格,就可以算出,它到底有多大的计算能力。2080 一共有 46 个 SM(Streaming Multiprocessor,流式处理器),这个 SM 相当于 GPU 里面的 GPU Core,所以你可以认为这是一个 46 核的 GPU,有 46 个取指令指令译码的渲染管线。每个 SM 里面有 64 个 Cuda Core。你可以认为,这里的 Cuda Core 就是我们上面说的 ALU 的数量或者 Pixel Shader 的数量,46x64 呢一共就有 2944 个 Shader。然后,还有 184 个 TMU,TMU 就是 Texture Mapping Unit,也就是用来做纹理映射的计算单元,它也可以认为是另一种类型的 Shader。

img

2080 的主频是 1515MHz,如果自动超频(Boost)的话,可以到 1700MHz。而 NVidia 的显卡,根据硬件架构的设计,每个时钟周期可以执行两条指令。所以,能做的浮点数运算的能力,就是:

(2944 + 184)× 1700 MHz × 2 = 10.06 TFLOPS

最新的 Intel i9 9900K 的性能是多少呢?不到 1TFLOPS。而 2080 显卡和 9900K 的价格却是差不多的。所以,在实际进行深度学习的过程中,用 GPU 所花费的时间,往往能减少一到两个数量级。而大型的深度学习模型计算,往往又是多卡并行,要花上几天乃至几个月。这个时候,用 CPU 显然就不合适了。

为什么GPU比CPU快

GPU拥有更多的计算单元

GPU像是大卡车,每次去内存取数据取得多,但是Latency高(AP);CPU像是法拉利,更在意处理速度而不是一次处理很多数据,所以CPU有多级cache,都是围绕速度在优化(TP)。在GPU中取数据和处理是流水线所以能消除高Latency。

GPU的每个core拥有更小更快的cache和registry,但是整个GPU的registry累加起来能比CPU大30倍,同时带宽也是后者的16倍

image-20210615105019238

总之GPU相对于CPU像是一群小学生和一个大学教授一起比赛计算10以内的加减法。

英伟达的GPU出圈

2016年之前英伟达的营收和市值基本跟intel一致,但是2021 年 4 月中旬的数字,Intel 是英伟达的近 5 倍,但是如果论市值,英伟达是 Intel 的 1.5 倍。

GPGPU:点亮并行计算的科技树

2007 年,英伟达首席科学家 David Kirk 非常前瞻性地提出 GPGPU 的概念,把英伟达和 GPU 从单纯图形计算拓展为通用计算,强调并行计算,鼓励开发者用 GPU 做计算,而不是局限在图形加速这个传统的领域。GPGPU,前面这个 GP,就是 General Purpose 通用的意思。

CUDA(Compute Unified Device Architecture,统一计算架构),CUDA 不仅仅是一个 GPU 计算的框架,它对下抽象了所有的英伟达出品的 GPU,对上构建了一个通用的编程框架,它实质上制定了一个 GPU 和上层软件之间的接口标准。

在 GPU 市场的早期竞争中,英伟达认识到软硬件之间的标准的重要性,花了 10 年苦功,投入 CUDA 软件生态建设,把软硬件之间的标准,变成自己的核心竞争力。

英伟达可以说是硬件公司中软件做得最好的。同样是生态强大,Wintel 的生态是微软帮忙建的,ARM-Android 的生态是 Google 建的,而 GPU-CUDA 的生态是英伟达自建的。

这个标准有多重要?这么说吧,一流企业定标准,二流企业做品牌,三流企业做产品。在所有的半导体公司中,制定出软件与硬件之间的标准,而且现在还算成功的,只有 3 个,一个是 x86 指令集,一个是 ARM 指令集,还有一个就是 CUDA 了。

img

GPU 相对 CPU 的 TOPS per Watt(花费每瓦特电能可以获得的算力)的差异竞争优势,它的本质就是将晶体管花在计算上,而不是逻辑判断上

2020 年超级计算机 TOP500 更新榜单,可以看到 TOP10 的超级计算机中有 8 台采用了英伟达 GPU、InfiniBand 网络技术,或同时采用了两种技术。TOP500 榜单中,有 333 套(三分之二)采用了英伟达的技术。

挖矿和深度学习撑起了英伟达的市值。

现场可编程门阵列FPGA(Field-Programmable Gate Array)

设计芯片十分复杂,还要不断验证。

那么不用单独制造一块专门的芯片来验证硬件设计呢?能不能设计一个硬件,通过不同的程序代码,来操作这个硬件之前的电路连线,通过“编程”让这个硬件变成我们设计的电路连线的芯片呢?这就是FPGA

  • P 代表 Programmable,这个很容易理解。也就是说这是一个可以通过编程来控制的硬件。
  • G 代表 Gate 也很容易理解,它就代表芯片里面的门电路。我们能够去进行编程组合的就是这样一个一个门电路。
  • A 代表的 Array,叫作阵列,说的是在一块 FPGA 上,密密麻麻列了大量 Gate 这样的门电路。
  • 最后一个 F,不太容易理解。它其实是说,一块 FPGA 这样的板子,可以在“现场”多次进行编程。它不像 PAL(Programmable Array Logic,可编程阵列逻辑)这样更古老的硬件设备,只能“编程”一次,把预先写好的程序一次性烧录到硬件里面,之后就不能再修改了。

FPGA 通过“软件”来控制“硬件”

专用集成电路ASIC(Application-Specific Integrated Circuit)

为解决特定应用问题而定制设计的集成电路,就是 ASIC(Application Specific IC)。当 ASIC 规模够大,逐渐通用起来,某类 ASIC 就会有一个专有名称,成为一个品类。例如现在用来解决人工智能问题的神经网络处理器。

除了 CPU、GPU,以及刚刚的 FPGA,我们其实还需要用到很多其他芯片。比如,现在手机里就有专门用在摄像头里的芯片;录音笔里会有专门处理音频的芯片。尽管一个 CPU 能够处理好手机拍照的功能,也能处理好录音的功能,但是我们直接在手机或者录音笔里塞上一个 Intel CPU,显然比较浪费。

因为 ASIC 是针对专门用途设计的,所以它的电路更精简,单片的制造成本也比 CPU 更低。而且,因为电路精简,所以通常能耗要比用来做通用计算的 CPU 更低。而我们所说的早期的图形加速卡,其实就可以看作是一种 ASIC。

因为 ASIC 的生产制造成本,以及能耗上的优势,过去几年里,有不少公司设计和开发 ASIC 用来“挖矿”。这个“挖矿”,说的其实就是设计专门的数值计算芯片,用来“挖”比特币、ETH 这样的数字货币。

如果量产的ASIC比较小的话可以直接用FPGA来实现,FPGA介于ASIC和PLA之间,PLA(可编程控制器)太简单,直接上ASIC又过于复杂、能耗高、成本高。

张量处理器TPU (tensor processing unit)

张量处理器(英语:tensor processing unit,缩写:TPU)是Google为机器学习定制的专用芯片(ASIC),专为Google的深度学习框架TensorFlow而设计。

在性能上,TPU 比现在的 CPU、GPU 在深度学习的推断任务上,要快 15~30 倍。而在能耗比上,更是好出 30~80 倍。另一方面,Google 已经用 TPU 替换了自家数据中心里 95% 的推断任务,可谓是拿自己的实际业务做了一个明证。

其它基础知识

晶振频率:控制CPU上的晶体管开关切换频率。一次晶振就是一个cycle。

从最简单的单指令周期 CPU 来说,其实时钟周期应该是放下最复杂的一条指令的时间长度。但是,我们现在实际用的都没有单指令周期 CPU 了,而是采用了流水线技术。采用了流水线技术之后,单个时钟周期里面,能够执行的就不是一个指令了。我们会把一条机器指令,拆分成很多个小步骤。不同的指令的步骤数量可能还不一样。不同的步骤的执行时间,也不一样。所以,一个时钟周期里面,能够放下的是最耗时间的某一个指令步骤。

不过没有pipeline,一条指令最少也要N个circle(N就是流水线深度);但是理想情况下流水线跑满的话一个指令也就只需要一个circle了,也就是IPC能到理论最大值1; 加上超标流水线一般IPC都能4,就是一般CPU的超标量。

制程:7nm、14nm、4nm都是指的晶体大小,用更小的晶体可以在相同面积CPU上集成更多的晶体数量,那么CPU的运算能力也更强。增加晶体管可以增加硬件能够支持的指令数量,增加数字通路的位数,以及利用好电路天然的并行性,从硬件层面更快地实现特定的指。打个比方,比如我们最简单的电路可以只有加法功能,没有乘法功能。乘法都变成很多个加法指令,那么实现一个乘法需要的指令数就比较多。但是如果我们增加晶体管在电路层面就实现了这个,那么需要的指令数就变少了,执行时间也可以缩短。

功耗 ~= 1/2 ×负载电容×电压的平方×开关频率×晶体管数量

功耗和电压的平方是成正比的。这意味着电压下降到原来的 1/5,整个的功耗会变成原来的 1/25。

堆栈溢出:函数调用用压栈来保存地址、变量等相关信息。没有选择直接嵌套扩展代码是避免循环调用下嵌套是个无尽循环,inline函数内联就是一种嵌套代码扩展优化。

windows下的exe文件之所以没法放到linux上运行(都是intel x86芯片),是因为可执行程序要经过链接,将所依赖的库函数调用合并进来形成可执行文件。这个可执行文件在Linux 下的 ELF(Execuatable and Linkable File Format) 文件格式,而 Windows 的可执行文件格式是一种叫作 PE(Portable Executable Format)的文件格式。Linux 下的装载器只能解析 ELF 格式而不能解析 PE 格式。而且windows和linux的库函数必然不一样,没法做到兼容。

链接器: 扫描所有输入的目标文件,然后把所有符号表里的信息收集起来,构成一个全局的符号表。然后再根据重定位表,把所有不确定要跳转地址的代码,根据符号表里面存储的地址,进行一次修正。最后,把所有的目标文件的对应段进行一次合并,变成了最终的可执行代码。这也是为什么,可执行文件里面的函数调用的地址都是正确的。

img

虚拟内存地址:应用代码可执行地址必须是连续,这也就意味着一个应用的内存地址必须连续,实际一个OS上会运行多个应用,没办法保证地址连续,所以可以通过虚拟地址来保证连续,虚拟地址再映射到实际零散的物理地址上(可以解决碎片问题),这个零散地址的最小组织形式就是Page。虚拟地址本来是连续的,使用一阵后数据部分也会变成碎片,代码部分是不可变的,一直连续。另外虚拟地址也方便了OS层面的库共享。

为了扩大虚拟地址到物理地址的映射范围同时又要尽可能少地节约空间,虚拟地址到物理地址的映射一般分成了四级Hash,这样4Kb就能管理256T内存。但是带来的问题就是要通过四次查找使得查找慢,这时引入TLAB来换成映射关系。

共享库:在 Windows 下,这些共享库文件就是.dll 文件,也就是 Dynamic-Link Libary(DLL,动态链接库)。在 Linux 下,这些共享库文件就是.so 文件,也就是 Shared Object(一般我们也称之为动态链接库). 不同的进程,调用同样的 lib.so,各自 全局偏移表(GOT,Global Offset Table) 里面指向最终加载的动态链接库里面的虚拟内存地址是不同的, 各个程序各自维护好自己的 GOT,能够找到对应的动态库就好了, 有点像函数指针。

img

符号表:/boot/System.map 和 /proc/kallsyms

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

宏观认识集成电路半导体行业

先从市场分布和市场占有率等几个行业宏观概念来了解半导体行业

半导体产业的产值分布

下图中的处理器就是我们日常所说的CPU,当然还包含了GPU等

我们常说的内存、固态硬盘这些存储器也是数字IC,后面你会看到一个CPU core里面还会有用于存储的cache电路

img

从一台iPhone来看集成电路和芯片

先看一台iPhone X拆解分析里面的所有芯片:

img

全球半导体营收分布

img

美光:美国;Hynix海力士:韩国现代;美国双通:高通(CDMA)、博通(各种买买买、并购,网络设备芯片);欧洲双雄(汽车芯片):恩智浦和英飞凌

半导体行业近 5 年的行业前十的公司列了如下:

img

半导体产品的十大买家

BBK是步步高集团,包含vivo、oppo、oneplus、realme等

img

国内半导体市场情况

中国半导体协会总结过国产芯片的比例,2014 年出台的《国家集成电路产业发展纲要》和 2015 年的《中国制造 2025》文件中有明确提出:到 2020 年,集成电路产业与国际先进水平的差距逐步缩小;2020 年中国芯片自给率要达到 40%,2025 年要达到 50%。

img

国产化国家主导:紫光, 紫光的策略从收购转为自建,2016 年 12 月,合并武汉新芯,成立长江存储,与西数合资成立紫光西数。

长江存储在量产 64 层 NAND Flash 之后,2020 年首发 192 层 3D NAND,被预测 2021 会拿下 8% 的 NAND Flash 份额。同时,在存储芯片领域,中国还有一家公司叫做长鑫存储,长鑫存储以唯一一家中国公司的名号,杀入 DRAM 领域。在世界著名的行业分析公司 Yole 公司的报告上,显示长江存储和长鑫存储与三星、SK 海力士、美光和 Intel 齐头并进。

市场份额上,国产存储芯片市场,也许还有望达到 2025 的目标。

以上是我们对集成电路半导体行业的宏观认识。接下来我们从一颗CPU的生产制造开始讲

工艺

光刻的粒度越来越细,玩家也越来越少,基本主流都是代工模式:

img

img

晶体管密度比较

image-20210728095829384

计算机芯片发展总结和展望

从最早集成工艺驱动了CPU的性能符合摩尔定律发展,到现在工艺受限于物理上的客观因素:门延迟、电路间的绝缘层越薄漏电越严重–高温导致漏电呈指数级增加、电压无法随着尺寸降低而线性降低–130nm之前电压随线宽(工艺)而线性下降,到90nm工艺之后工作电压始终在1V左右无法进一步下降,越来越难进一步优化,导致摩尔定律基本已经做不到了。

下图显示最近几年CPU性能改进都在3%左右了(红色部分)

f6.jpg

从上图可以看到性能的优化从最早CISC每年22%来自于指令本身优化,然后RISC(CISC内部也学习RISC将复杂指令编译成简单指令来适应流水线)每年52%,再到简单多核,然后到大规模多核,最后到红色无所改进。

登纳德缩放定律(Dennard scaling)指出,随着晶体管密度的增加,每个晶体管的能耗将降低,因此硅芯片上每平方毫米上的能耗几乎保持恒定。由于每平方毫米硅芯片的计算能力随着技术的迭代而不断增强,计算机将变得更加节能。登纳德缩放定律从 2007 年开始大幅放缓,2012 年左右接近失效(见下图)。

Dennard缩放定律(摩尔定律是经济定律,Dennard才是半导体专业定律):晶体管的尺寸在每一代技术中都缩小了30% (0.7倍),因此它们的面积减少了50%。这意味着电路减少了30% (0.7倍)的延迟,因此增加了约40% (1.4倍)的工作频率。最后,为了保持电场恒定,电压降低了30%,能量降低了65%,功率降低了50%。因此,在每一代技术中,晶体管密度增加一倍,电路速度提高40%,功耗(晶体管数量增加一倍)保持不变。

f3.jpg

阿姆达尔定律(Amdahl’s Law)认为,并行计算机的加速受限于串行计算的部分。如下图假设只在一个处理器上执行时的串行执行的部分所占比例不同,与单个内核相比,最多 64 个内核的应用程序运行速度能快多少。例如,如果只有 1% 的时间是串行的,那么 64 核配置可加速大约 35 倍,但所需能量与 64 个处理器成正比,因此大约有 45% 的能量被浪费了。核数越多多核带来的提升效果越来越差(程序总有地方是串行的)

f5.jpg

大概从2000年左右CPU的性能增长开始放缓,到2018年实际性能比摩尔定律预估的差了15倍(注意纵坐标是指数级),因为CMOS技术已经接近极限

f2.jpg

CMOS电路是互补型金属氧化物半导体电路(Complementary Metal-Oxide-Semiconductor)的英文字头缩写,它由绝缘场效应晶体管组成,由于只有一种载流子,因‘而是一种单极型晶体管集成电路,其基本结构是一个N沟道MOS管和一个P沟道MOS管

过去一些对性能或者便利性的改进以及对这些改进的打分:

虚拟地址在计算机体系结构里可以评为特优的一项技术,非性能上的改进,甚至对性能有负面影响;

超线程、流水线、多发射只是优;

cache 只是良好(成本高),cache整体肯定比超线程对性能提升要大,但是因为高成本导致得分不高

计算机架构的未来机遇

当实际集成度(性能)已经不再增长后,我们必须找到新的办法

如下图是用Python实现的矩阵相乘的性能优化过程,简单地将 Python 语言代码重写为 C 代码就可以将性能提升 46 倍(Python 是典型的高级、动态类型语言)。在多核上运行并行循环(parallel loops)又将性能提升接近 7 倍。优化内存配置又将性能提升了近 19 倍,而通过单指令多数据(SIMD)并行化操作(一个指令执行 16 个 32-bit 运算)的硬件扩展,性能又提升了 8 倍多。也就是说,最终的高度优化版本在多核英特尔处理器上的运行速度是初始 Python 版本的 62,000 多倍。这当然只是一个很小的例子,但我们会期望程序员使用优化库。尽管这夸大了常见的性能差距,但很多程序的性能差距可能达到 100 到 1000 倍。

f7.jpg

特定领域的体系结构(DSA)

特定领域的体系结构(DSA)–针对具体领域的特定优化和改进可以能是一个大的改进方向。比如SIMD,同时DSA和cache、内存的层次结构更匹配。

对特定领域降低精度,通用任务的 CPU 通常支持 32 和 64 位整型数和浮点数数据。对于很多机器学习和图像应用来说,这种准确率有点浪费了。例如在深度神经网络中(DNN),推理通常使用 4、8 或 16 位整型数,从而提高数据和计算吞吐量。同样,对于 DNN 训练程序,浮点数很有意义,但 32 位就够了,16 为经常也能用。

还可以在特定领域通过特定领域语言(DSL)编写的目标程序,这些程序可以实现更高的并行性(比如TPU、GPU)

开放式架构(Open Architectures)

RISC-V

总结

Wafer:晶圆,一片大的纯硅圆盘,新闻里常说的12寸、30寸晶圆厂说的就是它,光刻机在晶圆上蚀刻出电路

Die:从晶圆上切割下来的裸片(包含多个core、北桥、GPU等),Die的大小可以自由决定,得考虑成本和性能, Die做成方形便于切割和测试

封装:将一个或多个Die封装成一个物理上可以售卖的CPU

路:就是socket、也就是封装后的物理CPU

node:同一个Die下的多个core以及他们对应的内存,对应着NUMA

现在计算机系统的CPU和芯片组内核Die都是先封装到一个印制板上(PCB,printed circuit board),再通过LGA等等插槽(Socket)连上主板或直接焊接在主板上。这个过程叫做封装(Package),相关技术叫做封装技术。

系列文章

CPU的制造和概念

[CPU 性能和Cache Line](/2021/05/16/CPU Cache Line 和性能/)

[Perf IPC以及CPU性能](/2021/05/16/Perf IPC以及CPU利用率/)

Intel、海光、鲲鹏920、飞腾2500 CPU性能对比

飞腾ARM芯片(FT2500)的性能测试

十年后数据库还是不敢拥抱NUMA?

一次海光物理机资源竞争压测的记录

[Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的](/2019/12/16/Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的/)

多元 CPU 性能调优的技术挑战、产品设计和业务实践 – 2024 百度对AMD、Ampere和Intel 的一些差异进行了比较

CPU 性能和Cache Line

发表于 2021-05-16 | 分类于 CPU

CPU 性能和Cache Line

为了让程序能快点,特意了解了CPU的各种原理,比如多核、超线程、NUMA、睿频、功耗、GPU、大小核再到分支预测、cache_line失效、加锁代价、IPC等各种指标(都有对应的代码和测试数据)都会在这系列文章中得到答案。当然一定会有程序员最关心的分支预测案例、Disruptor无锁案例、cache_line伪共享案例等等。

这次让我们从最底层的沙子开始用8篇文章来回答各种疑问以及大量的实验对比案例和测试数据。

大的方面主要是从这几个疑问来写这些文章:

  • 同样程序为什么CPU跑到800%还不如CPU跑到200%快?
  • IPC背后的原理和和程序效率的关系?
  • 为什么数据库领域都爱把NUMA关了,这对吗?
  • 几个国产芯片的性能到底怎么样?

系列文章

CPU的制造和概念

[Perf IPC以及CPU性能](/2021/05/16/Perf IPC以及CPU利用率/)

[CPU 性能和Cache Line](/2021/05/16/CPU Cache Line 和性能/)

十年后数据库还是不敢拥抱NUMA?

[Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的](/2019/12/16/Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的/)

Intel、海光、鲲鹏920、飞腾2500 CPU性能对比

一次海光物理机资源竞争压测的记录

飞腾ARM芯片(FT2500)的性能测试

CPU为什么要CACHE,请看这篇

什么是 cache_line

CPU从内存中读取数据的时候是一次读一个cache_line到 cache中以提升效率,一般情况下cache_line的大小是64 byte(64Bytes也就是16个32位的整型)这就是CPU从内存中捞数据上来的最小数据单位,按照热点逻辑还是大概率会依次被访问到(详见后面的例子)。

比如L1 Cache 有32KB,那么它可以分成32KB / 64 = 512 条 Cache Line。

Cache Line 是 CPU 和主存之间数据传输的最小单位。当一行 Cache Line 被从内存拷贝到 Cache 里,Cache 里会为这个 Cache Line 创建一个条目。这个 Cache 条目里既包含了拷贝的内存数据,即 Cache Line,又包含了这行数据在内存里的位置等元数据信息。

处理器都实现了 Cache 一致性 (Cache Coherence)协议。如历史上 x86 曾实现了 MESI 协议,以及 MESIF 协议。

image-20220928160819468

先看下如上一张图,其中

tag:一般虚拟地址高位多bit表示;

index: 虚拟地址中间多bit表示;

offset: 虚拟地址多bit表示;

但是这三者的值是多少呢,只能说和cache缓存的的大小息息相关。

举个例子,录入cache缓存大小为64K, 有4路, 服务器寻址为64bit。

  • offset的值为 2^ = 64; offset = 6;
  • index的值为 64k / (64 * 4) = 256 = 2 ^ 8; 所以index的值为8bit;
  • tag的值为 64 - 8 - 6 = 50bit;

注:此计算完全按照理论方式计算,实际情况需要考虑TLB别名以及其他情况影响。

了解以上概念后,此处用一张图去介绍TLB转换获取数据的过程。

cache 失效

假设两个处理器 A 和 B, 都在各自本地 Cache Line 里有同一个变量的拷贝时,此时该 Cache Line 处于 Shared 状态。当处理器 A 在本地修改了变量,除去把本地变量所属的 Cache Line 置为 Modified 状态以外,还必须在另一个处理器 B 读同一个变量前,对该变量所在的 B 处理器本地 Cache Line 发起 Invaidate 操作,标记 B 处理器的那条 Cache Line 为 Invalidate 状态。随后,若处理器 B 在对变量做读写操作时,如果遇到这个标记为 Invalidate 的状态的 Cache Line,即会引发 Cache Miss,从而将内存中最新的数据拷贝到 Cache Line 里,然后处理器 B 再对此 Cache Line 对变量做读写操作。

cache ping-pong(cache-line ping-ponging) 是指不同的CPU共享位于同一个cache-line里边的变量,当不同的CPU频繁的对该变量进行读写时,会导致其他CPU cache-line的失效。

显而易见的是一旦cache失效就需要访问内存重新从内存中读取数据到CPU cache中,这个过程会很慢。

查看 cache_line

如下 Linux getconf 命令的输出,除了 *_LINESIZE 指示了系统的 Cache Line 的大小是 64 字节外,还给出了 Cache 类别,大小。 其中 *_ASSOC 则指示了该 Cache 是几路关联 (Way Associative) 的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$sudo getconf -a |grep CACHE
LEVEL1_ICACHE_SIZE 32768
LEVEL1_ICACHE_ASSOC 8
LEVEL1_ICACHE_LINESIZE 64
LEVEL1_DCACHE_SIZE 32768
LEVEL1_DCACHE_ASSOC 8
LEVEL1_DCACHE_LINESIZE 64
LEVEL2_CACHE_SIZE 262144
LEVEL2_CACHE_ASSOC 4
LEVEL2_CACHE_LINESIZE 64
LEVEL3_CACHE_SIZE 3145728
LEVEL3_CACHE_ASSOC 12
LEVEL3_CACHE_LINESIZE 64
LEVEL4_CACHE_SIZE 0
LEVEL4_CACHE_ASSOC 0
LEVEL4_CACHE_LINESIZE 0

比如,对于下面的FT2500 ARM芯片下,L1D是32K,是因为32K=256*2*64(64就是cache_line大小,16个int), 这32K是256个组,每组2行(x86一般是每组8行),每行就是一个cache_line

image-20210914175307651

cache_line 影响性能的案例

如下两个循环执行次数循环2是循环1的十六分之一。但是在x86和arm下执行时间都是循环2是循环1的四分之一左右。

之所以执行时间不是十六分之一是因为循环一重用了cache_line.

Xeon(R) Platinum 8260跑这个程序的性能是鲲鹏920的2倍左右。

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
#include "stdio.h"
#include <stdlib.h>
#include <time.h>

long timediff(clock_t t1, clock_t t2) {
long elapsed;
elapsed = ((double)t2 - t1) / CLOCKS_PER_SEC * 1000;
return elapsed;
}

int main(int argc, char *argv[])
{
long length=64*1024*1024;
int* arr=malloc(64*1024*1024 * sizeof(int));
long i=0;
long j=0;
for (i = 0; i < length; i++) arr[i] = i;

clock_t start=clock();
// 循环1
for(j=0; j<10; j++){
for (i = 0; i < length; i++) arr[i] *= 3; //每取一次arr[i], 通过cache_line顺便把后面15个arr[i]都取过来了
}
clock_t end =clock();
printf("%lu\n", timediff(start,end));

start=clock();
// 循环2
for(j=0; j<10; j++){
for (i = 0; i < length; i += 16) arr[i] *= 3;
}
end =clock();
printf("%lu\n", timediff(start,end));
}

鲲鹏920上循环一的perf结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#perf stat -- ./cache_line_loop.out
2790

failed to read counter branches

Performance counter stats for './cache_line_loop.out':

3238.892820 task-clock (msec) # 1.000 CPUs utilized
4 context-switches # 0.001 K/sec
0 cpu-migrations # 0.000 K/sec
65,582 page-faults # 0.020 M/sec
8,420,900,487 cycles # 2.600 GHz
23,284,432 stalled-cycles-frontend # 0.28% frontend cycles idle
4,709,527,283 stalled-cycles-backend # 55.93% backend cycles idle
14,553,892,976 instructions # 1.73 insns per cycle
# 0.32 stalled cycles per insn //因为有cache_line的命中,stall是循环二的四分之一
<not supported> branches
141,482 branch-misses # 0.00% of all branches

3.239729660 seconds time elapsed

鲲鹏920上循环二的perf结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#perf stat -- ./cache_line_loop.out
730
failed to read counter branches

Performance counter stats for './cache_line_loop.out':

1161.126720 task-clock (msec) # 0.999 CPUs utilized
1 context-switches # 0.001 K/sec
0 cpu-migrations # 0.000 K/sec
65,583 page-faults # 0.056 M/sec
3,018,882,346 cycles # 2.600 GHz
21,846,222 stalled-cycles-frontend # 0.72% frontend cycles idle
2,456,150,941 stalled-cycles-backend # 81.36% backend cycles idle
1,970,906,199 instructions # 0.65 insns per cycle
# 1.25 stalled cycles per insn
<not supported> branches
138,051 branch-misses # 0.00% of all branches

1.161791340 seconds time elapsed

在Xeon(R) Platinum 8260 CPU @ 2.40GHz 上运行上面两个循环的时间:

1
2
3
#perf stat -- ./cache_line_loop.out
1770
370

更多案例请参考7个示例科普CPU CACHE:Gallery of Processor Cache Effects

如下图,表示的是for循环每次跳K个int,在K小于16的时候虽然循环次数逐渐减少到原来的1/16, 但是总时间没变,因为一直是访问的同一个cache里面的数据。 到16个之后就会产生突变(跨了cache_line),再后面32、64、128的时间减少来源于循环次数的减少,因为如论如何每次循环都需要访问内存加载数据到cache_line中

1
for (int i = 0; i < arr.Length; i += K) arr[i] *= 3;

running times of this loop for different step values (K)

更典型的案例是对一个二维数组逐行遍历和逐列遍历的时间差异,变量次数一样,但是因为二维数组按行保存,所以逐行遍历对cache line 更友好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const int row = 1024;
const int col = 512
int matrix[row][col];
//逐行遍历 0.081ms
int sum_row=0;
for(int _r=0; _r<row; _r++) {
for(int _c=0; _c<col; _c++){
sum_row += matrix[_r][_c];
}
}
//逐列遍历 1.069ms
int sum_col=0;
for(int _c=0; _c<col; _c++) {
for(int _r=0; _r<row; _r++){
sum_col += matrix[_r][_c];
}
}

四线程竞争下的cache_line影响

image-20220613103011120

上图是每个线程对内存中自己的int进行++ (每个线程绑定在自己的core上,机器有4个P4 core), 蓝色部分是每个线程的变量分配在线程内部,也就是每个变量有独立的cache_line,红色部分(含蓝色)是将变量放在一个cache_line(必然会出现伪共享)

Disruptor

Disruptor论文中讲述了我们所做的一个实验。这个测试程序调用了一个函数,该函数会对一个64位的计数器循环自增5亿次。当单线程无锁时,程序耗时300ms。如果增加一个锁(仍是单线程、没有竞争、仅仅增加锁),程序需要耗时10000ms,慢了两个数量级。更令人吃惊的是,如果增加一个线程(简单从逻辑上想,应该比单线程加锁快一倍),耗时224000ms。使用两个线程对计数器自增5亿次比使用无锁单线程慢1000倍。并发很难而锁的性能糟糕。单线程使用CAS耗时5700ms。所以它比使用锁耗时少,但比不需要考虑竞争的单线程耗时多。

We will illustrate the cost of locks with a simple demonstration. The focus of this experiment is to call a function which increments a 64-bit counter in a loop 500 million times. This can be executed by a single thread on a 2.4Ghz Intel Westmere EP in just 300ms if written in Java. The language is unimportant to this experiment and results will be similar across all languages with the same basic primitives.

Once a lock is introduced to provide mutual exclusion, even when the lock is as yet un-contended, the cost goes up significantly. The cost increases again, by orders of magnitude, when two or more threads begin to contend. The results of this simple experiment are shown in the table below:

Table 1. Comparative costs of contention

Method Time (ms)
Single thread 300
Single thread with lock 10,000
Two threads with lock 224,000
Single thread with CAS 5,700
Two threads with CAS 30,000
Single thread with volatile write 4,700

如下测试代码:

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
86
87
88
89
90
91
package test;

import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockBenchmark{
public static void runIncrement()
{
long counter = 0;
long max = 50000000000L;
long start = System.currentTimeMillis();
while (counter < max) {
counter++;
}
long end = System.currentTimeMillis();
System.out.println("Time spent is " + (end-start) + "ms without lock");
}

public static void runIncrementWithLock()
{
Lock lock = new ReentrantLock();
long counter = 0;
long max = 500000000L;
long start = System.currentTimeMillis();
while (counter < max) {
if (lock.tryLock()){
counter++;
lock.unlock();
}
}
long end = System.currentTimeMillis();
System.out.println("Time spent is " + (end-start) + "ms with lock");
}

public static void main(String[] args) {
runIncrement();
System.out.println("start runIncrementWithLock.");
runIncrementWithLock();
}
}

[root@ARM 14:14 /root]
#java test.LockBenchmark
Time spent is 19261ms without lock
start runIncrementWithLock.
Time spent is 17267ms with lock

//单线程加锁在没有任何竞争的情况下慢了两个数量级是因为加锁动作本身需要几十个指令
reentrantLock.tryLock()实现:
11 final boolean nonfairTryAcquire(int);
12 Code:
13 0: invokestatic #2 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
14 3: astore_2
15 4: aload_0
16 5: invokevirtual #3 // Method getState:()I
17 8: istore_3
18 9: iload_3
19 10: ifne 29
20 13: aload_0
21 14: iconst_0
22 15: iload_1
23 16: invokevirtual #4 // Method compareAndSetState:(II)Z
24 19: ifeq 65
25 22: aload_0
26 23: aload_2
27 24: invokevirtual #5 // Method setExclusiveOwnerThread:(Ljava/lang/Thread;)V
28 27: iconst_1
29 28: ireturn
30 29: aload_2
31 30: aload_0
32 31: invokevirtual #6 // Method getExclusiveOwnerThread:()Ljava/lang/Thread;
33 34: if_acmpne 65
34 37: iload_3
35 38: iload_1
36 39: iadd
37 40: istore 4
38 42: iload 4
39 44: ifge 57
40 47: new #7 // class java/lang/Error
41 50: dup
42 51: ldc #8 // String Maximum lock count exceeded
43 53: invokespecial #9 // Method java/lang/Error."<init>":(Ljava/lang/String;)V
44 56: athrow
45 57: aload_0
46 58: iload 4
47 60: invokevirtual #10 // Method setState:(I)V
48 63: iconst_1
49 64: ireturn
50 65: iconst_0
51 66: ireturn

不加锁的循环执行500亿次循环,加锁的只执行5亿次,最终耗时差不多。对应两个阶段的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
34
35
36
#perf stat -p 92098
Performance counter stats for process id '92098':

3978.381920 task-clock (msec) # 1.001 CPUs utilized
121 context-switches # 0.030 K/sec
7 cpu-migrations # 0.002 K/sec
71 page-faults # 0.018 K/sec
10,343,414,319 cycles # 2.600 GHz
2,091,748 stalled-cycles-frontend # 0.02% frontend cycles idle
11,011,682 stalled-cycles-backend # 0.11% backend cycles idle
41,311,635,225 instructions # 3.99 insns per cycle //不加锁循环++
# 0.00 stalled cycles per insn
<not supported> branches
32,675 branch-misses # 0.00% of all branches

3.972534070 seconds time elapsed

[root@ARM 13:55 /root]
#perf stat -p 92098
^Cfailed to read counter branches

Performance counter stats for process id '92098':

10599.558340 task-clock (msec) # 1.001 CPUs utilized
292 context-switches # 0.028 K/sec
1 cpu-migrations # 0.000 K/sec
202 page-faults # 0.019 K/sec
27,557,631,981 cycles # 2.600 GHz
1,079,785,178 stalled-cycles-frontend # 3.92% frontend cycles idle
15,669,652,101 stalled-cycles-backend # 56.86% backend cycles idle
14,456,635,493 instructions # 0.52 insns per cycle //加锁循环++
# 1.08 stalled cycles per insn
<not supported> branches
69,722 branch-misses # 0.00% of all branches

10.592190690 seconds time elapsed

可以看到最终时间差了100倍,IPC差了8倍,从指令数来看加锁后指令数会略多,但是加锁造成了stall(即使没有实际竞争)。

上述代码如果是在:Intel(R) Xeon(R) CPU E5-2682 v4 @ 2.50GHz 上运行,差距要小很多,也可以看出intel x86芯片优化比较好。不加锁的循环X86比ARM要慢一点点是因为ARM芯片的主频是2.6G,要高一点点。

1
2
3
4
#java test.LockBenchmark  //x86
Time spent is 20135ms without lock
start runIncrementWithLock.
Time spent is 13056ms with lock

此时Intel CPU上对应的IPC分别是3.99和1.

这里加锁和不加锁最终性能差了将近2个数量级,但是IPC只差了8倍,另外的差异在加锁后增加了很多的指令、函数调用等。如果两个函数都增加每个循环里面的指令数量,那么他们的时间差距会缩小。如果增加的指令是乘法、除法会大幅降低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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#cat LockBenchmark.java
package test;

import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockBenchmark{
public static void runIncrement()
{
long counter = 0;
long max = 500000000L;
double sum =100.0;
long start = System.currentTimeMillis();
while (counter < max) {
counter++;
sum=3.251;
for(int i=0; i<10; ++i){
sum += sum*3.75/3;
}
}
long end = System.currentTimeMillis();
System.out.println("Time spent is " + (end-start) + "ms without lock:"+sum);
}

public static void runIncrementWithLock()
{
Lock lock = new ReentrantLock();
long counter = 0;
double sum=100.0;
long max = 500000000L;
long start = System.currentTimeMillis();
while (counter < max) {
if (lock.tryLock()){
counter++;
sum=3.253;
for(int i=0; i<10; ++i){
sum += sum*3.75/3;
}
lock.unlock();
}
}
long end = System.currentTimeMillis();
System.out.println("Time spent is " + (end-start) + "ms with lock:"+sum);
}

public static void main(String[] args) {
runIncrement();
System.out.println("start runIncrementWithLock.");
runIncrementWithLock();
}
}

在Intel芯片下,加锁运行时间慢了1倍,IPC差不多,运行时间和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
34
35
36
37
38
39
#java test.LockBenchmark  //如上代码循环次数都是5亿次, intel cpu
Time spent is 11884ms without lock:10810.40962948895
start runIncrementWithLock.
Time spent is 22662ms with lock:10817.060142949109

#perf stat -p `jps | grep LockBenchmark | awk '{ print $1 }'`
^C
Performance counter stats for process id '117862':

7144.193030 task-clock (msec) # 1.002 CPUs utilized (100.00%)
227 context-switches # 0.032 K/sec (100.00%)
26 cpu-migrations # 0.004 K/sec (100.00%)
199 page-faults # 0.028 K/sec
17,842,543,877 cycles # 2.497 GHz (100.00%)
<not supported> stalled-cycles-frontend
<not supported> stalled-cycles-backend
17,153,665,963 instructions # 0.96 insns per cycle (100.00%)
2,408,676,080 branches # 337.152 M/sec (100.00%)
39,593 branch-misses # 0.00% of all branches

7.133030625 seconds time elapsed


#perf stat -p `jps | grep LockBenchmark | awk '{ print $1 }'`
^C
Performance counter stats for process id '117862':

3962.496661 task-clock (msec) # 1.002 CPUs utilized (100.00%)
123 context-switches # 0.031 K/sec (100.00%)
3 cpu-migrations # 0.001 K/sec (100.00%)
77 page-faults # 0.019 K/sec
9,895,900,342 cycles # 2.497 GHz (100.00%)
<not supported> stalled-cycles-frontend
<not supported> stalled-cycles-backend
10,504,412,147 instructions # 1.06 insns per cycle (100.00%)
1,925,721,763 branches # 485.987 M/sec (100.00%)
55,018 branch-misses # 0.00% of all branches

3.955251872 seconds time elapsed

在鲲鹏920下的运行时间和IPC,两个循环最终执行时间一样,但是加锁的循环 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
34
35
36
37
38
39
40
41
42
43
#java test.LockBenchmark  //鲲鹏920
Time spent is 37037ms without lock:10810.40962948895
start runIncrementWithLock.
Time spent is 37045ms with lock:10817.060142949109 //极低的概率这里能跑出来15秒,应该是偷鸡优化了


#perf stat -p `jps | grep LockBenchmark | awk '{ print $1 }'`
^Cfailed to read counter branches

Performance counter stats for process id '104166':

3459.850580 task-clock (msec) # 1.002 CPUs utilized
122 context-switches # 0.035 K/sec
1 cpu-migrations # 0.000 K/sec
257 page-faults # 0.074 K/sec
8,995,482,376 cycles # 2.600 GHz
344,461,881 stalled-cycles-frontend # 3.83% frontend cycles idle
7,060,741,196 stalled-cycles-backend # 78.49% backend cycles idle
2,667,443,624 instructions # 0.30 insns per cycle //不带Lock 乘除法拉低了IPC
# 2.65 stalled cycles per insn
<not supported> branches
93,302,896 branch-misses # 0.00% of all branches

3.453102950 seconds time elapsed

#perf stat -p `jps | grep LockBenchmark | awk '{ print $1 }'`
^Cfailed to read counter branches

Performance counter stats for process id '100351':

3205.548380 task-clock (msec) # 1.002 CPUs utilized
97 context-switches # 0.030 K/sec
0 cpu-migrations # 0.000 K/sec
93 page-faults # 0.029 K/sec
8,334,345,888 cycles # 2.600 GHz
10,217,474 stalled-cycles-frontend # 0.12% frontend cycles idle
6,389,615,752 stalled-cycles-backend # 76.67% backend cycles idle
4,374,642,352 instructions # 0.52 insns per cycle //带lock
# 1.46 stalled cycles per insn
<not supported> branches
2,053,478 branch-misses # 0.00% of all branches

3.199261610 seconds time elapsed

这个代码加锁后指令多了1倍,所以intel CPU下体现出来的时间就差了一倍(IPC一样的);鲲鹏 CPU下时间差不多是因为没加锁的IPC太低了(乘除法对流水线没优化好),最终IPC差了一倍,就把执行时间拉平了。另外就就是Intel和鲲鹏的执行时间对比和IPC也是一致的,IPC高执行就快。

Disruptor中对cache_line的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
abstract class RingBufferPad
{
protected long p1, p2, p3, p4, p5, p6, p7;
}

abstract class RingBufferFields<E> extends RingBufferPad
{
......
private final long indexMask;
private final Object[] entries;
protected final int bufferSize;
protected final Sequencer sequencer;
......
}

public final class RingBuffer<E> extends RingBufferFields<E> implements Cursored, EventSequencer<E>, EventSink<E>
{
......
protected long p1, p2, p3, p4, p5, p6, p7;
......
}

重点留意上述代码中的p1-p7这几个没有用的long变量,实际使用来占位,占住实际变量前后的位置,这样避免这些变量被其他变量的修改而失效。

image.png

队列大部分时候都是空的(head挨着tail),也就导致head 和 tail在一个cache line中,读和写会造成没必要的cache ping-pong,一般可以通过将head 和 tail 中间填充其它内容来实现错开到不同的cache line中

image

数组(RingBuffer)基本能保证元素在内存中是连续的,但是Queue(链表)就不一定了,连续的话更利于CPU cache

Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的

MySQL利用Intel 的Pause指令在spinlock(自旋锁)的时候尽量避免cache line ping-pong,但是不同的Intel芯片每个Pause指令背后实际执行的circle是不一样的,从而导致MySQL性能差异很大

详细请看:

[《Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的》 从一个参数引起的rt抖动定位到OS锁等待再到CPU Pause指令,以及不同CPU型号对Pause使用cycles不同的影响,最终反馈到应用层面的rt全过程。在MySQL内核开发的时候考虑了Pause,但是没有考虑不同的CPU型号,所以换了CPU型号后性能差异比较大](/2019/12/16/Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的/)

pause 和 spinlock

spinlock(自旋锁)是内核中最常见的锁,它的特点是:等待锁的过程中不休眠,而是占着CPU空转,优点是避免了上下文切换的开销,缺点是该CPU空转属于浪费, 同时还有可能导致cache ping-pong,spinlock适合用来保护快进快出的临界区。持有spinlock的CPU不能被抢占,持有spinlock的代码不能休眠

ECS cache_line miss导致整个物理机响应慢

如果一台ECS运行大量的cache_line miss逻辑,也就是利用spinlock所保护的区域没有按照cacheline对齐的时候,CPU为了保证数据一致性,会触发Super Queue lock splits,将总线锁住,哪怕是其他socket,而这个时候,其他CPU CORE访问L2cache、L3cahe、以及内存就会阻塞,直到Super Queue lock splits释放。

这个影响不是socket、node内部,而是整个物理机总线被锁,所以影响的是整个物理机。

从地址不对齐访问到split lock

Intel CPU微架构允许不对齐的内存访问,但ARM、RISC-V等架构却不允许。在众多的不对齐中,一个特殊的场景是:原子操作的操作数(由于地址不对齐)跨越两个cache lines,Intel将之叫做split lock。它有两个特征:

  1. 原子操作,即汇编指令包含Lock前缀;
  2. 操作数地址不对齐,还跨越两个cache lines;

其实大部分吃瓜群众都不知道这个特性,但是它却对应用性能影响极大。Intel工程师Fenghua Yu同学正在开发一组内核补丁,用于检测和处理split lock,现在已经发出了第8版code review。阿里巴巴在多年前就意识到split lock的危害,在线上实施了大规模监控,并采取必要隔离措施。

学过体系结构的同学都应该知道,缓存一制性协议MESI只能保证cache line粒度的一致性。同时访问两个cache lines不是常见操作,为保证split lock的原子性,设计硬件时使用特殊逻辑(冷路径)来处理:锁住整个访存总线,阻止其它逻辑cpu访存。

从原理出发,我们很容易想到,锁住总线将导致其它core上访存操作受阻,宏观表现为平均访存延时显著上升。为不让各位看官白走一趟,小编在自己的skylake机器上测了一组数据,随着split lock速率的增加,访存延迟呈指数恶化。

img

分支预测案例

这个案例总循环次数一样多,但是里外循环次数不一样:

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
#include "stdio.h"
#include <stdlib.h>
#include <time.h>

long timediff(clock_t t1, clock_t t2) {
long elapsed;
elapsed = ((double)t2 - t1) / CLOCKS_PER_SEC * 1000;
return elapsed;
}

int main(int argc, char *argv[])
{
int j=0;
int k=0;
int c=0;
clock_t start=clock();
for(j=0; j<100000; j++){
for(k=0; k<1000; k++){
for(c=0; c<100; c++){
}
}
}
clock_t end =clock();
printf("%lu\n", timediff(start,end)); //case1

start=clock();
for(j=0; j<100; j++){
for(k=0; k<1000; k++){
for(c=0; c<100000; c++){
}
}
}
end =clock();
printf("%lu\n", timediff(start,end)); //case2
return 0;
}

x86_64下的执行结果,确实是case2略快

1
2
3
4
5
6
7
#taskset -c 0 ./for_prediction.out
25560
23420

#taskset -c 0 ./for_prediction.out
25510
23410

case1的branch miss大概接近1%(看0 core上的 BrchMiss%, 数据由 xperf 1.3.8采集)

image-20210517111209985

case2的branch miss降到了0,不过两者在x86上的IPC都是0.49,所以最终的执行时间差异不大

image-20210517111244550

image-20210512133536939

在arm下case1反而更快,如截图

image-20210512132121856

系列文章

CPU的制造和概念

[CPU 性能和Cache Line](/2021/05/16/CPU Cache Line 和性能/)

[Perf IPC以及CPU性能](/2021/05/16/Perf IPC以及CPU利用率/)

Intel、海光、鲲鹏920、飞腾2500 CPU性能对比

飞腾ARM芯片(FT2500)的性能测试

十年后数据库还是不敢拥抱NUMA?

一次海光物理机资源竞争压测的记录

[Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的](/2019/12/16/Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的/)

参考资料

Analysis of False Cache Line Sharing Effects on Multicore CPUs

Avoiding and Identifying False Sharing Among Threads

Gallery of Processor Cache Effects

7个示例科普CPU CACHE

Why is transposing a matrix of 512×512 much slower than transposing a matrix of 513×513 ? 矩阵倒置的时候因为同一个cache_line的数据频繁被update导致cache_line失效,也就是FALSE share

CPU时间都去哪了:一步步定位数据库代码中的性能瓶颈(SAP)

与程序员相关的CPU缓存知识

Perf IPC以及CPU性能

发表于 2021-05-16 | 分类于 CPU

Perf IPC以及CPU性能

为了让程序能快点,特意了解了CPU的各种原理,比如多核、超线程、NUMA、睿频、功耗、GPU、大小核再到分支预测、cache_line失效、加锁代价、IPC等各种指标(都有对应的代码和测试数据)都会在这系列文章中得到答案。当然一定会有程序员最关心的分支预测案例、Disruptor无锁案例、cache_line伪共享案例等等。

这次让我们从最底层的沙子开始用8篇文章来回答各种疑问以及大量的实验对比案例和测试数据。

大的方面主要是从这几个疑问来写这些文章:

  • 同样程序为什么CPU跑到800%还不如CPU跑到200%快?
  • IPC背后的原理和和程序效率的关系?
  • 为什么数据库领域都爱把NUMA关了,这对吗?
  • 几个国产芯片的性能到底怎么样?

系列文章

CPU的制造和概念

[Perf IPC以及CPU性能](/2021/05/16/Perf IPC以及CPU利用率/)

CPU性能和CACHE

[CPU 性能和Cache Line](/2021/05/16/CPU Cache Line 和性能/)

十年后数据库还是不敢拥抱NUMA?

[Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的](/2019/12/16/Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的/)

Intel、海光、鲲鹏920、飞腾2500 CPU性能对比

一次海光物理机资源竞争压测的记录

飞腾ARM芯片(FT2500)的性能测试

image-20210802161455950

程序性能

程序的 CPU 执行时间 = 指令数/(主频*IPC)

IPC: insns per cycle insn/cycles

CPU 流水线工作原理

cycles:CPU时钟周期。CPU从它的指令集(instruction set)中选择指令执行。

一个指令包含以下的步骤,每个步骤由CPU的一个叫做功能单元(functional unit)的组件来进行处理,每个步骤的执行都至少需要花费一个时钟周期。

  • 指令读取(instruction fetch, IF)
    
  • 指令解码(instruction decode, ID)
    
  • 执行(execute, EXE)
    
  • 内存访问(memory access,MEM)
    
  • 寄存器回写(register write-back, WB)
    

skylake server block diagram.svg

以上结构简化成流水线就是:

image-20210511154816751

IF/ID 就是我们常说的前端,他负责不停地取指和译指,然后为后端提供译指之后的指令,最核心的优化就是要做好分支预测,终归取指是要比执行慢,只有提前做好预测才能尽量匹配上后端。后端核心优化是要做好执行单元的并发量,以及乱序执行能力,最终要将乱序执行结果正确组合并输出。

在流水线指令之前是单周期处理器:也就是一个周期完成一条指令。每个时钟周期必须完成取指、译码、读寄存器、 执行、访存等很多组合逻辑工作,为了保证在下一个时钟上升沿到来之前准备好寄存器堆的写数 据,需要将每个时钟周期的间隔拉长,导致处理器的主频无法提高。

使用流水线技术可以提高处 理器的主频。五个步骤只能串行,但是可以做成pipeline提升效率,也就是第一个指令做第二步的时候,指令读取单元可以去读取下一个指令了,如果有一个指令慢就会造成stall,也就是pipeline有地方卡壳了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$sudo perf stat -a -- sleep 10

Performance counter stats for 'system wide':

239866.330098 task-clock (msec) # 23.985 CPUs utilized /10*1000 (100.00%)
45,709 context-switches # 0.191 K/sec (100.00%)
1,715 cpu-migrations # 0.007 K/sec (100.00%)
79,586 page-faults # 0.332 K/sec
3,488,525,170 cycles # 0.015 GHz (83.34%)
9,708,140,897 stalled-cycles-frontend # 278.29% /cycles frontend cycles idle (83.34%)
9,314,891,615 stalled-cycles-backend # 267.02% /cycles backend cycles idle (66.68%)
2,292,955,367 instructions # 0.66 insns per cycle insn/cycles
# 4.23 stalled cycles per insn stalled-cycles-frontend/insn (83.34%)
447,584,805 branches # 1.866 M/sec (83.33%)
8,470,791 branch-misses # 1.89% of all branches (83.33%)

stalled-cycles,则是指令管道未能按理想状态发挥并行作用,发生停滞的时钟周期。stalled-cycles-frontend指指令读取或解码的指令步骤,而stalled-cycles-backend则是指令执行步骤。第二列中的cycles idle其实意思跟stalled是一样的,由于指令执行停滞了,所以指令管道也就空闲了,千万不要误解为CPU的空闲率。这个数值是由stalled-cycles-frontend或stalled-cycles-backend除以上面的cycles得出的。

另外cpu可以同时有多条pipeline,这就是理论上最大的IPC.

pipeline效率和IPC

虽然一个指令需要5个步骤,也就是完全执行完需要5个cycles,这样一个时钟周期最多能执行0.2条指令,IPC就是0.2,显然太低了。

  • 非流水线:

image-20210511154859711

如果把多个指令的五个步骤用pipeline流水线跑起来,在理想情况下一个时钟周期就能跑完一条指令了,这样IPC就能达到1了。

这种非流水线的方式将一个指令分解成多个步骤后,能提升主频,但是一个指令执行需要的时间基本没变

  • 标量流水线, 标量(Scalar)流水计算机是只有一条指令流水线的计算机:

image-20210511155530477

进一步优化,如果我们加大流水线的条数,让多个指令并行执行,就能得到更高的IPC了,但是这种并行必然会有指令之间的依赖,比如第二个指令依赖第一个的结果,所以多个指令并行更容易出现互相等待(stall).

img

在每个时钟周期的开始,指令的部分数据和控制信息都保存在流水线锁存器中,并且该信息形成了下一个流水线的逻辑电路输入。在时钟周期内,信号通过组合逻辑传播,从而在时钟周期结束时及时产生输出,以供下一个pipeline锁存器捕获。

早期的RISC处理器,例如IBM的801原型,MIPS R2000(基于斯坦福MIPS机器)和原始的SPARC(来自Berkeley RISC项目),都实现了一个简单的5阶段流水线,与上面所示的流水线不同( 额外的阶段是内存访问,在执行后存放结果)。在那个时代,主流的CISC架构80386、68030和VAX处理器使用微码顺序工作(通过RISC进行流水作业比较容易,因为指令都是简单的寄存器到寄存器操作,与x86、68k或VAX不同)。导致的结果,以20 MHz运行的SPARC比以33 MHz运行的386快得多。从那以后,每个处理器都至少在某种程度上进行了流水线处理。

img

  • 超标量流水线:所谓超标量(Superscalar)流 水计算机,是指它具有两条以上的指令流水线, 超标流水线数量也就是ALU执行单元的并行度

image-20210511155708234

一般而言流水线的超标量不能超过单条流水线的深度

每个功能单元都有独立的管道,甚至可以具有不同的长度。 这使更简单的指令可以更快地完成,从而减少了等待时间。 在各个管道内部之间也有许多旁路,但是为简单起见,这些旁路已被省略。

下图中,处理器可能每个周期执行3条不同的指令,例如,一个整数,一个浮点和一个存储器操作。 甚至可以添加更多的功能单元,以便处理器能够在每个周期执行两个整数指令,或两个浮点指令,或使用任何其他方式。

鲲鹏的流水线结构:

zh-cn_image_0000001237942853.png

三级流水线的执行容易被打断,导致指令执行效率低,后面发展起来的五级指令流水线技术被认为是经典的处理器设置方式,已经在多种RISC处理器中广泛使用,它在三级流水线(取指、译码、执行)的基础上,增加了两级处理,将“执行”动作进一步分解为执行、访存、回写,解决了三级流水线中存储器访问指令在指令执行阶段的延迟问题,但是容易出现寄存器互锁等问题导致流水线中断。鲲鹏920处理器采用八级流水线结构,首先是提取指令,然后通过解码、寄存器重命名和调度阶段。一旦完成调度,指令将无序发射到八个执行管道中的一个,每个执行管道每个周期都可以接受并完成一条指令,最后就是访存和回写操作。

img

流水线的设计可以实现不间断取指、解码、执行、写回,也可以同时做几条流水线一起取指、解码、执行、写回,也就引出了超标量设计。

超标量处理器可以在一个时钟周期内执行多个指令。需要注意的是,每个执行单元不是单独的处理器,而是单个CPU内(也可以理解成单core)的执行资源,在上面图中也由体现。

三路超标量四工位流水线的指令/周期,将CPI从1变成0.33,即每周期执行3.33条指令,这样的改进幅度实在是令人着迷的,因此在初期的时候超标量甚至被人们赞美为标量程序的向量式处理。

理想是丰满的,现实却是骨感的,现实中的CPI是不可能都这样的,因为现在的处理器执行不同指令时候的“执行”段的工位并不完全一样,例如整数可能短一些,浮点或者向量和 Load/Store 指令需要长一些(这也是为什么AVX512指令下,CPU会降频的原因,因为一个工位太费时间了,不得不降速,频率快了也没啥用),加上一些别的因素,实际大部分程序的实际 CPI 都是 1.x 甚至更高。

多发射分发逻辑的复杂性随着发射数量呈现平方和指数的变化。也就是说,5发射处理器的调度逻辑几乎是4发射设计的两倍,其中6发射是4倍,而7发射是8倍,依此类推。

流水线的实际效果

假如一个15级的流水线,如果处理器要将做无用功的时间限制在 10%,那么它必须在正确预测每个分支的准确率达到 99.3%(因为错误一次,15级流水线都要重置,所以错误会放大15倍,0.7*15=10) 。很少有通用程序能够如此准确地预测分支。

下图是不同场景在英特尔酷睿 i7 基准测试,可以看到有19% 的指令都被浪费了,但能耗的浪费情况更加严重,因为处理器必须利用额外的能量才能在推测失误时恢复原来的状态。这样的度量导致许多人得出结论,架构师需要一种不同的方法来实现性能改进。于是多级流水线不能疯狂增加,这样只能往多核发展。

f4.jpg

Deeper Pipelines深度流水线

由于时钟速度受流水线中最长阶段的长度的限制,因此每个级的逻辑门可以再细分,尤其是较长的逻辑门,从而将流水线转换为更深的深度流水线,各阶段的数量长度变小而阶段总数量变多,如下图。

img

​ 这样整个处理器可以更高的时钟速度运行。当然,每个指令将需要更多的周期来完成(等待时间),但是处理器仍将在每个周期中完成1条指令,这样每秒将有更多的周期,处理器每秒将完成更多的指令。

​ Alpha架构师尤其喜欢这个深度流水线,这也是为什么早期的Alpha拥有非常深的流水线,并且在那个时代以很高的时钟速度运行。 当然还有Intel的NetBurst架构,唯主频论。

​ 如今,现代处理器努力将每个流水线阶段的门延迟数量降低到很少(大约12-25个)。

​ 在PowerPC G4e中为7-12,在ARM11和Cortex-A9中为8+,在Athlon中为10-15,在Pentium-Pro/II/III/M中为12+,在Athlon64/Phenom/Fusion-A中为12-17,在Cortex-A8中为13+,在UltraSPARC-III/IV中为14,在Core 2中为14+,在Core i*2中为14-18+,在Core i中为16+,在PowerPC G5中为16-25,在Pentium-4中为20+, 在奔腾4E中为31+。 与RISC相比,x86处理器通常具有更深的流水线,因为它们需要做更多的工作来解码复杂的x86指令。UltraSPARC-T1/T2/T3是深度流水线趋势的例外(UltraSPARC-T1仅6个,T2/T3是8-12,因为其倾向让单核简化的方式来堆叠核数量)。

​ 不同架构的CPU流水线的级数(长度)存在很大差异,从几级到几十级不等,流水线级数越多,CPU结构就越复杂,功能也就越强大,同时功耗也会越大。相反地,流水线级数少,CPU结构简单,功耗就会降低很多。下表是一些典型的ARM流水线级别。

例如 Cortex-A15、Sandy Bridge 都分别具备 15 级、14 级流水线,而 Intel NetBurst(Pentium 4)、AMD Bulldozer 都是 20 级流水线,它们的工位数都远超出基本的四(或者五)工位流水线设计。更长的流水线虽然能提高频率,但是代价是耗电更高而且可能会有各种性能惩罚。

ARM指令集以及对应的流水线

型号 指令集 流水线
ARM7 ARMv4 3级
ARM9 ARMv5 5级
ARM11 ARMv6 8级
Cortex-A8 ARMv7-A 13级
鲲鹏920/Cortex-A55 ARMv8 8级

流水线越长带来的问题:

  • 每一级流水线之间需要流水线寄存器暂存数据,存取需要额外的负担
  • 功耗高
  • 对分支预测不友好

指令延时

​ 考虑一个非流水线机器,具有6个执行阶段,长度分别为50 ns,50 ns,60 ns,60 ns,50 ns和50 ns。

​ -这台机器上的指令等待时间是多少?

​ -执行100条指令需要多少时间?

​ 指令等待时间 = 50+50+60+60+50+50= 320 ns
​ 执行100条指令需 = 100*320 = 32000 ns

对比流水线延时

​ 假设在上面这台机器上引入了流水线技术,但引入流水线技术时,时钟偏移会给每个执行阶段增加5ns的开销。

​ -流水线机器上的指令等待时间是多少?

​ -执行100条指令需要多少时间?

​ 这里需要注意的是,在流水线实现中,流水线级的长度必须全部相同,即最慢级的速度加上开销,开销为5ns。

​ 流水线级的长度= MAX(非流水线级的长度)+开销= 60 + 5 = 65 ns

​ 指令等待时间= 65 ns

​ 执行100条指令的时间= 65 * 6 * 1 + 65 * 1 * 99 = 390 + 6435 = 6825 ns

保留站和乱序执行

指令在做完取码、译码后一般先交由一个指令保留站,统一交给后面的多个执行单元(多发射),执行完后再次将结果排序就行,有依赖关系的需要等待。

保留站后面就是乱序执行技术,就好像在指令的执行阶段提供一个“线程池”。指令不再是顺序执行的,而是根据池里所拥有的资源,以及各个任务是否可以进行执行,进行动态调度。在执行完成之后,又重新把结果在一个队列里面,按照指令的分发顺序重新排序。即使内部是“乱序”的,但是在外部看起来,仍然是井井有条地顺序执行。

image-20221102161222519

从流水线获得加速

加速是没有流水线的平均指令时间与有流水线的平均指令时间之比。(这里不考虑由不同类型的危害引起的任何失速)

假设:

​ 未流水线的平均指令时间= 320 ns

​ 流水线的平均指令时间= 65 ns

​ 那么,100条指令的加速= 32000/6825 = 4.69,这种理想情况下效率提升了4.69倍。

每一个功能单元的流水线的长度是不同的。事实上,不同的功能单元的流水线长度本来就不一样。我们平时所说的 14 级流水线,指的通常是进行整数计算指令的流水线长度。如果是浮点数运算,实际的流水线长度则会更长一些。

img

指令缓存(Instruction Cache)和数据缓存(Data Cache)

在第 1 条指令执行到访存(MEM)阶段的时候,流水线里的第 4 条指令,在执行取指令(Fetch)的操作。访存和取指令,都要进行内存数据的读取。我们的内存,只有一个地址译码器的作为地址输入,那就只能在一个时钟周期里面读取一条数据,没办法同时执行第 1 条指令的读取内存数据和第 4 条指令的读取指令代码。

img

把内存拆成两部分的解决方案,在计算机体系结构里叫作哈佛架构(Harvard Architecture),来自哈佛大学设计Mark I 型计算机时候的设计。我们今天使用的 CPU,仍然是冯·诺依曼体系结构的,并没有把内存拆成程序内存和数据内存这两部分。因为如果那样拆的话,对程序指令和数据需要的内存空间,我们就没有办法根据实际的应用去动态分配了。虽然解决了资源冲突的问题,但是也失去了灵活性。

img

在流水线产生依赖的时候必须pipeline stall,也就是让依赖的指令执行NOP。

Intel X86每个指令需要的cycle

Intel xeon

img

不同架构带来IPC变化:

img

Intel 最新的CPU Ice Lake和其上一代的性能对比数据:

img

上图最终结果导致了IPC提升了20%,以及整体效率的提升:

img

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流水线分支预测的时候默认走可能性更大的分支。如果分支预测错误所有流水线都要取消重新计算。

编译器的分支预测和CPU内部流水线分支预测是两个维度,编译器的分支预测主要是为了充分利用 ICache, 尽量让每一次ICache load效率更高;CPU的流水线分支预测是在译码以后就做的,也就是说分支预测的结果下一条指令的取址之间几乎是无缝的,这中间没有stall,代价就是错误预测

perf 使用

主要是通过采集 PMU(Performance Monitoring Unit – CPU内部提供)数据来做性能监控

img

Perf 是一个包含 22 种子工具的工具集,每个工具分别作为一个子命令。

annotate 命令读取 perf.data 并显示注释过的代码;diff 命令读取两个 perf.data 文件并显示两份剖析信息之间的差异;

evlist 命令列出一个 perf.data 文件的事件名称;

inject 命令过滤以加强事件流,在其中加入额外的信 息;

kmem 命令为跟踪和测量内核中 slab 子系统属性的工具;

kvm 命令为跟踪和测量 kvm 客户机操 作系统的工具;

list 命令列出所有符号事件类型;

lock 命令分析锁事件;

probe 命令定义新的动态跟 踪点;

record 命令运行一个程序,并把剖析信息记录在 perf.data 中;

report 命令读取 perf.data 并显 示剖析信息;

sched 命令为跟踪和测量内核调度器属性的工具;

script 命令读取 perf.data 并显示跟踪 输出;

stat 命令运行一个程序并收集性能计数器统计信息;

timechart 命令为可视化某个负载在某时 间段的系统总体性能的工具;

top 命令为系统剖析工具。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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
sudo perf record -g -a -e skb:kfree_skb //perf 记录丢包调用栈 然后sudo perf script 查看 (网络报文被丢弃时会调用该函数kfree_skb)
perf record -e 'skb:consume_skb' -ag //记录网络消耗
perf probe --add tcp_sendmsg //增加监听probe perf record -e probe:tcp_sendmsg -aR sleep 1
sudo perf sched record -- sleep 1 //记录cpu调度的延时
sudo perf sched latency //查看

perf sched latency --sort max //查看上一步记录的结果,以调度延迟排序。

perf record --call-graph dwarf
perf report
perf report --call-graph -G //反转调用关系


展开汇编结果

占比 行号 指令
│ mov %r13,%rax
│ mov %r8,%rbx
0.56 │ mov %r9,%rcx
0.19 │ lock cmpxchg16b 0x10(%rsi) //加锁占89.53,下一行
89.53 │ sete %al
1.50 │ mov %al,%r13b
0.19 │ mov $0x1,%al
│ test %r13b,%r13b
│ ↓ je eb
│ ↓ jmpq ef
│47: mov %r9,(%rsp)


//如下代码的汇编
void main() {

while(1) {
__asm__ ("pause\n\t"
"pause\n\t"
"pause\n\t"
"pause\n\t"
"pause");
}
}

//每行pause占20%
│
│ Disassembly of section .text:
│
│ 00000000004004ed <main>:
│ main():
│ push %rbp
│ mov %rsp,%rbp
0.71 │ 4: pause
19.35 │ pause
20.20 │ pause
19.81 │ pause
19.88 │ pause
20.04 │ ↑ jmp 4

网络收包软中断

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
_raw_spin_lock_irqsave  /proc/kcore
│ Disassembly of section load2:
│
│ ffffffff81662b00 <load2+0x662b00>:
0.30 │ nop
│ push %rbp
0.21 │ mov %rsp,%rbp
0.15 │ push %rbx
0.12 │ pushfq
0.57 │ pop %rax
0.45 │ nop
0.15 │ mov %rax,%rbx
0.21 │ cli
1.20 │ nop
│ mov $0x20000,%edx
│ lock xadd %edx,(%rdi) //加锁耗时83%
83.42 │ mov %edx,%ecx
│ shr $0x10,%ecx
0.66 │ cmp %dx,%cx
│ ↓ jne 34
0.06 │2e: mov %rbx,%rax
│ pop %rbx
│ pop %rbp
0.57 │ ← retq
0.12 │34: mov %ecx,%r8d
0.03 │ movzwl %cx,%esi
│3a: mov $0x8000,%eax
│ ↓ jmp 4f
│ nop
0.06 │48: pause
4.67 │ sub $0x1,%eax
│ ↓ je 69
0.12 │4f: movzwl (%rdi),%edx //慢操作
6.73 │ mov %edx,%ecx
│ xor %r8d,%ecx
│ and $0xfffe,%ecx
│ ↑ jne 48
0.12 │ movzwl %dx,%esi
0.09 │ callq 0xffffffff8165501c
│ ↑ jmp 2e
│69: nop
│ ↑ jmp 3a

可以通过perf看到cpu的使用情况:

$sudo perf stat -a -- sleep 10

Performance counter stats for 'system wide':

 239866.330098      task-clock (msec)         #   23.985 CPUs utilized    /10*1000        (100.00%)
        45,709      context-switches          #    0.191 K/sec                    (100.00%)
         1,715      cpu-migrations            #    0.007 K/sec                    (100.00%)
        79,586      page-faults               #    0.332 K/sec
 3,488,525,170      cycles                    #    0.015 GHz                      (83.34%)
 9,708,140,897      stalled-cycles-frontend   #  278.29% /cycles frontend cycles idle     (83.34%)
 9,314,891,615      stalled-cycles-backend    #  267.02% /cycles backend  cycles idle     (66.68%)
 2,292,955,367      instructions              #    0.66  insns per cycle  insn/cycles
                                             #    4.23  stalled cycles per insn stalled-cycles-frontend/insn (83.34%)
   447,584,805      branches                  #    1.866 M/sec                    (83.33%)
     8,470,791      branch-misses             #    1.89% of all branches          (83.33%)

image.png

IPC测试

实际运行的时候增加如下nop到100个以上

1
2
3
4
5
6
7
void main() {
while(1) {
__asm__ ("nop\n\t"
"nop\n\t"
"nop");
}
}

如果同时运行两个如上测试程序,鲲鹏920运行,每个程序的IPC都是3.99

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#perf stat -- ./nop.out
failed to read counter branches

Performance counter stats for './nop.out':

8826.948260 task-clock (msec) # 1.000 CPUs utilized
8 context-switches # 0.001 K/sec
0 cpu-migrations # 0.000 K/sec
37 page-faults # 0.004 K/sec
22,949,862,030 cycles # 2.600 GHz
2,099,719 stalled-cycles-frontend # 0.01% frontend cycles idle
18,859,839 stalled-cycles-backend # 0.08% backend cycles idle
91,465,043,922 instructions # 3.99 insns per cycle
# 0.00 stalled cycles per insn
<not supported> branches
33,262 branch-misses # 0.00% of all branches

8.827886000 seconds time elapsed

intel X86 8260

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#perf stat -- ./nop.out

Performance counter stats for './nop.out':

65061.160345 task-clock (msec) # 1.001 CPUs utilized
46 context-switches # 0.001 K/sec
92 cpu-migrations # 0.001 K/sec
108 page-faults # 0.002 K/sec
155,659,827,263 cycles # 2.393 GHz
<not supported> stalled-cycles-frontend
<not supported> stalled-cycles-backend
603,247,401,995 instructions # 3.88 insns per cycle
4,742,051,659 branches # 72.886 M/sec
1,799,428 branch-misses # 0.04% of all branches

65.012821629 seconds time elapsed

这两块CPU理论IPC最大值都是4,实际x86离理论值更远一些. 增加while循环中的nop数量(从132增加到432个)IPC能提升到3.92

IPC和超线程

ipc是指每个core的IPC

超线程(Hyper-Threading)原理

概念:一个核还可以进一步分成几个逻辑核,来执行多个控制流程,这样可以进一步提高并行程度,这一技术就叫超线程,intel体系下也叫做 simultaneous multi-threading(SMT–wiki用的是simultaneous,也有人用 symmetric(29页),我觉得symmetric也比较能表达超线程的意思)。

Two logical cores can work through tasks more efficiently than a traditional single-threaded core. By taking advantage of idle time when the core would formerly be waiting for other tasks to complete, Intel® Hyper-Threading Technology improves CPU throughput (by up to 30% in server applications).

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

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

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

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

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

超线程如何查看

如果physical id和core id都一样的话,说明这两个core实际是一个物理core,其中一个是HT。

image.png

physical id对应socket,也就是物理上购买到的一块CPU; core id对应着每个物理CPU里面的一个物理core,同一个phyiscal id下core id一样说明开了HT

IPC和超线程的关系

IPC 和一个core上运行多少个进程没有关系。实际测试将两个运行nop指令的进程绑定到一个core上,IPC不变, 因为IPC就是该进程分到的circle里执行了多少个指令,只和进程业务逻辑相关。但是如果是这两个进程绑定到一个物理core以及对应的超线程core上那么IPC就会减半。如果程序是IO bound(比如需要频繁读写内存)首先IPC远远低于理论值4的,这个时候超线程同时工作的话IPC基本能翻倍

image-20210513123233344

对应的CPU使用率, 两个进程的CPU使用率是200%,实际产出IPC是2.1+1.64=3.75,比单个进程的IPC为3.92小多了。而单个进程CPU使用率才100%

image-20210513130252565

以上测试CPU为Intel(R) Xeon(R) Platinum 8260 CPU @ 2.40GHz (Thread(s) per core: 2)

再来看如下CPU上,0和64核是一对HT,单独跑nop、Pause指令的IPC分别是5/0.17(nop是一条完全不会卡顿的指令),可以得到这款CPU的最高IPC是5,一条 Pause 指令需要28-30个时钟周期。

如果在0/64上同时跑两个nop指令,虽然是两个超线程得到的IPC只有5的一半,也就是超线程在这种完全不卡顿的 nop 指令上完全没用;另外对比在0/64上同时跑两个Pause 指令,IPC 都还是0.17,也就是 Pause 指令完全可以将一个物理核发挥出两倍的运算能力

image-20221108095422200

Pause指令和nop指令同时跑在一对HT上,nop基本不受影响,Pause降得非常低

image-20221108094802841

Pause指令和nop指令同时跑在一个核上,IPC 倒是各自保持不变,但是抢到的 CPU 配额相当于各自 50%(在自己的50%范围内独占,IPC也不受影响)

image-20221108095753861

关掉如上CPU的超线程,从测试结果看海光如果开了超线程 Pause 是28个时钟周期,关掉超线程 Pause 是14个时钟周期

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
//关掉超线程后 Pause 的IPC 从0.17提升到了0.34
#perf stat taskset -c 0 ./pause
^Ctaskset: Interrupt

Performance counter stats for 'taskset -c 0 ./pause':

3,190.28 msec task-clock # 0.999 CPUs utilized
302 context-switches # 0.095 K/sec
1 cpu-migrations # 0.000 K/sec
99 page-faults # 0.031 K/sec
7,951,451,789 cycles # 2.492 GHz
1,337,801 stalled-cycles-frontend # 0.02% frontend cycles idle
7,842,812,091 stalled-cycles-backend # 98.63% backend cycles idle
2,671,280,445 instructions # 0.34 insn per cycle
# 2.94 stalled cycles per insn
21,917,856 branches # 6.870 M/sec
29,607 branch-misses # 0.14% of all branches

3.192937987 seconds time elapsed

3.190322000 seconds user
0.000000000 seconds sys


#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: 1070.009
CPU max MHz: 2200.0000
CPU min MHz: 1200.0000
BogoMIPS: 4399.40
Virtualization: AMD-V
L1d cache: 1.5 MiB
L1i cache: 3 MiB
L2 cache: 24 MiB
L3 cache: 128 MiB
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

image-20221108175100638

Intel和AMD单核以及HT性能比较

测试命令,这个测试命令无论在哪个CPU下,用2个物理核用时都是一个物理核的一半,所以这个计算是可以完全并行的

1
taskset -c 1,53 /usr/bin/sysbench --num-threads=2 --test=cpu --cpu-max-prime=50000 run //单核用一个threads,绑核, HT用2个threads,绑一对HT

测试结果为耗时,单位秒,Hygon 7280 就是Zen2架构

Family Name Intel 8269CY CPU @ 2.50GHz Intel E5-2682 v4 @ 2.50GHz Hygon 7280 2.1G
单核 prime 50000 83 109 89
HT prime 50000 48 74 87

流量一样但CPU使用率差别很大

同样大小内存、同样的cpu、同样的查询请求、同样的数据、几乎可以忽略的io,两个机器的load却表现异样。一个机器的load是12左右,另外一个机器却是30左右

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//load低、CPU使用率低 的物理机,省略一部分核
Cpu0 : 67.1%us, 1.6%sy, 0.0%ni, 30.6%id, 0.0%wa, 0.0%hi, 0.7%si, 0.0%st
Cpu1 : 64.1%us, 1.6%sy, 0.0%ni, 34.3%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu2 : 63.0%us, 1.6%sy, 0.0%ni, 35.4%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu3 : 60.0%us, 1.3%sy, 0.0%ni, 38.4%id, 0.0%wa, 0.0%hi, 0.3%si, 0.0%st
Cpu4 : 59.8%us, 1.3%sy, 0.0%ni, 37.9%id, 1.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu5 : 56.7%us, 1.0%sy, 0.0%ni, 42.3%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu6 : 63.4%us, 1.3%sy, 0.0%ni, 34.6%id, 0.0%wa, 0.0%hi, 0.7%si, 0.0%st
Cpu7 : 62.5%us, 2.0%sy, 0.0%ni, 35.5%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu8 : 58.5%us, 1.3%sy, 0.0%ni, 39.5%id, 0.0%wa, 0.0%hi, 0.7%si, 0.0%st
Cpu9 : 55.8%us, 1.6%sy, 0.0%ni, 42.2%id, 0.3%wa, 0.0%hi, 0.0%si, 0.0%st

//load高、CPU使用率高 的物理机,省略一部分核
Cpu0 : 90.1%us, 1.9%sy, 0.0%ni, 7.1%id, 0.0%wa, 0.0%hi, 1.0%si, 0.0%st
Cpu1 : 88.5%us, 2.9%sy, 0.0%ni, 8.0%id, 0.0%wa, 0.0%hi, 0.6%si, 0.0%st
Cpu2 : 90.4%us, 1.9%sy, 0.0%ni, 7.7%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Cpu3 : 86.9%us, 2.6%sy, 0.0%ni, 10.2%id, 0.0%wa, 0.0%hi, 0.3%si, 0.0%st
Cpu4 : 87.5%us, 1.9%sy, 0.0%ni, 10.2%id, 0.0%wa, 0.0%hi, 0.3%si, 0.0%st
Cpu5 : 87.3%us, 1.9%sy, 0.0%ni, 10.5%id, 0.0%wa, 0.0%hi, 0.3%si, 0.0%st
Cpu6 : 90.4%us, 2.9%sy, 0.0%ni, 6.4%id, 0.0%wa, 0.0%hi, 0.3%si, 0.0%st
Cpu7 : 90.1%us, 1.9%sy, 0.0%ni, 7.6%id, 0.0%wa, 0.0%hi, 0.3%si, 0.0%st
Cpu8 : 89.5%us, 2.6%sy, 0.0%ni, 6.7%id, 0.0%wa, 0.0%hi, 1.3%si, 0.0%st
Cpu9 : 90.7%us, 1.9%sy, 0.0%ni, 7.4%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st

也就是相同CPU使用率下,其中慢的机器产出低了一半。可以分析产出为什么低,检查CPU是否降频、内存频率是否有差异。检查结果一致,那么继续通过perf看IPC:

img

可以看到两台机器的IPC是 0.3 VS 0.55,和CPU使用率差异基本一致,instructions几乎一样(流量一样),但是使用掉的cpu-clock 几乎差了一倍,这应该是典型的内存时延大了一倍导致的。

经检查这两台物理机都是两路,虽然CPU型号一致,但是主板间跨Socket的QPI带宽差了一倍(主板是两个不同的服务商提供)。可以通过绑核测试不同Socket/Node 下内存时延来确认这个问题

主频和性价比

拿Intel 在数据中心计算的大核CPU IvyBridge与当时用于 存储系列的小核CPU Avoton(ATOM), 分别测试阿里巴巴(Oceanbase ,MySQL, ODPS)的workload,得到性能吞吐如下:

Intel 大小CPU 核心 阿里 Workload Output(QPS)

Avoton(8 cores) 2.4GHZ 10K on single core

Ivy Bridge(2650 v2 disable HT) 2.6GHZ 20K on single core

Ivy Bridge(2650 v2 enable HT) 2.4GHZ 25K on single core

Ivy Bridge(2650 v2 enable HT) 2.6GHZ 27K on single core

  1. 超线程等于将一个大核CPU 分拆成两个小核,Ivy Bridge的数据显示超线程给 Ivy Bridge 1.35倍(27K/20K) 的提升
  2. 现在我们分别评判 两种CPU对应的性能密度 (performance/core die size) ,该数据越大越好,根据我们的计算和测量发现:Avoton(包含L1D, L1I, and L2 per core)大约是 34平方毫米,Ivy Bridge (包含L1D, L1I, L2 )大约是1213平方毫米, L3/core是 6~7平方毫米, 所以 Ivy Bridge 单核心的芯片面积需要18 ~ 20平方毫米。基于上面的数据我们得到的 Avoton core的性能密度为 2.5 (10K/4sqmm),而Ivy Bridge的性能密度是1.35 (27K/20sqmm),因此相同的芯片面积下 Avoton 的性能是 Ivy Bridge的 1.85倍(2.5/1.35).
  3. 从功耗的角度看性能的提升的对比数据,E5-2650v2(Ivy Bridge) 8core TDP 90w, Avoton 8 core TDP 20瓦, 性能/功耗 Avoton 是 10K QPS/20瓦, Ivy Bridge是 27KQPS/90瓦, 因此 相同的功耗下 Avoton是 Ivy Bridge的 1.75倍(10K QPS/20)/ (27KQPS/95)
  4. 从价格方面再进行比较,E5-2650v2(Ivy Bridge) 8core 官方价格是1107美元, Avoton 8 core官方价格是171美元。性能/价格 Avoton是 10KQPS/171美元,Ivy Bridge 是 27KQPS/1107美元, 因此相同的美元 Avoton的性能是 Ivy Bridge 的2.3倍(1 10KQPS/171美元)/ (27KQPS/1107美元)

从以上结论可以看到在数据中心的场景下,由于指令数据相关性较高,同时由于内存访问的延迟更多,因此复杂的CPU体系结构并不能获得相应性能提升,该原因导致我们需要的是更多的小核CPU,以此达到高吞吐量的能力,因此2014年我们向Intel提出需要将物理CPU的超线程由 2个升级到4个/8个, 或者直接将用更多的小核CPU增加服务器的吞吐能力,最新数据表明Intel 会在大核CPU中引入4个超线程,和在相同的芯片面积下引入更多的小核CPU。

预测:为了减少数据中心的功耗,我们需要提升单位面积下的计算密度,因此将来会引入Rack Computing的计算模式,每台服务器将会有4~5百个CPU core,如果使用4个CPU socket,每台机器将会达到~1000个CPU core,结合Compute Express Link (CXL), 一个机架内允许16台服务器情况下,可以引入共享内存,那么一个进程可以运行在上万个CPU core中,这样复杂环境下,我们需要对于这样的软件环境做出更多的布局和优化。

perf top 和 pause 的案例

在Skylake的架构中,将pause由10个时钟周期增加到了140个时钟周期。主要用在spin lock当中因为spin loop 多线程竞争差生的内存乱序而引起的性能下降。pause的时钟周期高过了绝大多数的指令cpu cycles,那么当我们利用perf top统计cpu 性能的时候,pause会有什么影响呢?我们可以利用一段小程序来测试一下.

测试机器:
CPU: Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz * 2, 共96个超线程

案例:

image.png

对如上两个pause指令以及一个 count++(addq),进行perf top:

image.png

可以看到第一个pasue在perf top中cycles为0,第二个为46.85%,另外一个addq也有48.83%,基本可以猜测perf top在这里数据都往后挪了一个。

问题总结:
我们知道perf top是通过读取PMU的PC寄存器来获取当前执行的指令进而根据汇编的symbol信息获得是执行的哪条指令。所以看起来CPU在执行pause指令的时候,从PMU中看到的PC值指向到了下一条指令,进而导致我们看到的这个现象。通过查阅《Intel® 64 and IA-32 Architectures Optimization Reference Manual》目前还无法得知这是CPU的一个设计缺陷还是PMU的一个bug(需要对pause指令做特殊处理)。不管怎样,这个实验证明了我们统计spin lock的CPU占比还是准确的,不会因为pause指令导致PMU采样出错导致统计信息的整体失真。只是对于指令级的CPU统计,我们能确定的就是它把pause的执行cycles 数统计到了下一条指令。

补充说明: 经过测试,非skylake CPU也同样存在perf top会把pause(执行数cycles是10)的执行cycles数统计到下一条指令的问题,看来这是X86架构都存在的问题。

perf 和火焰图

调用 perf record 采样几秒钟,一般需要加 -g 参数,也就是 call-graph,还需要抓取函数的调用关系。在多核的机器上,还要记得加上 -a 参数,保证获取所有 CPU Core 上的函数运行情况。至于采样数据的多少,在讲解 perf 概念的时候说过,我们可以用 -c 或者 -F 参数来控制。

   83  07/08/19 13:56:26 sudo perf record -ag -p 4759
   84  07/08/19 13:56:50 ls /tmp/
   85  07/08/19 13:57:06 history |tail -16
   86  07/08/19 13:57:20 sudo chmod 777 perf.data
   87  07/08/19 13:57:33 perf script >out.perf
   88  07/08/19 13:59:24 ~/tools/FlameGraph-master/./stackcollapse-perf.pl ~/out.perf >out.folded
   89  07/08/19 14:01:01 ~/tools/FlameGraph-master/flamegraph.pl out.folded > kernel-perf.svg
   90  07/08/19 14:01:07 ls -lh
   91  07/08/19 14:03:33 history


$ sudo perf record -F 99 -a -g -- sleep 60 //-F 99 指采样每秒钟做 99 次

  执行这个命令将生成一个 perf.data 文件:

执行sudo perf report -n可以生成报告的预览。
执行sudo perf report -n –stdio可以生成一个详细的报告。
执行sudo perf script可以 dump 出 perf.data 的内容。

# 折叠调用栈
$ FlameGraph/stackcollapse-perf.pl out.perf > out.folded
# 生成火焰图
$ FlameGraph/flamegraph.pl out.folded > out.svg

ECS和perf

在ECS会采集不到 cycles等,cpu-clock、page-faults都是内核中的软事件,cycles/instructions得采集cpu的PMU数据,ECS采集不到这些PMU数据。

image.png

Perf 和 false share cache_line

从4.2kernel开始,perf支持perf c2c (cache 2 cahce) 来监控cache_line的伪共享

系列文章

CPU的制造和概念

[CPU 性能和Cache Line](/2021/05/16/CPU Cache Line 和性能/)

[Perf IPC以及CPU性能](/2021/05/16/Perf IPC以及CPU利用率/)

Intel、海光、鲲鹏920、飞腾2500 CPU性能对比

飞腾ARM芯片(FT2500)的性能测试

十年后数据库还是不敢拥抱NUMA?

一次海光物理机资源竞争压测的记录

[Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的](/2019/12/16/Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的/)

参考资料

perf详解

CPU体系结构

震惊,用了这么多年的 CPU 利用率,其实是错的cpu占用不代表在做事情,可能是stalled,也就是流水线卡顿,但是cpu占用了,实际没事情做。

CPU Utilization is Wrong

震惊,用了这么多年的 CPU 利用率,其实是错的

https://kernel.taobao.org/2019/03/Top-down-Microarchitecture-Analysis-Method/

What Every Programmer Should Know About Main Memory by Ulrich Drepper

How fast are Linux pipes anyway? 优化 pipes 的读写带宽,perf、hugepage、splice使用

Perf IPC以及CPU性能

发表于 2021-05-16 | 分类于 CPU

Perf IPC以及CPU性能

为了让程序能快点,特意了解了CPU的各种原理,比如多核、超线程、NUMA、睿频、功耗、GPU、大小核再到分支预测、cache_line失效、加锁代价、IPC等各种指标(都有对应的代码和测试数据)都会在这系列文章中得到答案。当然一定会有程序员最关心的分支预测案例、Disruptor无锁案例、cache_line伪共享案例等等。

这次让我们从最底层的沙子开始用8篇文章来回答各种疑问以及大量的实验对比案例和测试数据。

大的方面主要是从这几个疑问来写这些文章:

  • 同样程序为什么CPU跑到800%还不如CPU跑到200%快?
  • IPC背后的原理和和程序效率的关系?
  • 为什么数据库领域都爱把NUMA关了,这对吗?
  • 几个国产芯片的性能到底怎么样?

系列文章

CPU的制造和概念

[Perf IPC以及CPU性能](/2021/05/16/Perf IPC以及CPU利用率/)

CPU性能和CACHE

[CPU 性能和Cache Line](/2021/05/16/CPU Cache Line 和性能/)

十年后数据库还是不敢拥抱NUMA?

[Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的](/2019/12/16/Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的/)

Intel、海光、鲲鹏920、飞腾2500 CPU性能对比

一次海光物理机资源竞争压测的记录

飞腾ARM芯片(FT2500)的性能测试

image-20210802161455950

程序性能

程序的 CPU 执行时间 = 指令数/(主频*IPC)

IPC: insns per cycle insn/cycles

CPU 流水线工作原理

cycles:CPU时钟周期。CPU从它的指令集(instruction set)中选择指令执行。

一个指令包含以下的步骤,每个步骤由CPU的一个叫做功能单元(functional unit)的组件来进行处理,每个步骤的执行都至少需要花费一个时钟周期。

  • 指令读取(instruction fetch, IF)
    
  • 指令解码(instruction decode, ID)
    
  • 执行(execute, EXE)
    
  • 内存访问(memory access,MEM)
    
  • 寄存器回写(register write-back, WB)
    

skylake server block diagram.svg

以上结构简化成流水线就是:

image-20210511154816751

IF/ID 就是我们常说的前端,他负责不停地取指和译指,然后为后端提供译指之后的指令,最核心的优化就是要做好分支预测,终归取指是要比执行慢,只有提前做好预测才能尽量匹配上后端。后端核心优化是要做好执行单元的并发量,以及乱序执行能力,最终要将乱序执行结果正确组合并输出。

在流水线指令之前是单周期处理器:也就是一个周期完成一条指令。每个时钟周期必须完成取指、译码、读寄存器、 执行、访存等很多组合逻辑工作,为了保证在下一个时钟上升沿到来之前准备好寄存器堆的写数 据,需要将每个时钟周期的间隔拉长,导致处理器的主频无法提高。

使用流水线技术可以提高处 理器的主频。五个步骤只能串行,但是可以做成pipeline提升效率,也就是第一个指令做第二步的时候,指令读取单元可以去读取下一个指令了,如果有一个指令慢就会造成stall,也就是pipeline有地方卡壳了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$sudo perf stat -a -- sleep 10

Performance counter stats for 'system wide':

239866.330098 task-clock (msec) # 23.985 CPUs utilized /10*1000 (100.00%)
45,709 context-switches # 0.191 K/sec (100.00%)
1,715 cpu-migrations # 0.007 K/sec (100.00%)
79,586 page-faults # 0.332 K/sec
3,488,525,170 cycles # 0.015 GHz (83.34%)
9,708,140,897 stalled-cycles-frontend # 278.29% /cycles frontend cycles idle (83.34%)
9,314,891,615 stalled-cycles-backend # 267.02% /cycles backend cycles idle (66.68%)
2,292,955,367 instructions # 0.66 insns per cycle insn/cycles
# 4.23 stalled cycles per insn stalled-cycles-frontend/insn (83.34%)
447,584,805 branches # 1.866 M/sec (83.33%)
8,470,791 branch-misses # 1.89% of all branches (83.33%)

stalled-cycles,则是指令管道未能按理想状态发挥并行作用,发生停滞的时钟周期。stalled-cycles-frontend指指令读取或解码的指令步骤,而stalled-cycles-backend则是指令执行步骤。第二列中的cycles idle其实意思跟stalled是一样的,由于指令执行停滞了,所以指令管道也就空闲了,千万不要误解为CPU的空闲率。这个数值是由stalled-cycles-frontend或stalled-cycles-backend除以上面的cycles得出的。

另外cpu可以同时有多条pipeline,这就是理论上最大的IPC.

pipeline效率和IPC

虽然一个指令需要5个步骤,也就是完全执行完需要5个cycles,这样一个时钟周期最多能执行0.2条指令,IPC就是0.2,显然太低了。

  • 非流水线:

image-20210511154859711

如果把多个指令的五个步骤用pipeline流水线跑起来,在理想情况下一个时钟周期就能跑完一条指令了,这样IPC就能达到1了。

这种非流水线的方式将一个指令分解成多个步骤后,能提升主频,但是一个指令执行需要的时间基本没变

  • 标量流水线, 标量(Scalar)流水计算机是只有一条指令流水线的计算机:

image-20210511155530477

进一步优化,如果我们加大流水线的条数,让多个指令并行执行,就能得到更高的IPC了,但是这种并行必然会有指令之间的依赖,比如第二个指令依赖第一个的结果,所以多个指令并行更容易出现互相等待(stall).

img

在每个时钟周期的开始,指令的部分数据和控制信息都保存在流水线锁存器中,并且该信息形成了下一个流水线的逻辑电路输入。在时钟周期内,信号通过组合逻辑传播,从而在时钟周期结束时及时产生输出,以供下一个pipeline锁存器捕获。

早期的RISC处理器,例如IBM的801原型,MIPS R2000(基于斯坦福MIPS机器)和原始的SPARC(来自Berkeley RISC项目),都实现了一个简单的5阶段流水线,与上面所示的流水线不同( 额外的阶段是内存访问,在执行后存放结果)。在那个时代,主流的CISC架构80386、68030和VAX处理器使用微码顺序工作(通过RISC进行流水作业比较容易,因为指令都是简单的寄存器到寄存器操作,与x86、68k或VAX不同)。导致的结果,以20 MHz运行的SPARC比以33 MHz运行的386快得多。从那以后,每个处理器都至少在某种程度上进行了流水线处理。

img

  • 超标量流水线:所谓超标量(Superscalar)流 水计算机,是指它具有两条以上的指令流水线, 超标流水线数量也就是ALU执行单元的并行度

image-20210511155708234

一般而言流水线的超标量不能超过单条流水线的深度

每个功能单元都有独立的管道,甚至可以具有不同的长度。 这使更简单的指令可以更快地完成,从而减少了等待时间。 在各个管道内部之间也有许多旁路,但是为简单起见,这些旁路已被省略。

下图中,处理器可能每个周期执行3条不同的指令,例如,一个整数,一个浮点和一个存储器操作。 甚至可以添加更多的功能单元,以便处理器能够在每个周期执行两个整数指令,或两个浮点指令,或使用任何其他方式。

鲲鹏的流水线结构:

zh-cn_image_0000001237942853.png

三级流水线的执行容易被打断,导致指令执行效率低,后面发展起来的五级指令流水线技术被认为是经典的处理器设置方式,已经在多种RISC处理器中广泛使用,它在三级流水线(取指、译码、执行)的基础上,增加了两级处理,将“执行”动作进一步分解为执行、访存、回写,解决了三级流水线中存储器访问指令在指令执行阶段的延迟问题,但是容易出现寄存器互锁等问题导致流水线中断。鲲鹏920处理器采用八级流水线结构,首先是提取指令,然后通过解码、寄存器重命名和调度阶段。一旦完成调度,指令将无序发射到八个执行管道中的一个,每个执行管道每个周期都可以接受并完成一条指令,最后就是访存和回写操作。

img

流水线的设计可以实现不间断取指、解码、执行、写回,也可以同时做几条流水线一起取指、解码、执行、写回,也就引出了超标量设计。

超标量处理器可以在一个时钟周期内执行多个指令。需要注意的是,每个执行单元不是单独的处理器,而是单个CPU内(也可以理解成单core)的执行资源,在上面图中也由体现。

三路超标量四工位流水线的指令/周期,将CPI从1变成0.33,即每周期执行3.33条指令,这样的改进幅度实在是令人着迷的,因此在初期的时候超标量甚至被人们赞美为标量程序的向量式处理。

理想是丰满的,现实却是骨感的,现实中的CPI是不可能都这样的,因为现在的处理器执行不同指令时候的“执行”段的工位并不完全一样,例如整数可能短一些,浮点或者向量和 Load/Store 指令需要长一些(这也是为什么AVX512指令下,CPU会降频的原因,因为一个工位太费时间了,不得不降速,频率快了也没啥用),加上一些别的因素,实际大部分程序的实际 CPI 都是 1.x 甚至更高。

多发射分发逻辑的复杂性随着发射数量呈现平方和指数的变化。也就是说,5发射处理器的调度逻辑几乎是4发射设计的两倍,其中6发射是4倍,而7发射是8倍,依此类推。

流水线案例

在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流水线分支预测的时候默认走可能性更大的分支。如果分支预测错误所有流水线都要取消重新计算。

流水线的实际效果

假如一个15级的流水线,如果处理器要将做无用功的时间限制在 10%,那么它必须在正确预测每个分支的准确率达到 99.3%(因为错误一次,15级流水线都要重置,所以错误会放大15倍,0.7*15=10) 。很少有通用程序能够如此准确地预测分支。

下图是不同场景在英特尔酷睿 i7 基准测试,可以看到有19% 的指令都被浪费了,但能耗的浪费情况更加严重,因为处理器必须利用额外的能量才能在推测失误时恢复原来的状态。这样的度量导致许多人得出结论,架构师需要一种不同的方法来实现性能改进。于是多级流水线不能疯狂增加,这样只能往多核发展。

f4.jpg

Deeper Pipelines深度流水线

由于时钟速度受流水线中最长阶段的长度的限制,因此每个级的逻辑门可以再细分,尤其是较长的逻辑门,从而将流水线转换为更深的深度流水线,各阶段的数量长度变小而阶段总数量变多,如下图。

img

​ 这样整个处理器可以更高的时钟速度运行。当然,每个指令将需要更多的周期来完成(等待时间),但是处理器仍将在每个周期中完成1条指令,这样每秒将有更多的周期,处理器每秒将完成更多的指令。

​ Alpha架构师尤其喜欢这个深度流水线,这也是为什么早期的Alpha拥有非常深的流水线,并且在那个时代以很高的时钟速度运行。 当然还有Intel的NetBurst架构,唯主频论。

​ 如今,现代处理器努力将每个流水线阶段的门延迟数量降低到很少(大约12-25个)。

​ 在PowerPC G4e中为7-12,在ARM11和Cortex-A9中为8+,在Athlon中为10-15,在Pentium-Pro/II/III/M中为12+,在Athlon64/Phenom/Fusion-A中为12-17,在Cortex-A8中为13+,在UltraSPARC-III/IV中为14,在Core 2中为14+,在Core i*2中为14-18+,在Core i中为16+,在PowerPC G5中为16-25,在Pentium-4中为20+, 在奔腾4E中为31+。 与RISC相比,x86处理器通常具有更深的流水线,因为它们需要做更多的工作来解码复杂的x86指令。UltraSPARC-T1/T2/T3是深度流水线趋势的例外(UltraSPARC-T1仅6个,T2/T3是8-12,因为其倾向让单核简化的方式来堆叠核数量)。

​ 不同架构的CPU流水线的级数(长度)存在很大差异,从几级到几十级不等,流水线级数越多,CPU结构就越复杂,功能也就越强大,同时功耗也会越大。相反地,流水线级数少,CPU结构简单,功耗就会降低很多。下表是一些典型的ARM流水线级别。

例如 Cortex-A15、Sandy Bridge 都分别具备 15 级、14 级流水线,而 Intel NetBurst(Pentium 4)、AMD Bulldozer 都是 20 级流水线,它们的工位数都远超出基本的四(或者五)工位流水线设计。更长的流水线虽然能提高频率,但是代价是耗电更高而且可能会有各种性能惩罚。

ARM指令集以及对应的流水线

型号 指令集 流水线
ARM7 ARMv4 3级
ARM9 ARMv5 5级
ARM11 ARMv6 8级
Cortex-A8 ARMv7-A 13级
鲲鹏920/Cortex-A55 ARMv8 8级

流水线越长带来的问题:

  • 每一级流水线之间需要流水线寄存器暂存数据,存取需要额外的负担
  • 功耗高
  • 对分支预测不友好

指令延时

​ 考虑一个非流水线机器,具有6个执行阶段,长度分别为50 ns,50 ns,60 ns,60 ns,50 ns和50 ns。

​ -这台机器上的指令等待时间是多少?

​ -执行100条指令需要多少时间?

​ 指令等待时间 = 50+50+60+60+50+50= 320 ns
​ 执行100条指令需 = 100*320 = 32000 ns

对比流水线延时

​ 假设在上面这台机器上引入了流水线技术,但引入流水线技术时,时钟偏移会给每个执行阶段增加5ns的开销。

​ -流水线机器上的指令等待时间是多少?

​ -执行100条指令需要多少时间?

​ 这里需要注意的是,在流水线实现中,流水线级的长度必须全部相同,即最慢级的速度加上开销,开销为5ns。

​ 流水线级的长度= MAX(非流水线级的长度)+开销= 60 + 5 = 65 ns

​ 指令等待时间= 65 ns

​ 执行100条指令的时间= 65 * 6 * 1 + 65 * 1 * 99 = 390 + 6435 = 6825 ns

保留站和乱序执行

指令在做完取码、译码后一般先交由一个指令保留站,统一交给后面的多个执行单元(多发射),执行完后再次将结果排序就行,有依赖关系的需要等待。

保留站后面就是乱序执行技术,就好像在指令的执行阶段提供一个“线程池”。指令不再是顺序执行的,而是根据池里所拥有的资源,以及各个任务是否可以进行执行,进行动态调度。在执行完成之后,又重新把结果在一个队列里面,按照指令的分发顺序重新排序。即使内部是“乱序”的,但是在外部看起来,仍然是井井有条地顺序执行。

image-20221102161222519

从流水线获得加速

加速是没有流水线的平均指令时间与有流水线的平均指令时间之比。(这里不考虑由不同类型的危害引起的任何失速)

假设:

​ 未流水线的平均指令时间= 320 ns

​ 流水线的平均指令时间= 65 ns

​ 那么,100条指令的加速= 32000/6825 = 4.69,这种理想情况下效率提升了4.69倍。

每一个功能单元的流水线的长度是不同的。事实上,不同的功能单元的流水线长度本来就不一样。我们平时所说的 14 级流水线,指的通常是进行整数计算指令的流水线长度。如果是浮点数运算,实际的流水线长度则会更长一些。

img

指令缓存(Instruction Cache)和数据缓存(Data Cache)

在第 1 条指令执行到访存(MEM)阶段的时候,流水线里的第 4 条指令,在执行取指令(Fetch)的操作。访存和取指令,都要进行内存数据的读取。我们的内存,只有一个地址译码器的作为地址输入,那就只能在一个时钟周期里面读取一条数据,没办法同时执行第 1 条指令的读取内存数据和第 4 条指令的读取指令代码。

img

把内存拆成两部分的解决方案,在计算机体系结构里叫作哈佛架构(Harvard Architecture),来自哈佛大学设计Mark I 型计算机时候的设计。我们今天使用的 CPU,仍然是冯·诺依曼体系结构的,并没有把内存拆成程序内存和数据内存这两部分。因为如果那样拆的话,对程序指令和数据需要的内存空间,我们就没有办法根据实际的应用去动态分配了。虽然解决了资源冲突的问题,但是也失去了灵活性。

img

在流水线产生依赖的时候必须pipeline stall,也就是让依赖的指令执行NOP。

Intel X86每个指令需要的cycle

Intel xeon

img

不同架构带来IPC变化:

img

Intel 最新的CPU Ice Lake和其上一代的性能对比数据:

img

上图最终结果导致了IPC提升了20%,以及整体效率的提升:

img

perf 使用

主要是通过采集 PMU(Performance Monitoring Unit – CPU内部提供)数据来做性能监控

img

Perf 是一个包含 22 种子工具的工具集,每个工具分别作为一个子命令。

annotate 命令读取 perf.data 并显示注释过的代码;diff 命令读取两个 perf.data 文件并显示两份剖析信息之间的差异;

evlist 命令列出一个 perf.data 文件的事件名称;

inject 命令过滤以加强事件流,在其中加入额外的信 息;

kmem 命令为跟踪和测量内核中 slab 子系统属性的工具;

kvm 命令为跟踪和测量 kvm 客户机操 作系统的工具;

list 命令列出所有符号事件类型;

lock 命令分析锁事件;

probe 命令定义新的动态跟 踪点;

record 命令运行一个程序,并把剖析信息记录在 perf.data 中;

report 命令读取 perf.data 并显 示剖析信息;

sched 命令为跟踪和测量内核调度器属性的工具;

script 命令读取 perf.data 并显示跟踪 输出;

stat 命令运行一个程序并收集性能计数器统计信息;

timechart 命令为可视化某个负载在某时 间段的系统总体性能的工具;

top 命令为系统剖析工具。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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
sudo perf record -g -a -e skb:kfree_skb //perf 记录丢包调用栈 然后sudo perf script 查看 (网络报文被丢弃时会调用该函数kfree_skb)
perf record -e 'skb:consume_skb' -ag //记录网络消耗
perf probe --add tcp_sendmsg //增加监听probe perf record -e probe:tcp_sendmsg -aR sleep 1
sudo perf sched record -- sleep 1 //记录cpu调度的延时
sudo perf sched latency //查看

perf sched latency --sort max //查看上一步记录的结果,以调度延迟排序。

perf record --call-graph dwarf
perf report
perf report --call-graph -G //反转调用关系


展开汇编结果

占比 行号 指令
│ mov %r13,%rax
│ mov %r8,%rbx
0.56 │ mov %r9,%rcx
0.19 │ lock cmpxchg16b 0x10(%rsi) //加锁占89.53,下一行
89.53 │ sete %al
1.50 │ mov %al,%r13b
0.19 │ mov $0x1,%al
│ test %r13b,%r13b
│ ↓ je eb
│ ↓ jmpq ef
│47: mov %r9,(%rsp)


//如下代码的汇编
void main() {

while(1) {
__asm__ ("pause\n\t"
"pause\n\t"
"pause\n\t"
"pause\n\t"
"pause");
}
}

//每行pause占20%
│
│ Disassembly of section .text:
│
│ 00000000004004ed <main>:
│ main():
│ push %rbp
│ mov %rsp,%rbp
0.71 │ 4: pause
19.35 │ pause
20.20 │ pause
19.81 │ pause
19.88 │ pause
20.04 │ ↑ jmp 4

网络收包软中断

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
_raw_spin_lock_irqsave  /proc/kcore
│ Disassembly of section load2:
│
│ ffffffff81662b00 <load2+0x662b00>:
0.30 │ nop
│ push %rbp
0.21 │ mov %rsp,%rbp
0.15 │ push %rbx
0.12 │ pushfq
0.57 │ pop %rax
0.45 │ nop
0.15 │ mov %rax,%rbx
0.21 │ cli
1.20 │ nop
│ mov $0x20000,%edx
│ lock xadd %edx,(%rdi) //加锁耗时83%
83.42 │ mov %edx,%ecx
│ shr $0x10,%ecx
0.66 │ cmp %dx,%cx
│ ↓ jne 34
0.06 │2e: mov %rbx,%rax
│ pop %rbx
│ pop %rbp
0.57 │ ← retq
0.12 │34: mov %ecx,%r8d
0.03 │ movzwl %cx,%esi
│3a: mov $0x8000,%eax
│ ↓ jmp 4f
│ nop
0.06 │48: pause
4.67 │ sub $0x1,%eax
│ ↓ je 69
0.12 │4f: movzwl (%rdi),%edx //慢操作
6.73 │ mov %edx,%ecx
│ xor %r8d,%ecx
│ and $0xfffe,%ecx
│ ↑ jne 48
0.12 │ movzwl %dx,%esi
0.09 │ callq 0xffffffff8165501c
│ ↑ jmp 2e
│69: nop
│ ↑ jmp 3a

可以通过perf看到cpu的使用情况:

$sudo perf stat -a -- sleep 10

Performance counter stats for 'system wide':

 239866.330098      task-clock (msec)         #   23.985 CPUs utilized    /10*1000        (100.00%)
        45,709      context-switches          #    0.191 K/sec                    (100.00%)
         1,715      cpu-migrations            #    0.007 K/sec                    (100.00%)
        79,586      page-faults               #    0.332 K/sec
 3,488,525,170      cycles                    #    0.015 GHz                      (83.34%)
 9,708,140,897      stalled-cycles-frontend   #  278.29% /cycles frontend cycles idle     (83.34%)
 9,314,891,615      stalled-cycles-backend    #  267.02% /cycles backend  cycles idle     (66.68%)
 2,292,955,367      instructions              #    0.66  insns per cycle  insn/cycles
                                             #    4.23  stalled cycles per insn stalled-cycles-frontend/insn (83.34%)
   447,584,805      branches                  #    1.866 M/sec                    (83.33%)
     8,470,791      branch-misses             #    1.89% of all branches          (83.33%)

image.png

IPC测试

实际运行的时候增加如下nop到100个以上

1
2
3
4
5
6
7
void main() {
while(1) {
__asm__ ("nop\n\t"
"nop\n\t"
"nop");
}
}

如果同时运行两个如上测试程序,鲲鹏920运行,每个程序的IPC都是3.99

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#perf stat -- ./nop.out
failed to read counter branches

Performance counter stats for './nop.out':

8826.948260 task-clock (msec) # 1.000 CPUs utilized
8 context-switches # 0.001 K/sec
0 cpu-migrations # 0.000 K/sec
37 page-faults # 0.004 K/sec
22,949,862,030 cycles # 2.600 GHz
2,099,719 stalled-cycles-frontend # 0.01% frontend cycles idle
18,859,839 stalled-cycles-backend # 0.08% backend cycles idle
91,465,043,922 instructions # 3.99 insns per cycle
# 0.00 stalled cycles per insn
<not supported> branches
33,262 branch-misses # 0.00% of all branches

8.827886000 seconds time elapsed

intel X86 8260

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#perf stat -- ./nop.out

Performance counter stats for './nop.out':

65061.160345 task-clock (msec) # 1.001 CPUs utilized
46 context-switches # 0.001 K/sec
92 cpu-migrations # 0.001 K/sec
108 page-faults # 0.002 K/sec
155,659,827,263 cycles # 2.393 GHz
<not supported> stalled-cycles-frontend
<not supported> stalled-cycles-backend
603,247,401,995 instructions # 3.88 insns per cycle
4,742,051,659 branches # 72.886 M/sec
1,799,428 branch-misses # 0.04% of all branches

65.012821629 seconds time elapsed

这两块CPU理论IPC最大值都是4,实际x86离理论值更远一些. 增加while循环中的nop数量(从132增加到432个)IPC能提升到3.92

IPC和超线程

ipc是指每个core的IPC

超线程(Hyper-Threading)原理

概念:一个核还可以进一步分成几个逻辑核,来执行多个控制流程,这样可以进一步提高并行程度,这一技术就叫超线程,intel体系下也叫做 simultaneous multi-threading(SMT–wiki用的是simultaneous,也有人用 symmetric(29页),我觉得symmetric也比较能表达超线程的意思)。

Two logical cores can work through tasks more efficiently than a traditional single-threaded core. By taking advantage of idle time when the core would formerly be waiting for other tasks to complete, Intel® Hyper-Threading Technology improves CPU throughput (by up to 30% in server applications).

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

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

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

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

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

超线程如何查看

如果physical id和core id都一样的话,说明这两个core实际是一个物理core,其中一个是HT。

image.png

physical id对应socket,也就是物理上购买到的一块CPU; core id对应着每个物理CPU里面的一个物理core,同一个phyiscal id下core id一样说明开了HT

IPC和超线程的关系

IPC 和一个core上运行多少个进程没有关系。实际测试将两个运行nop指令的进程绑定到一个core上,IPC不变, 因为IPC就是该进程分到的circle里执行了多少个指令,只和进程业务逻辑相关。但是如果是这两个进程绑定到一个物理core以及对应的超线程core上那么IPC就会减半。如果程序是IO bound(比如需要频繁读写内存)首先IPC远远低于理论值4的,这个时候超线程同时工作的话IPC基本能翻倍

image-20210513123233344

对应的CPU使用率, 两个进程的CPU使用率是200%,实际产出IPC是2.1+1.64=3.75,比单个进程的IPC为3.92小多了。而单个进程CPU使用率才100%

image-20210513130252565

以上测试CPU为Intel(R) Xeon(R) Platinum 8260 CPU @ 2.40GHz (Thread(s) per core: 2)

再来看如下CPU上,0和64核是一对HT,单独跑nop、Pause指令的IPC分别是5/0.17(nop是一条完全不会卡顿的指令),可以得到这款CPU的最高IPC是5,一条 Pause 指令需要28-30个时钟周期。

如果在0/64上同时跑两个nop指令,虽然是两个超线程得到的IPC只有5的一半,也就是超线程在这种完全不卡顿的 nop 指令上完全没用;另外对比在0/64上同时跑两个Pause 指令,IPC 都还是0.17,也就是 Pause 指令完全可以将一个物理核发挥出两倍的运算能力

image-20221108095422200

Pause指令和nop指令同时跑在一对HT上,nop基本不受影响,Pause降得非常低

image-20221108094802841

Pause指令和nop指令同时跑在一个核上,IPC 倒是各自保持不变,但是抢到的 CPU 配额相当于各自 50%(在自己的50%范围内独占,IPC也不受影响)

image-20221108095753861

关掉如上CPU的超线程,从测试结果看海光如果开了超线程 Pause 是28个时钟周期,关掉超线程 Pause 是14个时钟周期

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
//关掉超线程后 Pause 的IPC 从0.17提升到了0.34
#perf stat taskset -c 0 ./pause
^Ctaskset: Interrupt

Performance counter stats for 'taskset -c 0 ./pause':

3,190.28 msec task-clock # 0.999 CPUs utilized
302 context-switches # 0.095 K/sec
1 cpu-migrations # 0.000 K/sec
99 page-faults # 0.031 K/sec
7,951,451,789 cycles # 2.492 GHz
1,337,801 stalled-cycles-frontend # 0.02% frontend cycles idle
7,842,812,091 stalled-cycles-backend # 98.63% backend cycles idle
2,671,280,445 instructions # 0.34 insn per cycle
# 2.94 stalled cycles per insn
21,917,856 branches # 6.870 M/sec
29,607 branch-misses # 0.14% of all branches

3.192937987 seconds time elapsed

3.190322000 seconds user
0.000000000 seconds sys


#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: 1070.009
CPU max MHz: 2200.0000
CPU min MHz: 1200.0000
BogoMIPS: 4399.40
Virtualization: AMD-V
L1d cache: 1.5 MiB
L1i cache: 3 MiB
L2 cache: 24 MiB
L3 cache: 128 MiB
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

image-20221108175100638

Intel和AMD单核以及HT性能比较

测试命令,这个测试命令无论在哪个CPU下,用2个物理核用时都是一个物理核的一半,所以这个计算是可以完全并行的

1
taskset -c 1,53 /usr/bin/sysbench --num-threads=2 --test=cpu --cpu-max-prime=50000 run //单核用一个threads,绑核, HT用2个threads,绑一对HT

测试结果为耗时,单位秒,Hygon 7280 就是Zen2架构

Family Name Intel 8269CY CPU @ 2.50GHz Intel E5-2682 v4 @ 2.50GHz Hygon 7280 2.1G
单核 prime 50000 83 109 89
HT prime 50000 48 74 87

主频和性价比

拿Intel 在数据中心计算的大核CPU IvyBridge与当时用于 存储系列的小核CPU Avoton(ATOM), 分别测试阿里巴巴(Oceanbase ,MySQL, ODPS)的workload,得到性能吞吐如下:

Intel 大小CPU 核心 阿里 Workload Output(QPS)

Avoton(8 cores) 2.4GHZ 10K on single core

Ivy Bridge(2650 v2 disable HT) 2.6GHZ 20K on single core

Ivy Bridge(2650 v2 enable HT) 2.4GHZ 25K on single core

Ivy Bridge(2650 v2 enable HT) 2.6GHZ 27K on single core

  1. 超线程等于将一个大核CPU 分拆成两个小核,Ivy Bridge的数据显示超线程给 Ivy Bridge 1.35倍(27K/20K) 的提升
  2. 现在我们分别评判 两种CPU对应的性能密度 (performance/core die size) ,该数据越大越好,根据我们的计算和测量发现:Avoton(包含L1D, L1I, and L2 per core)大约是 34平方毫米,Ivy Bridge (包含L1D, L1I, L2 )大约是1213平方毫米, L3/core是 6~7平方毫米, 所以 Ivy Bridge 单核心的芯片面积需要18 ~ 20平方毫米。基于上面的数据我们得到的 Avoton core的性能密度为 2.5 (10K/4sqmm),而Ivy Bridge的性能密度是1.35 (27K/20sqmm),因此相同的芯片面积下 Avoton 的性能是 Ivy Bridge的 1.85倍(2.5/1.35).
  3. 从功耗的角度看性能的提升的对比数据,E5-2650v2(Ivy Bridge) 8core TDP 90w, Avoton 8 core TDP 20瓦, 性能/功耗 Avoton 是 10K QPS/20瓦, Ivy Bridge是 27KQPS/90瓦, 因此 相同的功耗下 Avoton是 Ivy Bridge的 1.75倍(10K QPS/20)/ (27KQPS/95)
  4. 从价格方面再进行比较,E5-2650v2(Ivy Bridge) 8core 官方价格是1107美元, Avoton 8 core官方价格是171美元。性能/价格 Avoton是 10KQPS/171美元,Ivy Bridge 是 27KQPS/1107美元, 因此相同的美元 Avoton的性能是 Ivy Bridge 的2.3倍(1 10KQPS/171美元)/ (27KQPS/1107美元)

从以上结论可以看到在数据中心的场景下,由于指令数据相关性较高,同时由于内存访问的延迟更多,因此复杂的CPU体系结构并不能获得相应性能提升,该原因导致我们需要的是更多的小核CPU,以此达到高吞吐量的能力,因此2014年我们向Intel提出需要将物理CPU的超线程由 2个升级到4个/8个, 或者直接将用更多的小核CPU增加服务器的吞吐能力,最新数据表明Intel 会在大核CPU中引入4个超线程,和在相同的芯片面积下引入更多的小核CPU。

预测:为了减少数据中心的功耗,我们需要提升单位面积下的计算密度,因此将来会引入Rack Computing的计算模式,每台服务器将会有4~5百个CPU core,如果使用4个CPU socket,每台机器将会达到~1000个CPU core,结合Compute Express Link (CXL), 一个机架内允许16台服务器情况下,可以引入共享内存,那么一个进程可以运行在上万个CPU core中,这样复杂环境下,我们需要对于这样的软件环境做出更多的布局和优化。

perf top 和 pause 的案例

在Skylake的架构中,将pause由10个时钟周期增加到了140个时钟周期。主要用在spin lock当中因为spin loop 多线程竞争差生的内存乱序而引起的性能下降。pause的时钟周期高过了绝大多数的指令cpu cycles,那么当我们利用perf top统计cpu 性能的时候,pause会有什么影响呢?我们可以利用一段小程序来测试一下.

测试机器:
CPU: Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz * 2, 共96个超线程

案例:

image.png

对如上两个pause指令以及一个 count++(addq),进行perf top:

image.png

可以看到第一个pasue在perf top中cycles为0,第二个为46.85%,另外一个addq也有48.83%,基本可以猜测perf top在这里数据都往后挪了一个。

问题总结:
我们知道perf top是通过读取PMU的PC寄存器来获取当前执行的指令进而根据汇编的symbol信息获得是执行的哪条指令。所以看起来CPU在执行pause指令的时候,从PMU中看到的PC值指向到了下一条指令,进而导致我们看到的这个现象。通过查阅《Intel® 64 and IA-32 Architectures Optimization Reference Manual》目前还无法得知这是CPU的一个设计缺陷还是PMU的一个bug(需要对pause指令做特殊处理)。不管怎样,这个实验证明了我们统计spin lock的CPU占比还是准确的,不会因为pause指令导致PMU采样出错导致统计信息的整体失真。只是对于指令级的CPU统计,我们能确定的就是它把pause的执行cycles 数统计到了下一条指令。

补充说明: 经过测试,非skylake CPU也同样存在perf top会把pause(执行数cycles是10)的执行cycles数统计到下一条指令的问题,看来这是X86架构都存在的问题。

perf 和火焰图

调用 perf record 采样几秒钟,一般需要加 -g 参数,也就是 call-graph,还需要抓取函数的调用关系。在多核的机器上,还要记得加上 -a 参数,保证获取所有 CPU Core 上的函数运行情况。至于采样数据的多少,在讲解 perf 概念的时候说过,我们可以用 -c 或者 -F 参数来控制。

   83  07/08/19 13:56:26 sudo perf record -ag -p 4759
   84  07/08/19 13:56:50 ls /tmp/
   85  07/08/19 13:57:06 history |tail -16
   86  07/08/19 13:57:20 sudo chmod 777 perf.data
   87  07/08/19 13:57:33 perf script >out.perf
   88  07/08/19 13:59:24 ~/tools/FlameGraph-master/./stackcollapse-perf.pl ~/out.perf >out.folded
   89  07/08/19 14:01:01 ~/tools/FlameGraph-master/flamegraph.pl out.folded > kernel-perf.svg
   90  07/08/19 14:01:07 ls -lh
   91  07/08/19 14:03:33 history


$ sudo perf record -F 99 -a -g -- sleep 60 //-F 99 指采样每秒钟做 99 次

  执行这个命令将生成一个 perf.data 文件:

执行sudo perf report -n可以生成报告的预览。
执行sudo perf report -n –stdio可以生成一个详细的报告。
执行sudo perf script可以 dump 出 perf.data 的内容。

# 折叠调用栈
$ FlameGraph/stackcollapse-perf.pl out.perf > out.folded
# 生成火焰图
$ FlameGraph/flamegraph.pl out.folded > out.svg

ECS和perf

在ECS会采集不到 cycles等,cpu-clock、page-faults都是内核中的软事件,cycles/instructions得采集cpu的PMU数据,ECS采集不到这些PMU数据。

image.png

Perf 和 false share cache_line

从4.2kernel开始,perf支持perf c2c (cache 2 cahce) 来监控cache_line的伪共享

系列文章

CPU的制造和概念

[CPU 性能和Cache Line](/2021/05/16/CPU Cache Line 和性能/)

[Perf IPC以及CPU性能](/2021/05/16/Perf IPC以及CPU利用率/)

Intel、海光、鲲鹏920、飞腾2500 CPU性能对比

飞腾ARM芯片(FT2500)的性能测试

十年后数据库还是不敢拥抱NUMA?

一次海光物理机资源竞争压测的记录

[Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的](/2019/12/16/Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的/)

参考资料

perf详解

CPU体系结构

震惊,用了这么多年的 CPU 利用率,其实是错的cpu占用不代表在做事情,可能是stalled,也就是流水线卡顿,但是cpu占用了,实际没事情做。

CPU Utilization is Wrong

震惊,用了这么多年的 CPU 利用率,其实是错的

https://kernel.taobao.org/2019/03/Top-down-Microarchitecture-Analysis-Method/

What Every Programmer Should Know About Main Memory by Ulrich Drepper

How fast are Linux pipes anyway? 优化 pipes 的读写带宽,perf、hugepage、splice使用

飞腾ARM芯片-FT2500的性能测试

发表于 2021-05-15 | 分类于 CPU

飞腾ARM芯片-FT2500的性能测试

ARM

ARM公司最早是由赫尔曼·豪泽(Hermann Hauser)和工程师Chris Curry在1978年创立(早期全称是 Acorn RISC Machine),后来改名为现在的ARM公司(Advanced RISC Machine)

img

ARM 芯片厂家

查看厂家

#cat /proc/cpuinfo |grep implementer

CPU implementer : 0x70

#cat /sys/devices/system/cpu/cpu0/regs/identification/midr_el1
0x00000000701f6633 // 70 表示厂家

vendor id对应厂家

Vendor Name Vendor ID
ARM 0x41
Broadcom 0x42
Cavium 0x43
DigitalEquipment 0x44
HiSilicon 0x48
Infineon 0x49
Freescale 0x4D
NVIDIA 0x4E
APM 0x50
Qualcomm 0x51
Marvell 0x56
Intel 0x69
飞腾 0x70

飞腾ARM芯片介绍

飞腾处理器,又称银河飞腾处理器,是由中国人民解放军国防科学技术大学研制的一系列嵌入式数字信号处理器(DSP)和中央处理器(CPU)芯片。[1]这个处理器系列的研发,是由国防科技大的邢座程教授[2]带领的团队负责研发。[3]其商业化推广则是由中国电子信息产业集团有限公司旗下的天津飞腾信息技术有限公司负责。

飞腾公司在早期,考察了SPARC、MIPS、ALPHA架构,这三种指令集架构都可以以极其低廉的价格(据说SPARC的授权价只有99美元,ALPHA不要钱)获得授权,飞腾选择了SPARC架构进行了CPU的研发。

2012年ARM正式推出了自己的第一个64位指令集处理器架构ARMv8,进入服务器等新的领域。此后飞腾放弃了SPARC,拿了ARMv8指令集架构的授权,全面转向了ARM阵营,芯片roadmap如下:

img

测试芯片详细信息

2020 年 7 月 23 日,飞腾发布新一代高可扩展多路服务器芯片腾云 S2500,采用 16nm 工艺, 主频 2.0~2.2Ghz,拥有 64 个 FTC663 内核,片内集成 64MB 三级 Cache,支持 8 个 DDR4-3200 存 储通道,功耗 150W。

基于 ARM 架构,兼具高可拓展性和低功耗,扩展支持 2 路到 8 路直连。与主流架构 X86 相比, ARM 架构具备低功耗、低发热和低成本的优势,ARM 单核的面积仅为 X86 核的 1/7,同样芯片尺寸下可以继承更多核心数,可以通过增加核心数提高性能,在性能快速提升下,也能保持较低的功耗,符合云计算场景下并行计算上高并发和高效率的要求,也能有效控制服务器的能耗和成本支出。腾云 S2500 增加了 4 个直连接口,总带宽 800Gbps,支持 2 路、4 路和 8 路直连,具备高可 拓展性,可以形成 128 核到 512 核的计算机系统,带动算力提升。

飞腾(FT2500), ARMv8架构,主频2.1G,服务器两路,每路64个物理core,没有超线程,总共16个numa,每个numa 8个core

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
86
87
88
89
#dmidecode -t processor
# dmidecode 3.0
Getting SMBIOS data from sysfs.
SMBIOS 3.2.0 present.
# SMBIOS implementations newer than version 3.0 are not
# fully supported by this version of dmidecode.

Handle 0x0004, DMI type 4, 48 bytes
Processor Information
Socket Designation: BGA3576
Type: Central Processor
Family: <OUT OF SPEC>
Manufacturer: PHYTIUM
ID: 00 00 00 00 70 1F 66 22
Version: FT2500
Voltage: 0.8 V
External Clock: 50 MHz
Max Speed: 2100 MHz
Current Speed: 2100 MHz
Status: Populated, Enabled
Upgrade: Other
L1 Cache Handle: 0x0005
L2 Cache Handle: 0x0007
L3 Cache Handle: 0x0008
Serial Number: 1234567
Asset Tag: No Asset Tag
Part Number: NULL
Core Count: 64
Core Enabled: 64
Thread Count: 64
Characteristics:
64-bit capable
Multi-Core
Hardware Thread
Execute Protection
Enhanced Virtualization
Power/Performance Control

#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

node distances:
node 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
0: 10 20 40 30 20 30 50 40 100 100 100 100 100 100 100 100
1: 20 10 30 40 50 20 40 50 100 100 100 100 100 100 100 100
2: 40 30 10 20 40 50 20 30 100 100 100 100 100 100 100 100
3: 30 40 20 10 30 20 40 50 100 100 100 100 100 100 100 100
4: 20 50 40 30 10 50 30 20 100 100 100 100 100 100 100 100
5: 30 20 50 20 50 10 50 40 100 100 100 100 100 100 100 100
6: 50 40 20 40 30 50 10 30 100 100 100 100 100 100 100 100
7: 40 50 30 50 20 40 30 10 100 100 100 100 100 100 100 100
8: 100 100 100 100 100 100 100 100 10 20 40 30 20 30 50 40
9: 100 100 100 100 100 100 100 100 20 10 30 40 50 20 40 50
10: 100 100 100 100 100 100 100 100 40 30 10 20 40 50 20 30
11: 100 100 100 100 100 100 100 100 30 40 20 10 30 20 40 50
12: 100 100 100 100 100 100 100 100 20 50 40 30 10 50 30 20
13: 100 100 100 100 100 100 100 100 30 20 50 20 50 10 50 40
14: 100 100 100 100 100 100 100 100 50 40 20 40 30 50 10 30
15: 100 100 100 100 100 100 100 100 40 50 30 50 20 40 30 10

image-20210422121346490

cpu详细信息:

img

飞腾芯片,按如下distance绑核基本没区别!展示出来的distance是假的一样

img

FT2500芯片集成的 64 个处理器核心,划分为 8 个 Panel,每个 Panel 中有两个 Cluster (每个 Cluster 包含 4 个处理器核心及共享的 2M 二级 cache)、两个本地目录控 制部件(DCU)、一个片上网络路由器节点(Cell)和一个紧密耦合的访存控制 器(MCU)。Panel 之间通过片上网络接口连接,一致性维护报文、数据报文、 调测试报文、中断报文等统一从同一套网络接口进行路由和通信

一个Panel的实现是FTC663版本,采用四发射乱序超标量流水线结构,兼容 ARMv8 指令集,支持 EL0~EL3 多个特权级。流水线分为取指、译码、分派、执 行和写回五个阶段,采用顺序取指、乱序执行、顺序提交的多发射执行机制,取 值宽度、译码宽度、分派宽度均是 4 条指令,共有 9 个执行部件(或者称为 9 条功能流水线),分别是 4 个整数部件、2 个浮点部件、1 个 load 部件、1 个 load/store 部件和 1 个系统管理指令执行部件。浮点流水线能够合并执行双路浮点 SIMD 指 令,实现每拍可以执行 4 条双精度浮点操作的峰值性能。

image-20210910120438276

猜测FT2500 64core用的是一个Die, 但是core之间的连接是Ring Bus,而Ring Bus下core太多后延迟会快速增加,所以一个Die 内部做了8个小的Ring Bus,每个Ring Bus下8个core。

飞腾官方提供的测试结果

image-20210909175954574

飞腾2500 和 鲲鹏9200 参数对比

image-20210422095217195

FT2000与FT2500差异

下表是FT2000和FT2500产品规格对比表,和芯片的单核内部结构变化较少,多了L3,主频提高了,其他基本没有变化。

特征 FT-2000+/64 FT-2500
指令 兼容 ARM V8 指令集 FTC662 内核 兼容 ARM V8 指令集FTC663 内核
Core数 64个 64个
频率 2.2GHZ/2.0GHZ/1.8GHZ 2.0~2.3GHz
体系架构 NUMA NUMA
RAS 无 支持
加解密 无 ASE128、SHA1、SHA2-256、PMULL
L1 Cache 每个核独占32KB指令Cache与32KB数据Cache 每个核独占32K指令Cache与32K数据Cache
L2 Cache 共32MB,每4个核共享2MB 共32MB,每4个核共享2MB
L3 Cache 无 64MB
LMU数量 8个 8个
支持最大容量 1TB 1TB*socket数量
支持最大频率 3200MHZ 支持3200MHZ
外接设备 支持带 ECC 的 DDR4 DIMM,支持 RDIMM、UDIMM、SODIMM、 LR-DIMM,电压 1.2V 支持带 ECC 的 DDR4 DIMM,支持 RDIMM、UDIMM、SODIMM、LR-DIMM,电压 1.2V
镜像存储 无 每两个MCU互为备份
PCIe PCIE3.02 个 x16 和 1 个 x1每个 x16 可拆分成 2 个 x8,支持翻转 PCIE3.01 个 x16 和 1 个 x1x16 可拆分成 2 个 x8,支持翻转
SPI 支持 4 个片选,单片最大支持容量为 512MB,电压 1.8V 支持 4 个片选,单片最大支持容量为 512MB,电压 1.8V
UART 4个 UART,其中 1 个为 9 线全功能串口,3 个为 3 线调试串口 4个 UART,其中 1 个为 9 线全功能串口,3 个为 3 线调试串口
GPIO 4 个 8 位 GPIO 接口,GPIOA[0:7],GPIOB[0:7],GPIOC[0:7], GPIOD[0:7] 4 个 8 位 GPIO 接口,GPIOA[0:7],GPIOB[0:7],GPIOC[0:7], GPIOD[0:7]
LPC 1 个 LPC 接口,兼容 Intel Low Pin Count 协议, 电压 1.8V 1 个 LPC 接口,兼容 Intel Low Pin Count 协议, 电压 1.8V
I2C 2 个 I2C master 控制器 2 个 I2C master /Slave控制器,2个slave控制器
直连 无 四个直连通路,每路X4个lane,每条lane速率为25Gbps,支持2路、4路、8路

飞腾ARM芯片性能测试数据

以下测试场景基本都是运行CPU和网络瓶颈的业务逻辑,绑核前IPC只有0.08

img

绑核后对性能提升非常明显:

img

点查场景:

image-20210425092158127

如上是绑48-63号核

image-20210425091727122

image-20210425091557750

image-20210425093630438

绑不同的核性能差异比较大,比如同样绑第一个socket最后16core和绑第二个socket最后16core,第二个socket的最后16core性能要好25-30%—这是因为网卡软中断,如果将软中断绑定到0-4号cpu后差异基本消失,因为网卡队列设置的是60,基本跑在前60core上,也就是第一个socket上。

点查场景绑核和不绑核性能能差1倍, 将table分表后,物理rt稳定了(截图中物理rt下降是因为压力小了–待证)

点查场景压测16个core的节点

一个节点16core,16个core绑定到14、15号NUMA上,然后压测

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
#perl numa-maps-summary.pl </proc/79694/numa_maps //16core
N0 : 1103 ( 0.00 GB)
N1 : 107368 ( 0.41 GB)
N10 : 144736 ( 0.55 GB)
N11 : 16919 ( 0.06 GB)
N12 : 551987 ( 2.11 GB)
N13 : 59499 ( 0.23 GB)
N14 : 5621573 ( 21.44 GB) //内存就近分配
N15 : 6200398 ( 23.65 GB)
N2 : 700 ( 0.00 GB)
N3 : 89 ( 0.00 GB)
N4 : 5784 ( 0.02 GB)
N5 : 77 ( 0.00 GB)
N6 : 426 ( 0.00 GB)
N7 : 472 ( 0.00 GB)
N8 : 107 ( 0.00 GB)
N9 : 6137 ( 0.02 GB)
active : 85 ( 0.00 GB)
anon : 12712675 ( 48.50 GB)
dirty : 12712679 ( 48.50 GB)
kernelpagesize_kB: 17444 ( 0.07 GB)
mapmax : 1598 ( 0.01 GB)
mapped : 4742 ( 0.02 GB)

#perf stat -e branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,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 -a -p 79694
^C
Performance counter stats for process id '79694':

1719788217 branch-misses (39.70%)
311069393237 bus-cycles (38.07%)
2021349865 cache-misses # 6.669 % of all cache refs (38.32%)
30308501243 cache-references (39.67%)
310980728138 cpu-cycles (46.46%)
67298903097 instructions # 0.22 insns per cycle (47.63%)
1983728595 L1-dcache-load-misses # 6.62% of all L1-dcache hits (48.76%)
29943167305 L1-dcache-loads (47.89%)
1957152091 L1-dcache-store-misses (46.14%)
29572767575 L1-dcache-stores (44.91%)
4223808613 L1-icache-load-misses (43.08%)
49122358099 L1-icache-loads (38.15%)
1724605628 branch-load-misses (37.63%)
15225535577 branch-loads (36.61%)
997458038 dTLB-load-misses (35.81%)
542426693 iTLB-load-misses (34.98%)

10.489297296 seconds time elapsed

[ 29s] threads: 160, tps: 0.00, reads/s: 15292.01, writes/s: 0.00, response time: 25.82ms (95%)
[ 30s] threads: 160, tps: 0.00, reads/s: 16399.99, writes/s: 0.00, response time: 23.58ms (95%)
[ 31s] threads: 160, tps: 0.00, reads/s: 17025.00, writes/s: 0.00, response time: 20.73ms (95%)
[ 32s] threads: 160, tps: 0.00, reads/s: 16991.01, writes/s: 0.00, response time: 22.83ms (95%)
[ 33s] threads: 160, tps: 0.00, reads/s: 18400.94, writes/s: 0.00, response time: 21.29ms (95%)
[ 34s] threads: 160, tps: 0.00, reads/s: 17760.05, writes/s: 0.00, response time: 20.69ms (95%)
[ 35s] threads: 160, tps: 0.00, reads/s: 17935.00, writes/s: 0.00, response time: 20.23ms (95%)
[ 36s] threads: 160, tps: 0.00, reads/s: 18296.98, writes/s: 0.00, response time: 20.10ms (95%)
[ 37s] threads: 160, tps: 0.00, reads/s: 18111.02, writes/s: 0.00, response time: 20.56ms (95%)
[ 38s] threads: 160, tps: 0.00, reads/s: 17782.99, writes/s: 0.00, response time: 20.54ms (95%)
[ 38s] threads: 160, tps: 0.00, reads/s: 21412.13, writes/s: 0.00, response time: 11.96ms (95%)
[ 40s] threads: 160, tps: 0.00, reads/s: 18027.85, writes/s: 0.00, response time: 20.18ms (95%)
[ 41s] threads: 160, tps: 0.00, reads/s: 17907.04, writes/s: 0.00, response time: 20.02ms (95%)
[ 42s] threads: 160, tps: 0.00, reads/s: 13860.96, writes/s: 0.00, response time: 23.58ms (95%)
[ 43s] threads: 160, tps: 0.00, reads/s: 18491.02, writes/s: 0.00, response time: 20.18ms (95%)
[ 44s] threads: 160, tps: 0.00, reads/s: 17673.02, writes/s: 0.00, response time: 20.85ms (95%)
[ 45s] threads: 160, tps: 0.00, reads/s: 18048.96, writes/s: 0.00, response time: 21.47ms (95%)
[ 46s] threads: 160, tps: 0.00, reads/s: 18130.03, writes/s: 0.00, response time: 22.13ms (95%)

点查场景压测8个core的节点

因为每个NUMA才8个core,所以测试一下8core的节点绑核前后性能对比。实际结果看起来和16core节点绑核性能提升差不多。

绑核前后对比:绑核后QPS翻倍,绑核后的服务rt从7.5降低到了2.2,rt下降非常明显,可以看出主要是绑核前跨numa访问慢。实际这个测试是先跑的不绑核,内存分布在所有NUMA上,没有重启再绑核就直接测试了,所以性能提升不明显,因为内存已经跨NUMA分配完毕了。

image-20210427093424116

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#perl numa-maps-summary.pl </proc/33727/numa_maps //绑定8core后,在如下内存分配下QPS能到11000,但是抖动略大,应该是一个numa内存不够了
N0 : 551 ( 0.00 GB)
N1 : 1023418 ( 3.90 GB)
N10 : 52065 ( 0.20 GB)
N11 : 190737 ( 0.73 GB)
N12 : 516115 ( 1.97 GB)
N13 : 186556 ( 0.71 GB)
N14 : 1677489 ( 6.40 GB)
N15 : 324531 ( 1.24 GB)
N2 : 397 ( 0.00 GB)
N3 : 8 ( 0.00 GB)
N4 : 398 ( 0.00 GB)
N6 : 349 ( 0.00 GB)
N7 : 437 ( 0.00 GB)
N8 : 108508 ( 0.41 GB)
N9 : 69162 ( 0.26 GB)
active : 2296 ( 0.01 GB)
anon : 4144997 ( 15.81 GB)
dirty : 4145002 ( 15.81 GB)
kernelpagesize_kB: 7508 ( 0.03 GB)
mapmax : 1548 ( 0.01 GB)
mapped : 5724 ( 0.02 GB)

[ 349s] threads: 100, tps: 0.00, reads/s: 11088.99, writes/s: 0.00, response time: 20.18ms (95%)
[ 350s] threads: 100, tps: 0.00, reads/s: 8778.98, writes/s: 0.00, response time: 26.20ms (95%)
[ 351s] threads: 100, tps: 0.00, reads/s: 7995.01, writes/s: 0.00, response time: 31.79ms (95%)
[ 352s] threads: 100, tps: 0.00, reads/s: 9549.01, writes/s: 0.00, response time: 23.90ms (95%)
[ 353s] threads: 100, tps: 0.00, reads/s: 8757.99, writes/s: 0.00, response time: 24.60ms (95%)
[ 354s] threads: 100, tps: 0.00, reads/s: 10288.02, writes/s: 0.00, response time: 21.85ms (95%)
[ 355s] threads: 100, tps: 0.00, reads/s: 11003.97, writes/s: 0.00, response time: 18.90ms (95%)
[ 356s] threads: 100, tps: 0.00, reads/s: 11111.01, writes/s: 0.00, response time: 20.51ms (95%)
[ 357s] threads: 100, tps: 0.00, reads/s: 11426.00, writes/s: 0.00, response time: 17.98ms (95%)
[ 358s] threads: 100, tps: 0.00, reads/s: 11007.01, writes/s: 0.00, response time: 19.35ms (95%)
[ 359s] threads: 100, tps: 0.00, reads/s: 10425.00, writes/s: 0.00, response time: 20.92ms (95%)
[ 360s] threads: 100, tps: 0.00, reads/s: 10024.00, writes/s: 0.00, response time: 23.17ms (95%)
[ 361s] threads: 100, tps: 0.00, reads/s: 10100.98, writes/s: 0.00, response time: 22.94ms (95%)
[ 362s] threads: 100, tps: 0.00, reads/s: 8164.01, writes/s: 0.00, response time: 27.48ms (95%)
[ 363s] threads: 100, tps: 0.00, reads/s: 6593.00, writes/s: 0.00, response time: 37.10ms (95%)
[ 364s] threads: 100, tps: 0.00, reads/s: 7008.00, writes/s: 0.00, response time: 32.32ms (95%)

#调整这个实例到内存充足的NUMA7上 QPS峰值能到14000,稳定在11000-13000之间,RT明显更稳定了
#perl numa-maps-summary.pl </proc/78245/numa_maps
N0 : 551 ( 0.00 GB)
N1 : 115 ( 0.00 GB)
N11 : 695 ( 0.00 GB)
N12 : 878 ( 0.00 GB)
N13 : 2019 ( 0.01 GB)
N14 : 25 ( 0.00 GB)
N15 : 60 ( 0.00 GB)
N2 : 394 ( 0.00 GB)
N3 : 8 ( 0.00 GB)
N4 : 197713 ( 0.75 GB)
N6 : 349 ( 0.00 GB)
N7 : 3957844 ( 15.10 GB)
N8 : 1 ( 0.00 GB)
active : 10 ( 0.00 GB)
anon : 4154693 ( 15.85 GB)
dirty : 4154698 ( 15.85 GB)
kernelpagesize_kB: 7452 ( 0.03 GB)
mapmax : 1567 ( 0.01 GB)
mapped : 5959 ( 0.02 GB)

[ 278s] threads: 100, tps: 0.00, reads/s: 13410.99, writes/s: 0.00, response time: 15.36ms (95%)
[ 279s] threads: 100, tps: 0.00, reads/s: 14049.99, writes/s: 0.00, response time: 15.54ms (95%)
[ 280s] threads: 100, tps: 0.00, reads/s: 13107.02, writes/s: 0.00, response time: 16.72ms (95%)
[ 281s] threads: 100, tps: 0.00, reads/s: 12431.99, writes/s: 0.00, response time: 17.79ms (95%)
[ 282s] threads: 100, tps: 0.00, reads/s: 13164.01, writes/s: 0.00, response time: 16.33ms (95%)
[ 283s] threads: 100, tps: 0.00, reads/s: 13455.01, writes/s: 0.00, response time: 16.19ms (95%)
[ 284s] threads: 100, tps: 0.00, reads/s: 12932.01, writes/s: 0.00, response time: 16.22ms (95%)
[ 285s] threads: 100, tps: 0.00, reads/s: 12790.99, writes/s: 0.00, response time: 17.00ms (95%)
[ 286s] threads: 100, tps: 0.00, reads/s: 12706.00, writes/s: 0.00, response time: 17.88ms (95%)
[ 287s] threads: 100, tps: 0.00, reads/s: 11886.00, writes/s: 0.00, response time: 19.43ms (95%)
[ 288s] threads: 100, tps: 0.00, reads/s: 12700.00, writes/s: 0.00, response time: 16.97ms (95%)

#perl numa-maps-summary.pl </proc/54723/numa_maps //54723绑定在NUMA6上
N0 : 551 ( 0.00 GB)
N1 : 115 ( 0.00 GB)
N11 : 682 ( 0.00 GB)
N12 : 856 ( 0.00 GB)
N13 : 2018 ( 0.01 GB)
N14 : 25 ( 0.00 GB)
N15 : 60 ( 0.00 GB)
N2 : 1270166 ( 4.85 GB) //不应该分配这里的内存,实际是因为N6内存被PageCache使用掉了

N3 : 8 ( 0.00 GB)
N4 : 398 ( 0.00 GB)
N6 : 3662400 ( 13.97 GB)
N7 : 460 ( 0.00 GB)
N8 : 1 ( 0.00 GB)
active : 9 ( 0.00 GB)
anon : 4931796 ( 18.81 GB)
dirty : 4931801 ( 18.81 GB)
kernelpagesize_kB: 7920 ( 0.03 GB)
mapmax : 1580 ( 0.01 GB)
mapped : 5944 ( 0.02 GB)

#cat /proc/meminfo | grep -i active
Active: 22352360 kB
Inactive: 275173756 kB
Active(anon): 16984 kB
Inactive(anon): 240344208 kB
Active(file): 22335376 kB
Inactive(file): 34829548 kB

#echo 3 > /proc/sys/vm/drop_caches

#cat /proc/meminfo | grep -i active
Active: 1865724 kB
Inactive: 242335632 kB
Active(anon): 7108 kB
Inactive(anon): 240199020 kB
Active(file): 1858616 kB //回收了大量PageCache内存
Inactive(file): 2136612 kB
#perl numa-maps-summary.pl </proc/54723/numa_maps
N0 : 552 ( 0.00 GB)
N1 : 115 ( 0.00 GB)
N11 : 682 ( 0.00 GB)
N12 : 856 ( 0.00 GB)
N13 : 2018 ( 0.01 GB)
N14 : 25 ( 0.00 GB)
N15 : 60 ( 0.00 GB)
N2 : 1740 ( 0.01 GB)
N3 : 8 ( 0.00 GB)
N4 : 398 ( 0.00 GB)
N6 : 4972492 ( 18.97 GB)
N7 : 459 ( 0.00 GB)
N8 : 1 ( 0.00 GB)
active : 16 ( 0.00 GB)
anon : 4973486 ( 18.97 GB)
dirty : 4973491 ( 18.97 GB)
kernelpagesize_kB: 8456 ( 0.03 GB)
mapmax : 1564 ( 0.01 GB)
mapped : 5920 ( 0.02 GB)

image-20210427164953340

绑核前的IPC:

image-20210427093625575

绑核后的IPC:

image-20210427095130343

如果是两个8core对一个16core在都最优绑核场景下从上面的数据来看能有40-50%的性能提升,并且RT抖动更小,这两个8core绑定在同一个Socket下,验证是否争抢,同时可以看到绑核后性能可以随着加节点线性增加

image-20210427172612685

image-20210427173047815

image-20210427173417673

结论:不绑核一个FT2500的core点查只有500 QPS,绑核后能到1500QPS, 在Intel 8263下一个core能到6000以上(开日志、没开协程)

MySQL 数据库场景绑核

通过同一台物理上6个Tomcat节点,总共96个core,压6台MySQL,MySQL基本快打挂了。sysbench 点查,32个分表,增加Tomcat节点进来物理rt就增加,从最初的的1.2ms加到6个Tomcat节点后变成8ms。

image-20210425180535225

MySQL没绑好核,BIOS默认关闭了NUMA,外加12个MySQL分布在物理机上不均匀,3个节点3个MySQL,剩下的物理机上只有一个MySQL实例。

MySQL每个实例32core,管控默认已经做了绑核,但是如果两个MySQL绑在了一个socket上竞争会很激烈,ipc比单独的降一半。

比如这三个MySQL,qps基本均匀,上面两个cpu高,但是没效率,每个MySQL绑了32core,上面两个绑在一个socket上,下面的MySQL绑在另一个socket上,第一个socket还有网络软中断在争抢cpu,飞腾环境下性能真要冲高还有很大空间。

image-20210425180518926

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
#第二个MySQL IPC只有第三个的30%多点,这就是为什么CPU高这么多,但是QPS差不多
perf stat -e branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,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 -a -p 61238
^C
Performance counter stats for process id '61238':

86,491,052 branch-misses (58.55%)
98,481,418,793 bus-cycles (55.64%)
113,095,618 cache-misses # 6.169 % of all cache refs (53.20%)
1,833,344,484 cache-references (52.00%)
101,516,165,898 cpu-cycles (57.09%)
4,229,190,014 instructions # 0.04 insns per cycle (55.91%)
111,780,025 L1-dcache-load-misses # 6.34% of all L1-dcache hits (55.40%)
1,764,421,570 L1-dcache-loads (52.62%)
112,261,128 L1-dcache-store-misses (49.34%)
1,814,998,338 L1-dcache-stores (48.51%)
219,372,119 L1-icache-load-misses (49.56%)
2,816,279,627 L1-icache-loads (49.15%)
85,321,093 branch-load-misses (50.38%)
1,038,572,653 branch-loads (50.65%)
45,166,831 dTLB-load-misses (51.98%)
29,892,473 iTLB-load-misses (52.56%)

1.163750756 seconds time elapsed

#第三个MySQL
perf stat -e branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,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 -a -p 53400
^C
Performance counter stats for process id '53400':

295,575,513 branch-misses (40.51%)
110,934,600,206 bus-cycles (39.30%)
537,938,496 cache-misses # 8.310 % of all cache refs (38.99%)
6,473,688,885 cache-references (39.80%)
110,540,950,757 cpu-cycles (46.10%)
14,766,013,708 instructions # 0.14 insns per cycle (46.85%)
538,521,226 L1-dcache-load-misses # 8.36% of all L1-dcache hits (48.00%)
6,440,728,959 L1-dcache-loads (46.69%)
533,693,357 L1-dcache-store-misses (45.91%)
6,413,111,024 L1-dcache-stores (44.92%)
673,725,952 L1-icache-load-misses (42.76%)
9,216,663,639 L1-icache-loads (38.27%)
299,202,001 branch-load-misses (37.62%)
3,285,957,082 branch-loads (36.10%)
149,348,740 dTLB-load-misses (35.20%)
102,444,469 iTLB-load-misses (34.78%)

8.080841166 seconds time elapsed

12个MySQL流量基本均匀:

image-20210426083033989

numa太多,每个numa下core比较少

导致跨numa高概率发生,如下是在正常部署下的测试perf 数据,可以看到IPC极低,才0.08,同样的场景在其他家芯片都能打到0.6

img

执行绑核,将一个进程限制在2个numa内,因为进程需要16core,理论上用8core的进程性能会更好

img

可以看到IPC从0.08提升到了0.22,实际能到0.27,对应的业务测试QPS也是原来的4倍。

用numactl 在启动的时候绑定cpu在 node0、1上,优先使用node0、1的内存,不够再用其它node的内存

1
numactl --cpunodebind 0,1 --preferred 0,1 /u01/xcluster80/bin/mysqld_safe  --defaults-file=/polarx/xcluster3308/my.cnf  --basedir=/u01/xcluster80_current  --datadir=/polarx/xcluster3308/data  --plugin-dir=/u01/xcluster80/lib/plugin  --user=mysql  --log-error=/polarx/xcluster3308/log/alert.log  --open-files-limit=615350  --pid-file=/polarx/xcluster3308/run/mysql.pid  --socket=/polarx/xcluster3308/run/mysql.sock  --cluster-info=11.158.239.200:11308@1  --mysqlx-port=13308  --port=3308

网卡队列调整

这批机器默认都是双网卡做bond,但是两块网卡是HA,默认网卡队列是60,基本都跑在前面60个core上

将MySQL网卡队列从60个改成6个后MySQL性能提升大概10%

image-20210426085534983

默认第一个MySQL都绑在0-31号核上,其实改少队列加大了0-5号core的压力,但是实际数据表现要好。

比较其它

绑核的时候还要考虑磁盘、网卡在哪个socket上,相对来说node和磁盘、网卡在同一个socket下性能要好一些。

左边的mysqld绑定在socket1的64core上,磁盘、网卡都在socket1上;右边的mysqld绑定在0-31core上,网卡在socket0上,但是磁盘在socket1上

右边这个刚好是跨socket访问磁盘,不知道是不是巧合log_flush排位比较高

image-20210910180305752

此时对应的IPC:

image-20210910181820803

如果上面两个进程在没有刷日志的场景下时候对应的IPC两者基本一样:

image-20210910181909962

结论

FT2500比同主频Intel x86芯片差了快一个数量级的性能,在对FT2500上的业务按node绑核后性能提升了几倍,但是离Intel x86还有很大的距离

用循环跑多个nop指令在飞腾2500下IPC只能跑到1,据说这是因为nop指令被扔掉了,所以一直在跑跳转循环判断;

对寄存器变量进行++运算,IPC是0.5;

用如下代码能将IPC跑到2.49,也是我能跑出来的最高IPC了,去掉nop那行,IPC是1.99

1
2
3
4
        register unsigned i=0;
for (i=0;i<(1u<<31);i++) {
__asm__ ("nop");
}

系列文章

CPU的制造和概念

[CPU 性能和Cache Line](/2021/05/16/CPU Cache Line 和性能/)

[Perf IPC以及CPU性能](/2021/05/16/Perf IPC以及CPU利用率/)

Intel、海光、鲲鹏920、飞腾2500 CPU性能对比

飞腾ARM芯片(FT2500)的性能测试

十年后数据库还是不敢拥抱NUMA?

一次海光物理机资源竞争压测的记录

[Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的](/2019/12/16/Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的/)

参考资料

CPU Utilization is Wrong

十年后数据库还是不敢拥抱NUMA?

发表于 2021-05-14 | 分类于 CPU

十年后数据库还是不敢拥抱NUMA?

在2010年前后MySQL、PG、Oracle数据库在使用NUMA的时候碰到了性能问题,流传最广的这篇 MySQL – The MySQL “swap insanity” problem and the effects of the NUMA architecture 描述了性能问题的原因(文章中把原因找错了)以及解决方案:关闭NUMA。 实际这个原因是kernel实现的一个低级bug,这个Bug在2014年修复了,但是修复这么多年后仍然以讹传讹,这篇文章希望正本清源、扭转错误的认识。

背景

最近在做一次性能测试的时候发现MySQL实例有一个奇怪现象,在128core的物理机上运行三个MySQL实例,每个实例分别绑定32个物理core,绑定顺序就是第一个0-31、第二个32-63、第三个64-95,实际运行结果让人大跌眼镜,如下图

undefined

从CPU消耗来看差异巨大,高的实例CPU用到了2500%,低的才488%,差了5倍。但是神奇的是他们的QPS一样,执行的SQL也是一样

undefined
所有MySQL实例流量一样

那么问题来了为什么在同样的机器上、同样的流量下CPU使用率差了这么多? 换句话来问就是CPU使用率高就有效率吗?

这台物理机CPU 信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#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): 1
Model: 3
BogoMIPS: 100.00
L1d cache: 32K
L1i cache: 32K
L2 cache: 2048K
L3 cache: 65536K
NUMA node0 CPU(s): 0-127
Flags: fp asimd evtstrm aes pmull sha1 sha2 crc32 cpuid

原因分析

先来看这两个MySQL 进程的Perf数据

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
#第二个 MySQL IPC只有第三个的30%多点,这就是为什么CPU高这么多,但是QPS差不多
perf stat -e branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,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 -a -p 61238
^C
Performance counter stats for process id '61238':

86,491,052 branch-misses (58.55%)
98,481,418,793 bus-cycles (55.64%)
113,095,618 cache-misses # 6.169 % of all cache refs (53.20%)
1,833,344,484 cache-references (52.00%)
101,516,165,898 cpu-cycles (57.09%)
4,229,190,014 instructions # 0.04 insns per cycle (55.91%)
111,780,025 L1-dcache-load-misses # 6.34% of all L1-dcache hits (55.40%)
1,764,421,570 L1-dcache-loads (52.62%)
112,261,128 L1-dcache-store-misses (49.34%)
1,814,998,338 L1-dcache-stores (48.51%)
219,372,119 L1-icache-load-misses (49.56%)
2,816,279,627 L1-icache-loads (49.15%)
85,321,093 branch-load-misses (50.38%)
1,038,572,653 branch-loads (50.65%)
45,166,831 dTLB-load-misses (51.98%)
29,892,473 iTLB-load-misses (52.56%)

1.163750756 seconds time elapsed

#第三个 MySQL
perf stat -e branch-misses,bus-cycles,cache-misses,cache-references,cpu-cycles,instructions,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 -a -p 53400
^C
Performance counter stats for process id '53400':

295,575,513 branch-misses (40.51%)
110,934,600,206 bus-cycles (39.30%)
537,938,496 cache-misses # 8.310 % of all cache refs (38.99%)
6,473,688,885 cache-references (39.80%)
110,540,950,757 cpu-cycles (46.10%)
14,766,013,708 instructions # 0.14 insns per cycle (46.85%)
538,521,226 L1-dcache-load-misses # 8.36% of all L1-dcache hits (48.00%)
6,440,728,959 L1-dcache-loads (46.69%)
533,693,357 L1-dcache-store-misses (45.91%)
6,413,111,024 L1-dcache-stores (44.92%)
673,725,952 L1-icache-load-misses (42.76%)
9,216,663,639 L1-icache-loads (38.27%)
299,202,001 branch-load-misses (37.62%)
3,285,957,082 branch-loads (36.10%)
149,348,740 dTLB-load-misses (35.20%)
102,444,469 iTLB-load-misses (34.78%)

8.080841166 seconds time elapsed

从上面可以看到 IPC 差异巨大0.04 VS 0.14 ,也就是第一个MySQL的CPU效率很低,我们看到的CPU running实际是CPU在等待(stall)。

CPU的实际信息

找到同一个机型,但是NUMA开着的查了一下:

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

这告诉我们实际上这个机器有16个NUMA,跨NUMA访问内存肯定比访问本NUMA内的要慢几倍。

关于NUMA

如下图,是一个Intel Xeon E5 CPU的架构信息,左右两边的大红框分别是两个NUMA,每个NUMA的core访问直接插在自己红环上的内存必然很快,如果访问插在其它NUMA上的内存还要走两个红环之间上下的黑色箭头线路,所以要慢很多。

img

实际测试Intel的E5-2682(对应V42机型)和8269(对应V62机型) 的CPU跨Socket(这两块CPU内部不再是上图的红环Bus,而是改用了Mesh Bus一个Die就是一个NUMA,服务器有两路,也就是一个Socket就是一个NUMA),也就是跨NUMA访问内存的延迟是本Node延迟的将近2倍。测试工具从这里下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//E5-2682
Intel(R) Memory Latency Checker - v3.9
Measuring idle latencies (in ns)...
Numa node
Numa node 0 1
0 85.0 136.3
1 137.2 84.2

//8269
Intel(R) Memory Latency Checker - v3.9
Measuring idle latencies (in ns)...
Numa node
Numa node 0 1
0 78.6 144.1
1 144.7 78.5

开启NUMA会优先就近使用内存,在本NUMA上的内存不够的时候可以选择回收本地的PageCache还是到其它NUMA 上分配内存,这是可以通过Linux参数 zone_reclaim_mode 来配置的,默认是到其它NUMA上分配内存,也就是跟关闭NUMA是一样的。

这个架构距离是物理上就存在的不是你在BIOS里关闭了NUMA差异就消除了,我更愿意认为在BIOS里关掉NUMA只是掩耳盗铃。

以上理论告诉我们:也就是在开启NUMA和 zone_reclaim_mode 默认在内存不够的如果去其它NUMA上分配内存,比关闭NUMA要快很多而没有任何害处。

UMA和NUMA对比

The SMP/UMA architecture

img

The NUMA architecture

img

Modern multiprocessor systems mix these basic architectures as seen in the following diagram:

img

In this complex hierarchical scheme, processors are grouped by their physical location on one or the other multi-core CPU package or “node.” Processors within a node share access to memory modules as per the UMA shared memory architecture. At the same time, they may also access memory from the remote node using a shared interconnect, but with slower performance as per the NUMA shared memory architecture.

03-05-Broadwell_HCC_Architecture

对比测试Intel NUMA 性能

对如下Intel CPU进行一些测试,在开启NUMA的情况下

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
#lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 64
On-line CPU(s) list: 0-63
Thread(s) per core: 2
Core(s) per socket: 16
Socket(s): 2
NUMA node(s): 2
Vendor ID: GenuineIntel
CPU family: 6
Model: 79
Model name: Intel(R) Xeon(R) CPU E5-2682 v4 @ 2.50GHz
Stepping: 1
CPU MHz: 2500.000
CPU max MHz: 3000.0000
CPU min MHz: 1200.0000
BogoMIPS: 5000.06
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 256K
L3 cache: 40960K
NUMA node0 CPU(s): 0-15,32-47
NUMA node1 CPU(s): 16-31,48-63
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 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 rdt rdseed adx smap xsaveopt cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local cat_l3

#numastat
node0 node1
numa_hit 129600200 60501102
numa_miss 0 0
numa_foreign 0 0
interleave_hit 108648 108429
local_node 129576548 60395061
other_node 23652 106041

我在这个64core的物理机上运行一个MySQL 实例,先将MySQL进程绑定在0-63core,0-31core,以及0-15,32-47上

用sysbench对一亿条记录跑点查,数据都加载到内存中了:

  • 绑0-63core qps 不到8万,总cpu跑到5000%,降低并发的话qps能到11万;
  • 如果绑0-31core qps 12万,总cpu跑到3200%,IPC 0.29;
  • 如果绑同一个numa下的32core,qps飙到27万,总CPU跑到3200% IPC: 0.42;
  • 绑0-15个物理core,qps能到17万,绑32-47也是一样的效果;

undefined

从这个数据看起来即使Intel在只有两个NUMA的情况下跨性能差异也有2倍,可见正确的绑核方法收益巨大,尤其是在刷榜的情况下, NUMA更多性能差异应该会更大。

说明前面的理论是正确的。

来看看不通绑核情况下node之间的带宽利用情况:

image-20210525151537507

image-20210525151622425

实际在不开NUMA的同样CPU上,进行以上各种绑核测试,测试结果也完全一样。

如果比较读写混合场景的话肯定会因为写锁导致CPU跑起来,最终的性能差异也不会这么大,但是绑在同一个NUMA下的性能肯定要好,IPC也会高一些。具体好多少取决于锁的竞争程度。

为什么集团内外所有物理机都把NUMA关掉了呢?

10年前几乎所有的运维都会多多少少被NUMA坑害过,让我们看看究竟有多少种在NUMA上栽的方式:

  • MySQL – The MySQL “swap insanity” problem and the effects of the NUMA architecture
  • PostgreSQL – PostgreSQL, NUMA and zone reclaim mode on linux
  • Oracle – Non-Uniform Memory Access (NUMA) architecture with Oracle database by examples
  • Java – Optimizing Linux Memory Management for Low-latency / High-throughput Databases

最有名的是这篇 MySQL – The MySQL “swap insanity” problem and the effects of the NUMA architecture

我总结下这篇2010年的文章说的是啥:

  • 如果本NUMA内存不够的时候,Linux会优先回收PageCache内存,即使其它NUMA还有内存
  • 回收PageCache经常会造成系统卡顿,这个卡顿不能接受

所以文章给出的解决方案就是(三选一):

  • 关掉NUMA
  • 或者启动MySQL的时候指定不分NUMA,比如:/usr/bin/numactl –interleave all $cmd
  • 或者启动MySQL的时候先回收所有PageCache

我想这就是这么多人在上面栽了跟头,所以干脆一不做二不休干脆关了NUMA 一了百了。

但真的NUMA有这么糟糕?或者说Linux Kernel有这么笨,默认优先去回收PageCache吗?

Linux Kernel对NUMA内存的使用

实际我们使用NUMA的时候期望是:优先使用本NUMA上的内存,如果本NUMA不够了不要优先回收PageCache而是优先使用其它NUMA上的内存。

zone_reclaim_mode

事实上Linux识别到NUMA架构后,默认的内存分配方案就是:优先尝试在请求线程当前所处的CPU的Local内存上分配空间。如果local内存不足,优先淘汰local内存中无用的Page(Inactive,Unmapped)。然后才到其它NUMA上分配内存。

intel 芯片跨node延迟远低于其他家,所以跨node性能损耗不大

zone_reclaim_mode,它用来管理当一个内存区域(zone)内部的内存耗尽时,是从其内部进行内存回收还是可以从其他zone进行回收的选项:

zone_reclaim_mode:

Zone_reclaim_mode allows someone to set more or less aggressive approaches to
reclaim memory when a zone runs out of memory. If it is set to zero then no
zone reclaim occurs. Allocations will be satisfied from other zones / nodes
in the system.

zone_reclaim_mode的四个参数值的意义分别是:

0 = Allocate from all nodes before reclaiming memory
1 = Reclaim memory from local node vs allocating from next node
2 = Zone reclaim writes dirty pages out
4 = Zone reclaim swaps pages

1
2
# cat /proc/sys/vm/zone_reclaim_mode
0

我查了2.6.32以及4.19.91内核的机器 zone_reclaim_mode 都是默认0 ,也就是kernel会:优先使用本NUMA上的内存,如果本NUMA不够了不要优先回收PageCache而是优先使用其它NUMA上的内存。这也是我们想要的

Kernel文档也告诉大家默认就是0,但是为什么会出现优先回收了PageCache呢?

查看kernel提交记录

github kernel commit

undefined

undefined

undefined

关键是上图红框中的代码,node distance比较大(也就是开启了NUMA的话),强制将 zone_reclaim_mode设为1,这是2014年提交的代码,将这个强制设为1的逻辑去掉了。

这也就是为什么之前大佬们碰到NUMA问题后尝试修改 zone_reclaim_mode 没有效果,也就是2014年前只要开启了NUMA就强制线回收PageCache,即使设置zone_reclaim_mode也没有意义,真是个可怕的Bug。

验证一下zone_reclaim_mode 0是生效的

内核版本:3.10.0-327.ali2017.alios7.x86_64

测试方法

先将一个160G的文件加载到内存里,然后再用代码分配64G的内存出来使用。
单个NUMA node的内存为256G,本身用掉了60G,加上这次的160G的PageCache,和之前的一些其他PageCache,总的 PageCache用了179G,那么这个node总内存还剩256G-60G-179G,

如果这个时候再分配64G内存的话,本node肯定不够了,我们来看在 zone_reclaim_mode=0 的时候是优先回收PageCache还是分配了到另外一个NUMA node(这个NUMA node 有240G以上的内存空闲)

测试过程

分配64G内存

1
2
3
4
5
#taskset -c 0 ./alloc 64
To allocate 64GB memory
Used time: 39 seconds

#grep FilePages /sys/devices/system/node/node0/meminfo

undefined

从如上截图来看,再分配64G内存的时候即使node0不够了也没有回收node0上的PageCache,而是将内存跨NUMA分配到了node1上,符合预期!

释放这64G内存后,如下图可以看到node0回收了25G,剩下的39G都是在node1上:
undefined

将 /proc/sys/vm/zone_reclaim_mode 改成 1 继续同样的测试

可以看到zone_reclaim_mode 改成 1,node0内存不够了也没有分配node1上的内存,而是从PageCache回收了40G内存,整个分配64G内存的过程也比不回收PageCache慢了12秒,这12秒就是额外的卡顿

undefined

测试结论:从这个测试可以看到NUMA 在内存使用上不会优先回收 PageCache 了

innodb_numa_interleave

从5.7开始,mysql增加了对NUMA的无感知:innodb_numa_interleave,也就是在开了NUMA的机器上,使用内错交错来分配内存,相当于使用上关掉 NUMA

For the innodb_numa_interleave option to be available, MySQL must be compiled on a NUMA-enabled Linux system.

当开启了 innodb_numa_interleave 的话在为innodb buffer pool分配内存的时候将 NUMA memory policy 设置为 MPOL_INTERLEAVE 分配完后再设置回 MPOL_DEFAULT(OS默认内存分配行为,也就是zone_reclaim_mode指定的行为)。

innodb_numa_interleave参数是为innodb更精细化地分配innodb buffer pool 而增加的。很典型地innodb_numa_interleave为on只是更好地规避了前面所说的zone_reclaim_mode的kernel bug,修复后这个参数没有意义了。

AUTOMATIC NUMA BALANCING

RedHat 7默认会自动让内存或者进程就近迁移,让内存和CPU距离更近以达到最好的效果

Automatic NUMA balancing improves the performance of applications running on NUMA hardware systems. It is enabled by default on Red Hat Enterprise Linux 7 systems.

An application will generally perform best when the threads of its processes are accessing memory on the same NUMA node as the threads are scheduled. Automatic NUMA balancing moves tasks (which can be threads or processes) closer to the memory they are accessing. It also moves application data to memory closer to the tasks that reference it. This is all done automatically by the kernel when automatic NUMA balancing is active.

对应参数

1
cat /proc/sys/kernel/numa_balancing shows 1

监控

查找相应的内存和调度器事件

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
#perf stat -e sched:sched_stick_numa,sched:sched_move_numa,sched:sched_swap_numa,migrate:mm_migrate_pages,minor-faults -p 7191
Performance counter stats for process id '7191':

0 sched:sched_stick_numa (100.00%)
1 sched:sched_move_numa (100.00%)
0 sched:sched_swap_numa
0 migrate:mm_migrate_pages
286 minor-faults

# perf stat -e sched:sched_stick_numa,sched:sched_move_numa,sched:sched_swap_numa,migrate:mm_migrate_pages,minor-faults -p PID
...
1 sched:sched_stick_numa
3 sched:sched_move_numa
41 sched:sched_swap_numa
5,239 migrate:mm_migrate_pages
50,161 minor-faults

#perf stat -e sched:sched_stick_numa,sched:sched_move_numa,sched:sched_swap_numa,migrate:mm_migrate_pages,minor-faults -p 676322
Performance counter stats for process id '676322':

0 sched:sched_stick_numa
16 sched:sched_move_numa
0 sched:sched_swap_numa
24 migrate:mm_migrate_pages
2,079 minor-faults

总结

  • 放弃对NUMA的偏见吧,优先回收 PageCache 这个Bug早已修复了
  • 按NUMA绑定core收益巨大,即使只有两个NUMA的intel芯片,也有一倍以上的性能提升,在飞腾等其他芯片上收益更大
  • 没必要自欺欺人关掉NUMA了
  • RDS这样独占物理机的服务可以做到按NUMA来绑定core,收益可观
  • ECS售卖如果能够精确地按NUMA绑核的话性能,超卖比能高很多
  • 在刷tpcc数据的时候更应该开NUMA和正确绑核

我个人一直对集团所有机器默认关闭NUMA耿耿于怀,因为定制的物理机(BIOS也是定制的)BIOS默认就是关闭NUMA的,装机还得一台台手工打开(跪了,几十万台啊),算是理清了来龙去脉。因为一个kernel的bug让大家对NUMA一直有偏见,即使14年已经修复了,大家还是以讹传讹,没必要。

关于cpu为什么高但是没有产出的原因是因为CPU流水线长期stall,导致很低的IPC,所以性能自然上不去,可以看这篇文章

其他同学测试的结论:

  • Hadoop离线作业在 Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz 24 cores/socket * 2, Turbo Off 下打开NUMA后性能提升8%

一些其它不好解释的现象:

  1. 增加少量跨NUMA 的core进来时能增加QPS的,但是随着跨NUMA core越来越多(总core也越来越多)QPS反而会达到一个峰值后下降—效率低的core多了,抢走任务,执行得慢
  2. 压12-19和8-15同样8core,不跨NUMA的8-15性能只好5%左右(87873 VS 92801) — 难以解释
  3. 由1、2所知在测试少量core的时候跨NUMA性能下降体现不出来
  4. 在压0-31core的时候,如果运行 perf这个时候QPS反而会增加(13万上升到15万)— 抢走了一些CPU资源,让某个地方竞争反而减小了
  5. 综上在我个人理解是core越多的时候UPI压力到了瓶颈,才会出现加core性能反而下降

系列文章

CPU的制造和概念

[CPU 性能和Cache Line](/2021/05/16/CPU Cache Line 和性能/)

[Perf IPC以及CPU性能](/2021/05/16/Perf IPC以及CPU利用率/)

Intel、海光、鲲鹏920、飞腾2500 CPU性能对比

飞腾ARM芯片(FT2500)的性能测试

十年后数据库还是不敢拥抱NUMA?

一次海光物理机资源竞争压测的记录

[Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的](/2019/12/16/Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的/)

参考资料

https://www.redhat.com/files/summit/session-assets/2018/Performance-analysis-and-tuning-of-Red-Hat-Enterprise-Linux-Part-1.pdf

https://informixdba.wordpress.com/2015/10/16/zone-reclaim-mode/

https://queue.acm.org/detail.cfm?id=2513149

NUMA DEEP DIVE PART 1: FROM UMA TO NUMA 这是一个系列,都很干货,值得推荐

https://15721.courses.cs.cmu.edu/spring2016/papers/p743-leis.pdf Morsel-Driven Parallelism: A NUMA-Aware Query Evaluation Framework for the Many-Core Age 论文给出了很多numa-aware下的bandwidth、latency数据,以及对THC-H的影响

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

find me on twitter: @plantegg

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

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

争取在星球内:

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

为什么这么多CLOSE_WAIT

发表于 2021-04-06 | 分类于 TCP

为什么这么多CLOSE_WAIT

案例1:服务响应慢,经常连不上

应用发布新版本上线后,业务同学发现业务端口上的TCP连接处于CLOSE_WAIT状态的数量有积压,多的时候能堆积到几万个,有时候应用无法响应了

这个案例目标:怎么样才能获取举三反一的秘籍, 普通人为什么要案例来深化对理论知识的理解。

检查机器状态

img

img

从上述两个图中可以看到磁盘 sdb压力非常大,util经常会到 100%,这个时候对应地从top中也可以看到cpu wait%很高(这个ECS cpu本来竞争很激烈),st%一直非常高,所以整体留给应用的CPU不多,碰上磁盘缓慢的话,这时如果业务写日志是同步刷盘那么就会导致程序卡顿严重。

实际看到FGC的时间也是正常状态下的10倍了。

再看看实际上应用同步写日志到磁盘比较猛,平均20-30M,高的时候能到200M每秒。如果输出的时候磁盘卡住了那么就整个卡死了

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
#dstat
----total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai hiq siq| read writ| recv send| in out | int csw
4 1 89 5 0 0|1549M 8533M| 0 0 | 521k 830k|6065k 7134
3 1 95 0 0 0|3044k 19M|1765k 85k| 0 84k| 329k 7770
5 1 93 0 0 0|3380k 18M|4050k 142k| 0 0 | 300k 8008
7 1 91 1 0 1|2788k 227M|5094k 141k| 0 28k| 316k 8644
4 1 93 2 0 0|2788k 55M|2897k 63k| 0 68k| 274k 6453
6 1 91 1 0 0|4464k 24M|3683k 98k| 0 28k| 299k 7379
7 1 91 1 0 0| 10M 34M|3655k 130k| 0 208k| 375k 8417
3 1 87 8 0 0|6940k 33M|1335k 91k| 0 112k| 334k 7369
3 1 88 7 0 0|4932k 16M|1918k 61k| 0 44k| 268k 6542
7 1 86 6 0 0|5508k 20M|5377k 111k| 0 0 | 334k 7998
7 2 88 3 0 0|5628k 115M|4713k 104k| 0 0 | 280k 7392
4 1 95 0 0 0| 0 732k|2940k 85k| 0 76k| 189k 7682
3 1 96 0 0 0| 0 800k|1809k 68k| 0 16k| 181k 9640
7 2 76 14 0 1|6300k 38M|3834k 132k| 0 0 | 333k 7502
7 2 90 1 0 0|3896k 19M|3786k 93k| 0 0 | 357k 7578
4 1 94 0 0 0|5732k 29M|2906k 806k| 0 0 | 338k 8966
4 1 94 1 0 0|6044k 17M|2202k 95k| 0 0 | 327k 7573
4 1 95 1 0 0|3524k 17M|2277k 88k| 0 0 | 299k 6462
4 1 96 0 0 0| 456k 14M|2770k 91k| 60k 0 | 252k 6644
6 2 92 0 0 0| 0 12M|4251k 847k| 0 0 | 264k 10k
3 1 92 4 0 0| 788k 204M|1555k 43k| 0 0 | 249k 6215
6 1 86 6 0 0|7180k 20M|2073k 92k| 0 0 | 303k 7028
11 4 84 1 0 0|6116k 29M|3079k 99k| 28k 0 | 263k 6605

磁盘util 100%和CLOSE_WAIT强相关,也和理论比较符合,CLOSE_WAIT就是连接被动关闭端的应用没调 socket.close

img

原因推断:

1)新发布的代码需要消耗更多的CPU,代码增加了新的逻辑 //这只是一个微小的诱因

2)机器本身资源(CPU /IO)很紧张 这两个条件下导致应用响应缓慢。 目前看到的稳定重现条件就是重启一个业务节点,重启会触发业务节点之间重新同步数据,以及重新推送很多数据到客户端的新连接上,这两件事情都会让应用CPU占用飙升响应缓慢,响应慢了之后会导致更多的心跳失效进一步加剧数据同步,然后就雪崩恶化了。最后表现就是看到系统卡死了,也就是 tcp buffer 中的数据也不读走、连接也不 close,连接大量堆积在 close_wait状态

img

CLOSE_WAIT的原因分析

先看TCP连接状态图

这是网络、书本上凡是描述TCP状态一定会出现的状态图,理论上看这个图能解决任何TCP状态问题。

image.png

反复看这个图的右下部分的CLOSE_WAIT ,从这个图里可以得到如下结论:

CLOSE_WAIT 是被动关闭端在等待应用进程的关闭

基本上这一结论要能帮助解决所有CLOSE_WAIT相关的问题,如果不能说明对这个知识点理解的不够。

案例1结论

机器超卖严重、IO卡顿,导致应用线程卡顿,来不及调用 socket.close()

案例2:server端大量close_wait

用实际案例来检查自己对CLOSE_WAIT 理论(CLOSE_WAIT是被动关闭端在等待应用进程的关闭)的掌握 – 能不能用这个结论来解决实际问题。同时也可以看看自己从知识到问题的推理能力(跟前面的知识效率呼应一下)。

问题描述:

服务端出现大量CLOSE_WAIT ,并且个数正好 等于 somaxconn(调整somaxconn大小后 CLOSE_WAIT 也会跟着变成一样的值)

根据这个描述先不要往下看,自己推理分析下可能的原因。

我的推理如下:

从这里看起来,client跟server成功建立了 somaxconn个连接(somaxconn小于backlog,所以accept queue只有这么大),但是应用没有accept这些连接(连接建立三次握手不需要应用参与),导致这些连接一直在accept queue中。但是这些连接的状态已经是ESTABLISHED了,也就是client可以发送数据了,数据发送到server 后OS ack了,并放在os的tcp buffer中,应用一直没有accept也就没法读取数据。client 于是发送fin(可能是超时、也可能是简单发送数据任务完成了得结束连接),这时 Server上这个连接变成了CLOSE_WAIT .

也就是从开始到结束这些连接都在 accept queue中,没有被应用 accept,很快他们又因为client 发送 fin 包变成了CLOSE_WAIT ,所以始终看到的是服务端出现大量 CLOSE_WAIT 并且个数正好等于somaxconn(调整somaxconn后 CLOSE_WAIT 也会跟着变成一样的值)。

如下图所示,在连接进入accept queue后状态就是ESTABLISED了,也就是可以正常收发数据和fin了。client是感知不到 server是否accept()了,只是发了数据后server的 OS 代为保存在OS的TCP buffer中,因为应用没来取自然在CLOSE_WAIT 后应用也没有close(),所以一直维持CLOSE_WAIT 。

得检查server 应用为什么没有accept。

Recv-Q和Send-Q

如上是老司机的思路靠经验缺省了一些理论推理,缺省还是对理论理解不够, 这个分析抓住了 大量CLOSE_WAIT 个数正好 等于somaxconn(调整somaxconn后 CLOSE_WAIT 也会跟着变成一样的值)但是没有抓住 CLOSE_WAIT 背后的核心原因

更简单的推理

如果没有任何实战经验,只看上面的状态图的学霸应该是这样推理的:

看到server上有大量的CLOSE_WAIT说明 client主动断开了连接,server的OS收到client 发的fin,并回复了ack,这个过程不需要应用感知,进而连接从ESTABLISHED进入CLOSE_WAIT,此时在等待server上的应用调用close连关闭连接(处理完所有收发数据后才会调close()) —- 结论:server上的应用一直卡着没有调close().

CLOSE_WAIT 状态拆解

通常,CLOSE_WAIT 状态在服务器停留时间很短,如果你发现大量的 CLOSE_WAIT 状态,那么就意味着被动关闭的一方没有及时发出 FIN 包,一般有如下几种可能:

  • 程序问题:如果代码层面忘记了 close 相应的 socket 连接,那么自然不会发出 FIN 包,从而导致 CLOSE_WAIT 累积;或者代码不严谨,出现死循环之类的问题,导致即便后面写了 close 也永远执行不到。
  • 响应太慢或者超时设置过小:如果连接双方不和谐,一方不耐烦直接 timeout,另一方却还在忙于耗时逻辑,就会导致 close 被延后。响应太慢是首要问题,不过换个角度看,也可能是 timeout 设置过小。
  • BACKLOG 太大:此处的 backlog 不是 syn backlog,而是 accept 的 backlog,如果 backlog 太大的话,设想突然遭遇大访问量的话,即便响应速度不慢,也可能出现来不及消费的情况,导致多余的请求还在队列里就被对方关闭了。

如果你通过「netstat -ant」或者「ss -ant」命令发现了很多 CLOSE_WAIT 连接,请注意结果中的「Recv-Q」和「Local Address」字段,通常「Recv-Q」会不为空,它表示应用还没来得及接收数据,而「Local Address」表示哪个地址和端口有问题,我们可以通过「lsof -i:」来确认端口对应运行的是什么程序以及它的进程号是多少。

如果是我们自己写的一些程序,比如用 HttpClient 自定义的蜘蛛,那么八九不离十是程序问题,如果是一些使用广泛的程序,比如 Tomcat 之类的,那么更可能是响应速度太慢或者 timeout 设置太小或者 BACKLOG 设置过大导致的故障。

看完这段 CLOSE_WAIT 更具体深入点的分析后再来分析上面的案例看看,能否推导得到正确的结论。

一些疑问

连接都没有被accept(), client端就能发送数据了?

答:是的。只要这个连接在OS看来是ESTABLISHED的了就可以,因为握手、接收数据都是由内核完成的,内核收到数据后会先将数据放在内核的tcp buffer中,然后os回复ack。另外三次握手之后client端是没法知道server端是否accept()了。

CLOSE_WAIT与accept queue有关系吗?

答:没有关系。只是本案例中因为open files不够了,影响了应用accept(), 导致accept queue满了,同时因为即使应用不accept(三次握手后,server端是否accept client端无法感知),client也能发送数据和发 fin断连接,这些响应都是os来负责,跟上层应用没关系,连接从握手到ESTABLISHED再到CLOSE_WAIT都不需要fd,也不需要应用参与。CLOSE_WAIT只跟应用不调 close() 有关系。

CLOSE_WAIT与accept queue为什么刚好一致并且联动了?

答:这里他们的数量刚好一致是因为所有新建连接都没有accept,堵在queue中。同时client发现问题后把所有连接都fin了,也就是所有queue中的连接从来没有被accept过,但是他们都是ESTABLISHED,过一阵子之后client端发了fin所以所有accept queue中的连接又变成了 CLOSE_WAIT, 所以二者刚好一致并且联动了

CLOSE_WAIT与TIME_WAIT

  • 简单说就是CLOSE_WAIT出现在被动断开连接端,一般过多就不太正常;
  • TIME_WAIT出现在主动断开连接端,是正常现象,多出现在短连接场景下

openfiles 和 accept()的关系是?

答:accept()的时候才会创建文件句柄,消耗openfiles

一个连接如果在accept queue中了,但是还没有被应用 accept,那么这个时候在server上看这个连接的状态他是ESTABLISHED的吗?

答:是

如果server的os参数 open files到了上限(就是os没法打开新的文件句柄了)会导致这个accept queue中的连接一直没法被accept对吗?

答:对

如果通过gdb attach 应用进程,故意让进程 accept,这个时候client还能连上应用吗?

答: 能,这个时候在client和server两边看到的连接状态都是 ESTABLISHED,只是Server上的全连接队列占用加1。连接握手并切换到ESTABLISHED状态都是由OS来负责的,应用不参与,ESTABLISHED后应用才能accept,进而收发数据。也就是能放入到全连接队列里面的连接肯定都是 ESTABLISHED 状态的了

接着上面的问题,如果新连接继续连接进而全连接队列满了呢?

答:那就连不上了,server端的OS因为全连接队列满了直接扔掉第一个syn握手包,这个时候连接在client端是SYN_SENT,Server端没有这个连接,这是因为syn到server端就直接被OS drop 了。

1
2
3
//如下图,本机测试,只有一个client端发起的syn_send, 3306的server端没有任何连接
$netstat -antp |grep -i 127.0.0.1:3306
tcp 0 1 127.0.0.1:61106 127.0.0.1:3306 SYN_SENT 21352/telnet

能进入到accept queue中的连接都是 ESTABLISHED,不管用户态有没有accept,用户态accept后队列大小减1

如果一个连接握手成功进入到accept queue但是应用accept前被对方RESET了呢?

答: 如果此时收到对方的RESET了,那么OS会释放这个连接。但是内核认为所有 listen 到的连接, 必须要 accept 走, 因为用户有权利知道有过这么一个连接存在过。所以OS不会到全连接队列拿掉这个连接,全连接队列数量也不会减1,直到应用accept这个连接,然后read/write才发现这个连接断开了,报communication failure异常

什么时候连接状态变成 ESTABLISHED

三次握手成功就变成 ESTABLISHED 了,不需要用户态来accept,如果握手第三步的时候OS发现全连接队列满了,这时OS会扔掉这个第三次握手ack,并重传握手第二步的syn+ack, 在OS端这个连接还是 SYN_RECV 状态的,但是client端是 ESTABLISHED状态的了。

这是在4000(tearbase)端口上全连接队列没满,但是应用不再accept了,nc用12346端口去连4000(tearbase)端口的结果

1
2
3
4
5
6
# netstat -at |grep ":12346 "
tcp 0 0 dcep-blockchain-1:12346 dcep-blockchai:terabase ESTABLISHED //server
tcp 0 0 dcep-blockchai:terabase dcep-blockchain-1:12346 ESTABLISHED //client
[root@dcep-blockchain-1 cfl-sm2-sm3]# ss -lt
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 73 1024 *:terabase *:*

这是在4000(tearbase)端口上全连接队列满掉后,nc用12346端口去连4000(tearbase)端口的结果

1
2
3
4
5
6
# netstat -at |grep ":12346 "  
tcp 0 0 dcep-blockchai:terabase dcep-blockchain-1:12346 SYN_RECV //server
tcp 0 0 dcep-blockchain-1:12346 dcep-blockchai:terabase ESTABLISHED //client
# ss -lt
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 1025 1024 *:terabase *:*

Intel AMD 鲲鹏 海光 飞腾性能PK

发表于 2021-03-21 | 分类于 CPU

Intel AMD 鲲鹏 海光 飞腾性能PK

前言

本文在sysbench、tpcc等实践场景下对多款CPU的性能进行对比,同时分析各款CPU的硬件指标,最后分析不同场景下的实际性能和核心参数的关系。

本文的姊妹篇:十年后数据库还是不敢拥抱NUMA? 主要讲述的是 同一块CPU的不同NUMA结构配置带来的几倍性能差异,这些性能差异原因也可以从本文最后时延测试数据得到印证,一起阅读效果更好。

性能定义

同一个平台下(X86、ARM是两个平台)编译好的程序可以认为他们的 指令 数是一样的,那么执行效率就是每个时钟周期所能执行的指令数量了。

执行指令数量第一取决的就是CPU主频了,但是目前主流CPU都是2.5G左右,另外就是单核下的并行度(多发射)以及多核,再就是分支预测等,这些基本归结到了访问内存的延时。

X86和ARM这两不同平台首先指令就不一样了,然后还有上面所说的主频、内存时延的差异

IPC的说明:

IPC: insns per cycle insn/cycles 也就是每个时钟周期能执行的指令数量,越大程序跑的越快

程序的执行时间 = 指令数/(主频*IPC) //单核下,多核的话再除以核数

参与比较的几款CPU参数

先来看看测试所用到的几款CPU的主要指标,大家关注下主频、各级cache大小、numa结构

Hygon 7280

Hygon 7280 就是AMD Zen架构,最大IPC能到5.

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
架构:                           x86_64
CPU 运行模式: 32-bit, 64-bit
字节序: Little Endian
Address sizes: 43 bits physical, 48 bits virtual
CPU: 128
在线 CPU 列表: 0-127
每个核的线程数: 2
每个座的核数: 32
座: 2
NUMA 节点: 8
厂商 ID: HygonGenuine
CPU 系列: 24
型号: 1
型号名称: Hygon C86 7280 32-core Processor
步进: 1
CPU MHz: 2194.586
BogoMIPS: 3999.63
虚拟化: AMD-V
L1d 缓存: 2 MiB
L1i 缓存: 4 MiB
L2 缓存: 32 MiB
L3 缓存: 128 MiB
NUMA 节点0 CPU: 0-7,64-71
NUMA 节点1 CPU: 8-15,72-79
NUMA 节点2 CPU: 16-23,80-87
NUMA 节点3 CPU: 24-31,88-95
NUMA 节点4 CPU: 32-39,96-103
NUMA 节点5 CPU: 40-47,104-111
NUMA 节点6 CPU: 48-55,112-119
NUMA 节点7 CPU: 56-63,120-127

AMD EPYC 7H12

AMD EPYC 7H12 64-Core(ECS,非物理机),最大IPC能到5.

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
# lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 64
On-line CPU(s) list: 0-63
Thread(s) per core: 2
Core(s) per socket: 16
座: 2
NUMA 节点: 2
厂商 ID: AuthenticAMD
CPU 系列: 23
型号: 49
型号名称: AMD EPYC 7H12 64-Core Processor
步进: 0
CPU MHz: 2595.124
BogoMIPS: 5190.24
虚拟化: AMD-V
超管理器厂商: KVM
虚拟化类型: 完全
L1d 缓存: 32K
L1i 缓存: 32K
L2 缓存: 512K
L3 缓存: 16384K
NUMA 节点0 CPU: 0-31
NUMA 节点1 CPU: 32-63

Intel

这次对比测试用到了两块Intel CPU,分别是 8163、8269 。他们的信息如下,最大IPC 是4:

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
#lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 96
On-line CPU(s) list: 0-95
Thread(s) per core: 2
Core(s) per socket: 24
Socket(s): 2
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 85
Model name: Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz
Stepping: 4
CPU MHz: 2499.121
CPU max MHz: 3100.0000
CPU min MHz: 1000.0000
BogoMIPS: 4998.90
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 1024K
L3 cache: 33792K
NUMA node0 CPU(s): 0-95

-----8269CY
#lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 104
On-line CPU(s) list: 0-103
Thread(s) per core: 2
Core(s) per socket: 26
Socket(s): 2
NUMA node(s): 2
Vendor ID: GenuineIntel
CPU family: 6
Model: 85
Model name: Intel(R) Xeon(R) Platinum 8269CY CPU @ 2.50GHz
Stepping: 7
CPU MHz: 3200.000
CPU max MHz: 3800.0000
CPU min MHz: 1200.0000
BogoMIPS: 4998.89
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 1024K
L3 cache: 36608K
NUMA node0 CPU(s): 0-25,52-77
NUMA node1 CPU(s): 26-51,78-103

鲲鹏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
[root@ARM 19:15 /root/lmbench3]
#numactl -H
available: 4 nodes (0-3)
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
node 0 size: 192832 MB
node 0 free: 146830 MB
node 1 cpus: 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
node 1 size: 193533 MB
node 1 free: 175354 MB
node 2 cpus: 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
node 2 size: 193533 MB
node 2 free: 175718 MB
node 3 cpus: 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
node 3 size: 193532 MB
node 3 free: 183643 MB
node distances:
node 0 1 2 3
0: 10 12 20 22
1: 12 10 22 24
2: 20 22 10 12
3: 22 24 12 10

node 0 <------------ socket distance ------------> node 2
| (die distance) | (die distance)
node 1 node 3


#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 //一个Die下24core共享24M L3,每个core 1MB
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

飞腾2500

飞腾2500用nop去跑IPC的话,只能到1,但是跑其它代码能到2.33,理论值据说也是4但是我没跑到过

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

单核以及超线程计算Prime性能比较

测试命令,这个测试命令无论在哪个CPU下,用2个物理核用时都是一个物理核的一半,所以这个计算是可以完全并行的

1
taskset -c 1 /usr/bin/sysbench --num-threads=1 --test=cpu --cpu-max-prime=50000 run //单核绑一个core; 2个thread就绑一对HT

测试结果为耗时,单位秒

测试项 AMD EPYC 7H12 2.5G CentOS 7.9 Hygon 7280 2.1GHz CentOS Hygon 7280 2.1GHz 麒麟 Intel 8269 2.50G Intel 8163 CPU @ 2.50GHz Intel E5-2682 v4 @ 2.50GHz
单核 prime 50000 耗时 59秒 IPC 0.56 77秒 IPC 0.55 89秒 IPC 0.56; 83 0.41 105秒 IPC 0.41 109秒 IPC 0.39
HT prime 50000 耗时 57秒 IPC 0.31 74秒 IPC 0.29 87秒 IPC 0.29 48 0.35 60秒 IPC 0.36 74秒 IPC 0.29

从上面的测试结果来看,简单纯计算场景下 AMD/海光 的单核能力还是比较强的,但是超线程完全不给力(数据库场景超线程就给力了);而Intel的超线程非常给力,一对超线程能达到单物理core的1.8倍,并且从E5到8269更是好了不少。
ARM基本都没有超线程所有没有跑鲲鹏、飞腾。

计算Prime毕竟太简单,让我们来看看他们在数据库下的真实能力吧

对比MySQL sysbench和tpcc性能

MySQL 默认用5.7.34社区版,操作系统默认是centos,测试中所有mysqld都做了绑核,一样的压力配置尽量将CPU跑到100%, HT表示将mysqld绑定到一对HT核。

sysbench点查

测试命令类似如下:

1
sysbench --test='/usr/share/doc/sysbench/tests/db/select.lua' --oltp_tables_count=1 --report-interval=1 --oltp-table-size=10000000  --mysql-port=3307 --mysql-db=sysbench_single --mysql-user=root --mysql-password='Bj6f9g96!@#'  --max-requests=0   --oltp_skip_trx=on --oltp_auto_inc=on  --oltp_range_size=5  --mysql-table-engine=innodb --rand-init=on   --max-time=300 --mysql-host=x86.51 --num-threads=4 run

测试结果分别取QPS/IPC两个数据(测试中的差异AMD、Hygon CPU跑在CentOS7.9, intel CPU、Kunpeng 920 跑在AliOS上, xdb表示用集团的xdb替换社区的MySQL Server, 麒麟是国产OS):

测试核数 AMD EPYC 7H12 2.5G Hygon 7280 2.1G Hygon 7280 2.1GHz 麒麟 Intel 8269 2.50G Intel 8163 2.50G Intel 8163 2.50G XDB5.7 鲲鹏 920-4826 2.6G 鲲鹏 920-4826 2.6G XDB8.0 FT2500 alisql 8.0 本地–socket
单核 24674 0.54 13441 0.46 10236 0.39 28208 0.75 25474 0.84 29376 0.89 9694 0.49 8301 0.46 3602 0.53
一对HT 36157 0.42 21747 0.38 19417 0.37 36754 0.49 35894 0.6 40601 0.65 无HT 无HT 无HT
4物理核 94132 0.52 49822 0.46 38033 0.37 90434 0.69 350% 87254 0.73 106472 0.83 34686 0.42 28407 0.39 14232 0.53
16物理核 325409 0.48 171630 0.38 134980 0.34 371718 0.69 1500% 332967 0.72 446290 0.85 //16核比4核好! 116122 0.35 94697 0.33 59199 0.6 8core:31210 0.59
32物理核 542192 0.43 298716 0.37 255586 0.33 642548 0.64 2700% 588318 0.67 598637 0.81 CPU 2400% 228601 0.36 177424 0.32 114020 0.65
说明:麒麟OS下CPU很难跑满,大致能跑到90%-95%左右,麒麟上装的社区版MySQL-5.7.29;飞腾要特别注意mysqld所在socket,同时以上飞腾数据都是走--socket压测锁的,32core走网络压测QPS为:99496(15%的网络损耗)

从上面的结果先看单物理核能力ARM 和 X86之间的差异还是很明显的

tpcc 1000仓

测试结果(测试中Hygon 7280分别跑在CentOS7.9和麒麟上, 鲲鹏/intel CPU 跑在AliOS、麒麟是国产OS):

tpcc测试数据,结果为1000仓,tpmC (NewOrders) ,未标注CPU 则为跑满了

测试核数 Intel 8269 2.50G Intel 8163 2.50G Hygon 7280 2.1GHz 麒麟 Hygon 7280 2.1G CentOS 7.9 鲲鹏 920-4826 2.6G 鲲鹏 920-4826 2.6G XDB8.0
1物理核 12392 9902 4706 7011 6619 4653
一对HT 17892 15324 8950 11778 无HT 无HT
4物理核 51525 40877 19387 380% 30046 23959 20101
8物理核 100792 81799 39664 750% 60086 42368 40572
16物理核 160798 抖动 140488 CPU抖动 75013 1400% 106419 1300-1550% 70581 1200% 79844
24物理核 188051 164757 1600-2100% 100841 1800-2000% 130815 1600-2100% 88204 1600% 115355
32物理核 195292 185171 2000-2500% 116071 1900-2400% 142746 1800-2400% 102089 1900% 143567
48物理核 19969l 195730 2100-2600% 128188 2100-2800% 149782 2000-2700% 116374 2500% 206055 4500%

测试过程CPU均跑满(未跑满的话会标注出来),IPC跑不起来性能就必然低,超线程虽然总性能好了但是会导致IPC降低(参考前面的公式)。可以看到对本来IPC比较低的场景,启用超线程后一般对性能会提升更大一些。

tpcc并发到一定程度后主要是锁导致性能上不去,所以超多核意义不大,可以做分库分表搞多个mysqld实例

比如在Hygon 7280 2.1GHz 麒麟上起两个MySQLD实例,每个实例各绑定32物理core,性能刚好翻倍:image-20210823082702539

32核的时候对比下MySQL 社区版在Hygon7280和Intel 8163下的表现,IPC的差异还是很明显的,基本和TPS差异一致:

image-20210817181752243

从sysbench和tpcc测试结果来看AMD和Intel差异不大,ARM和X86差异比较大,国产CPU还有很大的进步空间。就像前面所说抛开指令集的差异,主频差不多,内存管够为什么还有这么大的性能差别呢?

三款CPU的性能指标

下面让我们回到硬件本身的数据来看这个问题

先记住这个图,描述的是CPU访问寄存器、L1 cache、L2 cache等延时,关键记住他们的差异
各级延时

接下来用lmbench来测试各个机器的内存延时

stream主要用于测试带宽,对应的时延是在带宽跑满情况下的带宽。

lat_mem_rd用来测试操作不同数据大小的时延。

飞腾2500

用stream测试带宽和latency,可以看到带宽随着numa距离不断减少、对应的latency不断增加,到最近的numa node有10%的损耗,这个损耗和numactl给出的距离完全一致。跨socket访问内存latency是node内的3倍,带宽是三分之一,但是socket1性能和socket0性能完全一致。从这个延时来看如果要是跑一个32core的实例性能一定不会太好,并且抖动剧烈

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
86
87
88
89
90
91
92
93
94
95
96
97
time for i in $(seq 7 8 128); do echo $i; numactl -C $i -m 0 ./bin/stream -W 5 -N 5 -M 64M; done

#numactl -C 7 -m 0 ./bin/stream -W 5 -N 5 -M 64M
STREAM copy latency: 2.84 nanoseconds
STREAM copy bandwidth: 5638.21 MB/sec
STREAM scale latency: 2.72 nanoseconds
STREAM scale bandwidth: 5885.97 MB/sec
STREAM add latency: 2.26 nanoseconds
STREAM add bandwidth: 10615.13 MB/sec
STREAM triad latency: 4.53 nanoseconds
STREAM triad bandwidth: 5297.93 MB/sec

#numactl -C 7 -m 1 ./bin/stream -W 5 -N 5 -M 64M
STREAM copy latency: 3.16 nanoseconds
STREAM copy bandwidth: 5058.71 MB/sec
STREAM scale latency: 3.15 nanoseconds
STREAM scale bandwidth: 5074.78 MB/sec
STREAM add latency: 2.35 nanoseconds
STREAM add bandwidth: 10197.36 MB/sec
STREAM triad latency: 5.12 nanoseconds
STREAM triad bandwidth: 4686.37 MB/sec

#numactl -C 7 -m 2 ./bin/stream -W 5 -N 5 -M 64M
STREAM copy latency: 3.85 nanoseconds
STREAM copy bandwidth: 4150.98 MB/sec
STREAM scale latency: 3.95 nanoseconds
STREAM scale bandwidth: 4054.30 MB/sec
STREAM add latency: 2.64 nanoseconds
STREAM add bandwidth: 9100.12 MB/sec
STREAM triad latency: 6.39 nanoseconds
STREAM triad bandwidth: 3757.70 MB/sec

#numactl -C 7 -m 3 ./bin/stream -W 5 -N 5 -M 64M
STREAM copy latency: 3.69 nanoseconds
STREAM copy bandwidth: 4340.24 MB/sec
STREAM scale latency: 3.62 nanoseconds
STREAM scale bandwidth: 4422.18 MB/sec
STREAM add latency: 2.47 nanoseconds
STREAM add bandwidth: 9704.82 MB/sec
STREAM triad latency: 5.74 nanoseconds
STREAM triad bandwidth: 4177.85 MB/sec

[root@101a05001.cloud.a05.am11 /root/lmbench3]
#numactl -C 7 -m 7 ./bin/stream -W 5 -N 5 -M 64M
STREAM copy latency: 3.95 nanoseconds
STREAM copy bandwidth: 4051.51 MB/sec
STREAM scale latency: 3.94 nanoseconds
STREAM scale bandwidth: 4060.63 MB/sec
STREAM add latency: 2.54 nanoseconds
STREAM add bandwidth: 9434.51 MB/sec
STREAM triad latency: 6.13 nanoseconds
STREAM triad bandwidth: 3913.36 MB/sec

[root@101a05001.cloud.a05.am11 /root/lmbench3]
#numactl -C 7 -m 10 ./bin/stream -W 5 -N 5 -M 64M
STREAM copy latency: 8.80 nanoseconds
STREAM copy bandwidth: 1817.78 MB/sec
STREAM scale latency: 8.59 nanoseconds
STREAM scale bandwidth: 1861.65 MB/sec
STREAM add latency: 5.55 nanoseconds
STREAM add bandwidth: 4320.68 MB/sec
STREAM triad latency: 13.94 nanoseconds
STREAM triad bandwidth: 1721.76 MB/sec

[root@101a05001.cloud.a05.am11 /root/lmbench3]
#numactl -C 7 -m 11 ./bin/stream -W 5 -N 5 -M 64M
STREAM copy latency: 9.27 nanoseconds
STREAM copy bandwidth: 1726.52 MB/sec
STREAM scale latency: 9.31 nanoseconds
STREAM scale bandwidth: 1718.10 MB/sec
STREAM add latency: 5.65 nanoseconds
STREAM add bandwidth: 4250.89 MB/sec
STREAM triad latency: 14.09 nanoseconds
STREAM triad bandwidth: 1703.66 MB/sec

//在另外一个socket上测试本numa,和node0性能完全一致
[root@101a0500 /root/lmbench3]
#numactl -C 88 -m 11 ./bin/stream -W 5 -N 5 -M 64M
STREAM copy latency: 2.93 nanoseconds
STREAM copy bandwidth: 5454.67 MB/sec
STREAM scale latency: 2.96 nanoseconds
STREAM scale bandwidth: 5400.03 MB/sec
STREAM add latency: 2.28 nanoseconds
STREAM add bandwidth: 10543.42 MB/sec
STREAM triad latency: 4.52 nanoseconds
STREAM triad bandwidth: 5308.40 MB/sec

[root@101a0500 /root/lmbench3]
#numactl -C 7 -m 15 ./bin/stream -W 5 -N 5 -M 64M
STREAM copy latency: 8.73 nanoseconds
STREAM copy bandwidth: 1831.77 MB/sec
STREAM scale latency: 8.81 nanoseconds
STREAM scale bandwidth: 1815.13 MB/sec
STREAM add latency: 5.63 nanoseconds
STREAM add bandwidth: 4265.21 MB/sec
STREAM triad latency: 13.09 nanoseconds
STREAM triad bandwidth: 1833.68 MB/sec

Lat_mem_rd 用cpu7访问node0和node15对比结果,随着数据的加大,延时在加大,64M时能有3倍差距,和上面测试一致

下图 第一列 表示读写数据的大小(单位M),第二列表示访问延时(单位纳秒),一般可以看到在L1/L2/L3 cache大小的地方延时会有跳跃,远超过L3大小后,延时就是内存延时了

image-20210924185044090

测试命令如下

1
numactl -C 7 -m 0 ./bin/lat_mem_rd -W 5 -N 5 -t 64M  //-C 7 cpu 7, -m 0 node0, -W 热身 -t stride

同样的机型,开关numa的测试结果,关numa 时延、带宽都差了几倍,所以一定要开NUMA

image-20210924192330025

鲲鹏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
#for i in $(seq 0 15); do echo core:$i; numactl -N $i -m 7 ./bin/stream  -W 5 -N 5 -M 64M; done
STREAM copy latency: 1.84 nanoseconds
STREAM copy bandwidth: 8700.75 MB/sec
STREAM scale latency: 1.86 nanoseconds
STREAM scale bandwidth: 8623.60 MB/sec
STREAM add latency: 2.18 nanoseconds
STREAM add bandwidth: 10987.04 MB/sec
STREAM triad latency: 3.03 nanoseconds
STREAM triad bandwidth: 7926.87 MB/sec

#numactl -C 7 -m 1 ./bin/stream -W 5 -N 5 -M 64M
STREAM copy latency: 2.05 nanoseconds
STREAM copy bandwidth: 7802.45 MB/sec
STREAM scale latency: 2.08 nanoseconds
STREAM scale bandwidth: 7681.87 MB/sec
STREAM add latency: 2.19 nanoseconds
STREAM add bandwidth: 10954.76 MB/sec
STREAM triad latency: 3.17 nanoseconds
STREAM triad bandwidth: 7559.86 MB/sec

#numactl -C 7 -m 2 ./bin/stream -W 5 -N 5 -M 64M
STREAM copy latency: 3.51 nanoseconds
STREAM copy bandwidth: 4556.86 MB/sec
STREAM scale latency: 3.58 nanoseconds
STREAM scale bandwidth: 4463.66 MB/sec
STREAM add latency: 2.71 nanoseconds
STREAM add bandwidth: 8869.79 MB/sec
STREAM triad latency: 5.92 nanoseconds
STREAM triad bandwidth: 4057.12 MB/sec

[root@ARM 19:14 /root/lmbench3]
#numactl -C 7 -m 3 ./bin/stream -W 5 -N 5 -M 64M
STREAM copy latency: 3.94 nanoseconds
STREAM copy bandwidth: 4064.25 MB/sec
STREAM scale latency: 3.82 nanoseconds
STREAM scale bandwidth: 4188.67 MB/sec
STREAM add latency: 2.86 nanoseconds
STREAM add bandwidth: 8390.70 MB/sec
STREAM triad latency: 4.78 nanoseconds
STREAM triad bandwidth: 5024.25 MB/sec

#numactl -C 24 -m 3 ./bin/stream -W 5 -N 5 -M 64M
STREAM copy latency: 4.10 nanoseconds
STREAM copy bandwidth: 3904.63 MB/sec
STREAM scale latency: 4.03 nanoseconds
STREAM scale bandwidth: 3969.41 MB/sec
STREAM add latency: 3.07 nanoseconds
STREAM add bandwidth: 7816.08 MB/sec
STREAM triad latency: 5.06 nanoseconds
STREAM triad bandwidth: 4738.66 MB/sec

海光7280

可以看到跨numa(一个numa也就是一个socket,等同于跨socket)RT从1.5上升到2.5,这个数据比鲲鹏920要好很多。
这里还会测试同一块CPU设置不同数量的numa node对性能的影响,所以接下来的测试会列出numa node数量

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
[root@hygon8 14:32 /root/lmbench-master]
#lscpu
架构: x86_64
CPU 运行模式: 32-bit, 64-bit
字节序: Little Endian
Address sizes: 43 bits physical, 48 bits virtual
CPU: 128
在线 CPU 列表: 0-127
每个核的线程数: 2
每个座的核数: 32
座: 2
NUMA 节点: 8
厂商 ID: HygonGenuine
CPU 系列: 24
型号: 1
型号名称: Hygon C86 7280 32-core Processor
步进: 1
CPU MHz: 2194.586
BogoMIPS: 3999.63
虚拟化: AMD-V
L1d 缓存: 2 MiB
L1i 缓存: 4 MiB
L2 缓存: 32 MiB
L3 缓存: 128 MiB
NUMA 节点0 CPU: 0-7,64-71
NUMA 节点1 CPU: 8-15,72-79
NUMA 节点2 CPU: 16-23,80-87
NUMA 节点3 CPU: 24-31,88-95
NUMA 节点4 CPU: 32-39,96-103
NUMA 节点5 CPU: 40-47,104-111
NUMA 节点6 CPU: 48-55,112-119
NUMA 节点7 CPU: 56-63,120-127

//可以看到7号core比15、23、31号core明显要快,就近访问node 0的内存,跨numa node(跨Die)没有内存交织分配
[root@hygon8 14:32 /root/lmbench-master]
#time for i in $(seq 7 8 64); do echo $i; numactl -C $i -m 0 ./bin/stream -W 5 -N 5 -M 64M; done
7
STREAM copy latency: 1.38 nanoseconds
STREAM copy bandwidth: 11559.53 MB/sec
STREAM scale latency: 1.16 nanoseconds
STREAM scale bandwidth: 13815.87 MB/sec
STREAM add latency: 1.40 nanoseconds
STREAM add bandwidth: 17145.85 MB/sec
STREAM triad latency: 1.44 nanoseconds
STREAM triad bandwidth: 16637.18 MB/sec
15
STREAM copy latency: 1.67 nanoseconds
STREAM copy bandwidth: 9591.77 MB/sec
STREAM scale latency: 1.56 nanoseconds
STREAM scale bandwidth: 10242.50 MB/sec
STREAM add latency: 1.45 nanoseconds
STREAM add bandwidth: 16581.00 MB/sec
STREAM triad latency: 2.00 nanoseconds
STREAM triad bandwidth: 12028.83 MB/sec
23
STREAM copy latency: 1.65 nanoseconds
STREAM copy bandwidth: 9701.49 MB/sec
STREAM scale latency: 1.53 nanoseconds
STREAM scale bandwidth: 10427.98 MB/sec
STREAM add latency: 1.42 nanoseconds
STREAM add bandwidth: 16846.10 MB/sec
STREAM triad latency: 1.97 nanoseconds
STREAM triad bandwidth: 12189.72 MB/sec
31
STREAM copy latency: 1.64 nanoseconds
STREAM copy bandwidth: 9742.86 MB/sec
STREAM scale latency: 1.52 nanoseconds
STREAM scale bandwidth: 10510.80 MB/sec
STREAM add latency: 1.45 nanoseconds
STREAM add bandwidth: 16559.86 MB/sec
STREAM triad latency: 1.92 nanoseconds
STREAM triad bandwidth: 12490.01 MB/sec
39
STREAM copy latency: 2.55 nanoseconds
STREAM copy bandwidth: 6286.25 MB/sec
STREAM scale latency: 2.51 nanoseconds
STREAM scale bandwidth: 6383.11 MB/sec
STREAM add latency: 1.76 nanoseconds
STREAM add bandwidth: 13660.83 MB/sec
STREAM triad latency: 3.68 nanoseconds
STREAM triad bandwidth: 6523.02 MB/sec

如果这种芯片在bios里设置Die interleaving,4块die当成一个numa node吐出来给OS

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
#lscpu
架构: x86_64
CPU 运行模式: 32-bit, 64-bit
字节序: Little Endian
Address sizes: 43 bits physical, 48 bits virtual
CPU: 128
在线 CPU 列表: 0-127
每个核的线程数: 2
每个座的核数: 32
座: 2
NUMA 节点: 2
厂商 ID: HygonGenuine
CPU 系列: 24
型号: 1
型号名称: Hygon C86 7280 32-core Processor
步进: 1
CPU MHz: 2108.234
BogoMIPS: 3999.45
虚拟化: AMD-V
L1d 缓存: 2 MiB
L1i 缓存: 4 MiB
L2 缓存: 32 MiB
L3 缓存: 128 MiB
//注意这里bios配置了Die Interleaving Enable
//表示每路内多个Die内存交织分配,这样整个一个socket就是一个大Die
NUMA 节点0 CPU: 0-31,64-95
NUMA 节点1 CPU: 32-63,96-127


//enable die interleaving 后继续streaming测试
//最终测试结果表现就是7/15/23/31 core性能一致,因为默认一个numa内内存交织分配
//可以看到同一路下的四个die内存交织访问,所以4个node内存延时一样了(被平均),都不如8node就近快
[root@hygon3 16:09 /root/lmbench-master]
#time for i in $(seq 7 8 64); do echo $i; numactl -C $i -m 0 ./bin/stream -W 5 -N 5 -M 64M; done
7
STREAM copy latency: 1.48 nanoseconds
STREAM copy bandwidth: 10782.58 MB/sec
STREAM scale latency: 1.20 nanoseconds
STREAM scale bandwidth: 13364.38 MB/sec
STREAM add latency: 1.46 nanoseconds
STREAM add bandwidth: 16408.32 MB/sec
STREAM triad latency: 1.53 nanoseconds
STREAM triad bandwidth: 15696.00 MB/sec
15
STREAM copy latency: 1.51 nanoseconds
STREAM copy bandwidth: 10601.25 MB/sec
STREAM scale latency: 1.24 nanoseconds
STREAM scale bandwidth: 12855.87 MB/sec
STREAM add latency: 1.46 nanoseconds
STREAM add bandwidth: 16382.42 MB/sec
STREAM triad latency: 1.53 nanoseconds
STREAM triad bandwidth: 15691.48 MB/sec
23
STREAM copy latency: 1.50 nanoseconds
STREAM copy bandwidth: 10700.61 MB/sec
STREAM scale latency: 1.27 nanoseconds
STREAM scale bandwidth: 12634.63 MB/sec
STREAM add latency: 1.47 nanoseconds
STREAM add bandwidth: 16370.67 MB/sec
STREAM triad latency: 1.55 nanoseconds
STREAM triad bandwidth: 15455.75 MB/sec
31
STREAM copy latency: 1.50 nanoseconds
STREAM copy bandwidth: 10637.39 MB/sec
STREAM scale latency: 1.25 nanoseconds
STREAM scale bandwidth: 12778.99 MB/sec
STREAM add latency: 1.46 nanoseconds
STREAM add bandwidth: 16420.65 MB/sec
STREAM triad latency: 1.61 nanoseconds
STREAM triad bandwidth: 14946.80 MB/sec
39
STREAM copy latency: 2.35 nanoseconds
STREAM copy bandwidth: 6807.09 MB/sec
STREAM scale latency: 2.32 nanoseconds
STREAM scale bandwidth: 6906.93 MB/sec
STREAM add latency: 1.63 nanoseconds
STREAM add bandwidth: 14729.23 MB/sec
STREAM triad latency: 3.36 nanoseconds
STREAM triad bandwidth: 7151.67 MB/sec
47
STREAM copy latency: 2.31 nanoseconds
STREAM copy bandwidth: 6938.47 MB/sec

intel 8269CY

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
lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 104
On-line CPU(s) list: 0-103
Thread(s) per core: 2
Core(s) per socket: 26
Socket(s): 2
NUMA node(s): 2
Vendor ID: GenuineIntel
CPU family: 6
Model: 85
Model name: Intel(R) Xeon(R) Platinum 8269CY CPU @ 2.50GHz
Stepping: 7
CPU MHz: 3200.000
CPU max MHz: 3800.0000
CPU min MHz: 1200.0000
BogoMIPS: 4998.89
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 1024K
L3 cache: 36608K
NUMA node0 CPU(s): 0-25,52-77
NUMA node1 CPU(s): 26-51,78-103

[root@numaopen.cloud.et93 /home/ren/lmbench3]
#time for i in $(seq 0 8 51); do echo $i; numactl -C $i -m 0 ./bin/stream -W 5 -N 5 -M 64M; done
0
STREAM copy latency: 1.15 nanoseconds
STREAM copy bandwidth: 13941.80 MB/sec
STREAM scale latency: 1.16 nanoseconds
STREAM scale bandwidth: 13799.89 MB/sec
STREAM add latency: 1.31 nanoseconds
STREAM add bandwidth: 18318.23 MB/sec
STREAM triad latency: 1.56 nanoseconds
STREAM triad bandwidth: 15356.72 MB/sec
16
STREAM copy latency: 1.12 nanoseconds
STREAM copy bandwidth: 14293.68 MB/sec
STREAM scale latency: 1.13 nanoseconds
STREAM scale bandwidth: 14162.47 MB/sec
STREAM add latency: 1.31 nanoseconds
STREAM add bandwidth: 18293.27 MB/sec
STREAM triad latency: 1.53 nanoseconds
STREAM triad bandwidth: 15692.47 MB/sec
32
STREAM copy latency: 1.52 nanoseconds
STREAM copy bandwidth: 10551.71 MB/sec
STREAM scale latency: 1.52 nanoseconds
STREAM scale bandwidth: 10508.33 MB/sec
STREAM add latency: 1.38 nanoseconds
STREAM add bandwidth: 17363.22 MB/sec
STREAM triad latency: 2.00 nanoseconds
STREAM triad bandwidth: 12024.52 MB/sec
40
STREAM copy latency: 1.49 nanoseconds
STREAM copy bandwidth: 10758.50 MB/sec
STREAM scale latency: 1.50 nanoseconds
STREAM scale bandwidth: 10680.17 MB/sec
STREAM add latency: 1.34 nanoseconds
STREAM add bandwidth: 17948.34 MB/sec
STREAM triad latency: 1.98 nanoseconds
STREAM triad bandwidth: 12133.22 MB/sec
48
STREAM copy latency: 1.49 nanoseconds
STREAM copy bandwidth: 10736.56 MB/sec
STREAM scale latency: 1.50 nanoseconds
STREAM scale bandwidth: 10692.93 MB/sec
STREAM add latency: 1.34 nanoseconds
STREAM add bandwidth: 17902.85 MB/sec
STREAM triad latency: 1.96 nanoseconds
STREAM triad bandwidth: 12239.44 MB/sec

Intel(R) Xeon(R) CPU E5-2682 v4

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
#time for i in $(seq 0 8 51); do echo $i; numactl -C $i -m 0 ./bin/stream -W 5 -N 5 -M 64M; done
0
STREAM copy latency: 1.59 nanoseconds
STREAM copy bandwidth: 10092.31 MB/sec
STREAM scale latency: 1.57 nanoseconds
STREAM scale bandwidth: 10169.16 MB/sec
STREAM add latency: 1.31 nanoseconds
STREAM add bandwidth: 18360.83 MB/sec
STREAM triad latency: 2.28 nanoseconds
STREAM triad bandwidth: 10503.81 MB/sec
8
STREAM copy latency: 1.55 nanoseconds
STREAM copy bandwidth: 10312.14 MB/sec
STREAM scale latency: 1.56 nanoseconds
STREAM scale bandwidth: 10283.70 MB/sec
STREAM add latency: 1.30 nanoseconds
STREAM add bandwidth: 18416.26 MB/sec
STREAM triad latency: 2.23 nanoseconds
STREAM triad bandwidth: 10777.08 MB/sec
16
STREAM copy latency: 2.02 nanoseconds
STREAM copy bandwidth: 7914.25 MB/sec
STREAM scale latency: 2.02 nanoseconds
STREAM scale bandwidth: 7919.85 MB/sec
STREAM add latency: 1.39 nanoseconds
STREAM add bandwidth: 17276.06 MB/sec
STREAM triad latency: 2.92 nanoseconds
STREAM triad bandwidth: 8231.18 MB/sec
24
STREAM copy latency: 1.99 nanoseconds
STREAM copy bandwidth: 8032.18 MB/sec
STREAM scale latency: 1.98 nanoseconds
STREAM scale bandwidth: 8061.12 MB/sec
STREAM add latency: 1.39 nanoseconds
STREAM add bandwidth: 17313.94 MB/sec
STREAM triad latency: 2.88 nanoseconds
STREAM triad bandwidth: 8318.93 MB/sec

#lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 64
On-line CPU(s) list: 0-63
Thread(s) per core: 2
Core(s) per socket: 16
Socket(s): 2
NUMA node(s): 2
Vendor ID: GenuineIntel
CPU family: 6
Model: 79
Model name: Intel(R) Xeon(R) CPU E5-2682 v4 @ 2.50GHz
Stepping: 1
CPU MHz: 2500.000
CPU max MHz: 3000.0000
CPU min MHz: 1200.0000
BogoMIPS: 5000.06
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 256K
L3 cache: 40960K
NUMA node0 CPU(s): 0-15,32-47
NUMA node1 CPU(s): 16-31,48-63

stream对比数据

总结下几个CPU用stream测试访问内存的RT以及抖动和带宽对比数据

最小RT 最大RT 最大copy bandwidth 最小copy bandwidth
申威3231(2numa node) 7.09 8.75 2256.59 MB/sec 1827.88 MB/sec
飞腾2500(16 numa node) 2.84 10.34 5638.21 MB/sec 1546.68 MB/sec
鲲鹏920(4 numa node) 1.84 3.87 8700.75 MB/sec 4131.81 MB/sec
海光7280(8 numa node) 1.38 2.58 11591.48 MB/sec 6206.99 MB/sec
海光5280(4 numa node) 1.22 2.52 13166.34 MB/sec 6357.71 MB/sec
Intel8269CY(2 numa node) 1.12 1.52 14293.68 MB/sec 10551.71 MB/sec
Intel E5-2682(2 numa node) 1.58 2.02 10092.31 MB/sec 7914.25 MB/sec

从以上数据可以看出这5款CPU性能一款比一款好,飞腾2500慢的core上延时快到intel 8269的10倍了,平均延时5倍以上了。延时数据基本和单核上测试sysbench TPS一致。

lat_mem_rd对比数据

用不同的node上的core 跑lat_mem_rd测试访问node0内存的RT,只取最大64M的时延,时延和node距离完全一致,这里就不再列出测试原始数据了。

RT变化
飞腾2500(16 numa node) core:0 149.976
core:8 168.805
core:16 191.415
core:24 178.283
core:32 170.814
core:40 185.699
core:48 212.281
core:56 202.479
core:64 426.176
core:72 444.367
core:80 465.894
core:88 452.245
core:96 448.352
core:104 460.603
core:112 485.989
core:120 490.402
鲲鹏920(4 numa node) core:0 117.323
core:24 135.337
core:48 197.782
core:72 219.416
海光7280(8 numa node) numa0 106.839
numa1 168.583
numa2 163.925
numa3 163.690
numa4 289.628
numa5 288.632
numa6 236.615
numa7 291.880
分割行
enabled die interleaving
core:0 153.005
core:16 152.458
core:32 272.057
core:48 269.441
海光5280(4 numa node) core:0 102.574
core:8 160.989
core:16 286.850
core:24 231.197
Intel 8269CY(2 numa node) core:0 69.792
core:26 93.107
申威3231(2numa node) core:0 215.146
core:32 282.443

测试命令:

1
for i in $(seq 0 8 127); do echo core:$i; numactl -C $i -m 0 ./bin/lat_mem_rd -W 5 -N 5 -t 64M; done >lat.log 2>&1

测试结果和numactl -H 看到的node distance完全一致,芯片厂家应该就是这样测试然后把这个延迟当做距离写进去了

最后用一张实际测试Inte E5 L1 、L2、L3的cache延时图来加深印象,可以看到在每级cache大小附近时延有个跳跃:
undefined
纵坐标是访问延时 纳秒,横坐标是cache大小 M,为什么上图没放内存延时,因为延时太大,放出来就把L1、L2的跳跃台阶压平了

结论

  • X86比ARM性能要好
  • AMD和Intel单核基本差别不大,Intel适合要求核多的大实例,AMD适合云上拆分售卖
  • 国产CPU还有比较大的进步空间
  • 性能上的差异在数据库场景下归因下来主要在CPU访问内存的时延上
  • 跨Numa Node时延差异很大,一定要开NUMA 就近访问内存
  • 数据库场景下大实例因为锁导致CPU很难跑满,建议 分库分表搞多个mysqld实例

如果你一定要知道一块CPU性能的话先看 内存延时 而不是 主频,各种CPU自家打榜一般都是简单计算场景,内存访问不多,但是实际业务中大部分时候又是高频访问内存的。

参考资料

十年后数据库还是不敢拥抱NUMA?
Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的
lmbench测试要考虑cache等
CPU的制造和概念
CPU 性能和Cache Line
Perf IPC以及CPU性能
CPU性能和CACHE

海光CPU

发表于 2021-03-08 | 分类于 CPU

海光CPU

海光物理机CPU相关信息

总共有16台如下的海光服务器

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
#lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 64
On-line CPU(s) list: 0-63
Thread(s) per core: 2 //每个物理core有两个超线程
Core(s) per socket: 16 //每路16个物理core
Socket(s): 2 //2路
NUMA node(s): 4
Vendor ID: HygonGenuine
CPU family: 24
Model: 1
Model name: Hygon C86 5280 16-core Processor
Stepping: 1
CPU MHz: 2455.552
CPU max MHz: 2500.0000
CPU min MHz: 1600.0000
BogoMIPS: 4999.26
Virtualization: AMD-V
L1d cache: 32K
L1i cache: 64K
L2 cache: 512K
L3 cache: 8192K
NUMA node0 CPU(s): 0-7,32-39
NUMA node1 CPU(s): 8-15,40-47
NUMA node2 CPU(s): 16-23,48-55
NUMA node3 CPU(s): 24-31,56-63
Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl nonstop_tsc cpuid extd_apicid amd_dcm aperfmperf pni pclmulqdq monitor ssse3 fma cx16 sse4_1 sse4_2 movbe popcnt xsave avx f16c rdrand lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw skinit wdt tce topoext perfctr_core perfctr_nb bpext perfctr_llc mwaitx cpb hw_pstate sme ssbd sev ibpb vmmcall fsgsbase bmi1 avx2 smep bmi2 MySQLeed adx smap clflushopt sha_ni xsaveopt xsavec xgetbv1 xsaves clzero irperf xsaveerptr arat npt lbrv svm_lock nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold avic v_vmsave_vmload vgif overflow_recov succor smca

#numactl -H
available: 4 nodes (0-3)
node 0 cpus: 0 1 2 3 4 5 6 7 32 33 34 35 36 37 38 39
node 0 size: 128854 MB
node 0 free: 89350 MB
node 1 cpus: 8 9 10 11 12 13 14 15 40 41 42 43 44 45 46 47
node 1 size: 129019 MB
node 1 free: 89326 MB
node 2 cpus: 16 17 18 19 20 21 22 23 48 49 50 51 52 53 54 55
node 2 size: 128965 MB
node 2 free: 86542 MB
node 3 cpus: 24 25 26 27 28 29 30 31 56 57 58 59 60 61 62 63
node 3 size: 129020 MB
node 3 free: 98227 MB
node distances:
node 0 1 2 3
0: 10 16 28 22
1: 16 10 22 28
2: 28 22 10 16
3: 22 28 16 10

AMD Zen 架构的CPU是胶水核,也就是把两个die拼一块封装成一块CPU,所以一块CPU内跨die之间延迟还是很高的。

7260 系列的hygon CPU(关掉了超线程)

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: 1065.890
CPU max MHz: 2200.0000
CPU min MHz: 1200.0000
BogoMIPS: 4399.38
Virtualization: AMD-V
L1d cache: 1.5 MiB
L1i cache: 3 MiB
L2 cache: 24 MiB
L3 cache: 128 MiB
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

7280

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
#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): 128
On-line CPU(s) list: 0-127
Thread(s) per core: 2
Core(s) per socket: 32
Socket(s): 2
NUMA node(s): 8
Vendor ID: HygonGenuine
CPU family: 24
Model: 1
Model name: Hygon C86 7280 32-core Processor
Stepping: 1
CPU MHz: 2313.699
BogoMIPS: 3999.47
Virtualization: AMD-V
L1d cache: 2 MiB
L1i cache: 4 MiB
L2 cache: 32 MiB
L3 cache: 128 MiB
NUMA node0 CPU(s): 0-7,64-71
NUMA node1 CPU(s): 8-15,72-79
NUMA node2 CPU(s): 16-23,80-87
NUMA node3 CPU(s): 24-31,88-95
NUMA node4 CPU(s): 32-39,96-103
NUMA node5 CPU(s): 40-47,104-111
NUMA node6 CPU(s): 48-55,112-119
NUMA node7 CPU(s): 56-63,120-127

64 个 core 的分配策略

1
2
3
4
5
physical         core      processor
0 0~15 0~15
1 0~15 16~31
0 0~15 32~47
1 0~15 48~63

海光bios配置

1
2
3
4
5
6
在grub.conf里面加入noibrs noibpb nopti nospectre_v2 nospectre_v1 l1tf=off nospec_store_bypass_disable no_stf_barrier mds=off tsx=on tsx_async_abort=off mitigations=off iommu.passthrough=1;持久化ip;挂盘参数defaults,noatime,nodiratime,lazytime,delalloc,nobarrier,data=writeback(因为后面步骤要重启,把一些OS优化也先做了)
2. bios设置里面
配置 Hygon 设定 --- DF选项 --- 内存交错 --- Channel
--- NB选项 --- 关闭iommu
打开CPB
风扇模式设置为高性能模式

海光简介

公司成立于2016年3月,当前送测处理器为其第一代1.0版本的7185对标处理器为Intel的E5-2680V4,其服务器样机为曙光H620-G30。

海光CPU的命名规则:
型号71xx
7:高端
1:海光1号
xx:sku

其后续roadmap如下图

image-20221026100149808

img

海光其产品规格如下,产品相对密集,但是产品之间差异化很小,频率总体接近。

img

AMD授权Zen IP给海光的操作是先成立合资公司,授权给合资公司基于Zen 研发新的 CPU,而且转让给中国的所有信息都符合美国出口法规。天津海光和AMD成立的合资公司可以修改AMD的CPU核,变相享有X86授权,而海光公司可以通过购买合资公司研发的CPU核,开发服务器CPU,不过仅仅局限于中国市场。

AMD与国内公司A成立合资公司B,合资公司B由AMD控股,负责开发CPU核(其实就是拿AMD现成的内核),然后公司A购买合资公司B开发的CPU核,以此为基础开发CPU,最终实现ARM卖IP核的翻版。

image-20221026100539350

image-20221026100646800

海光与AMD 的 Ryzen/EPYC 比较

由于在 Zen 1 的基础上进行了大量的修改,海光 CPU 可以不用简单地称之为换壳 AMD 处理器了。但其性能相比同代原版 CPU 略差:整数性能基本相同,浮点性能显著降低——普通指令吞吐量只有基准水平的一半。海光 CPU 的随机数生成机制也被修改,加密引擎已被替换,不再对常见的 AES 指令进行加速,但覆盖了其他面向国内安全性的指令如 SM2、SM3 和 SM4。

相同

与 AMD 的 Ryzen/EPYC 相比,海光处理器究竟有哪些不同?总体而言,核心布局是相同的,缓存大小、TLB 大小和端口分配都相同,在基础级别上两者没有差异。CPU 仍然是 64KB 四路 L1 指令缓存,32KB 八路 L1 数据缓存,512KB 八路 L2 缓存以及 8MB 十六路 L3 缓存,与 Zen 1 核心完全相同。

不同

加密方式变化**

在 Linux 内核升级中有关加密变化的信息已经明示。这些更新围绕 AMD 虚拟化功能(SEV)的安全加密进行。通常对于 EPYC 处理器来说,SEV 由 AMD 定义的加密协议控制,在这种情况下为 RSA、ECDSA、ECDH、SHA 和 AES。

但在海光 Dhyana 处理器中,SEV 被设计为使用 SM2、SM3 和 SM4 算法。在更新中有关 SM2 的部分声明道,这种算法基于椭圆曲线加密法,且需要其他私钥/公钥交换;SM3 是一种哈希算法,类似于 SHA-256;而 SM4 是类似于 AES-128 的分组密码算法。为支持这些算法所需的额外功能,其他指令也被加入到了 Linux 内核中。在说明文件中指出,这些算法已在 Hygon Dhyana Plus 处理器上成功进行测试,也已在 AMD 的 EPYC CPU 上成功测试。

此外,海光与 AMD 原版芯片最大的设计区别在于吞吐量,尽管整数性能相同,但海光芯片对于某些浮点指令并未做流水线处理,这意味着吞吐量和延迟都减小了:

img

这些对于最基础的任务来说也会有所影响,降低吞吐量的设计会让 CPU 在并行计算时性能受限。另外一个最大的变化,以及 Dhyana 与服务器版的「Dhyana Plus」版本之间的不同在于随机数生成的能力。

Openjdk 对海光的支持

https://github.com/openjdk/jdk/commit/d03cf75344fccba375881f0dab4ad169254e650c

https://bugs.openjdk.org/browse/JDK-8222090

https://github.com/dragonwell-project/dragonwell11/pull/517

比较不同 NUMA 方式

bios on and os cmdline off

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
lscpu
架构: x86_64
CPU 运行模式: 32-bit, 64-bit
字节序: Little Endian
Address sizes: 43 bits physical, 48 bits virtual
CPU: 96
在线 CPU 列表: 0-95
每个核的线程数: 2
每个座的核数: 24
座: 2
NUMA 节点: 1
厂商 ID: HygonGenuine
CPU 系列: 24
型号: 1
型号名称: Hygon C86 7260 24-core Processor
步进: 1
Frequency boost: enabled
CPU MHz: 1603.426
CPU 最大 MHz: 2200.0000
CPU 最小 MHz: 1200.0000
BogoMIPS: 4399.55
虚拟化: AMD-V
L1d 缓存: 1.5 MiB
L1i 缓存: 3 MiB
L2 缓存: 24 MiB
L3 缓存: 128 MiB
NUMA 节点0 CPU: 0-95

# uname -r
4.19.90-23.8.v2101.ky10.x86_64

测试命令和结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//以下多个测试方式的结果一样
for i in $(seq 0 6 47); do echo core:$i; taskset -c $i ./bin/lat_mem_rd -W 5 -N 5 -t 64M; done >lat.log 2>&1
for i in $(seq 0 6 47); do echo core:$i; numactl -C $i -m 0 ./bin/lat_mem_rd -W 5 -N 5 -t 64M; done >lat.log 2>&1

cat lat.log |grep -E "core:|64.0000"
core:0
64.00000 270.555
core:6
64.00000 231.677
core:12
64.00000 268.527
core:18
64.00000 268.878
core:24
64.00000 158.644
core:30
64.00000 159.796
core:36
64.00000 162.938
core:42
64.00000 112.052


//不绑核会一直慢,因为刚好内存在高地址,程序大概率在低core上运行
for i in $(seq 0 6 47); do echo core:$i; numactl -C $i -m 0 ./bin/lat_mem_rd -W 5 -N 5 -t 64M; done >lat.log 2>&1

#cat lat.log |grep -E "core:|64.0000"
core:0
64.00000 267.904
core:6
64.00000 266.112
core:12
64.00000 265.657
core:18
64.00000 266.033
core:24
64.00000 269.574
core:30
64.00000 269.640
core:36
64.00000 269.639
core:42
64.00000 266.373

如果绑核,看起来还是能识别距离远近,但是需要同时绑内存,默认绑核不绑核内存达不到就近分配

内置分配默认从高地址开始,所以core 0总是最慢的。

不绑核的话内存默认是在高地址,程序大概率远程访问内存,所以极慢

bios off

测试命令和结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//以下三个命令结果一致
for i in $(seq 0 6 47); do echo core:$i; taskset -c $i ./bin/lat_mem_rd -W 5 -N 5 -t 64M; done >lat.log 2>&1
for i in $(seq 0 6 47); do echo core:$i; numactl -C $i -m 0 ./bin/lat_mem_rd -W 5 -N 5 -t 64M; done >lat.log 2>&1
for i in $(seq 0 6 47); do echo core:$i; ./bin/lat_mem_rd -W 5 -N 5 -t 64M; done >lat.log 2>&1

cat lat.log |grep -E "core:|64.0000"
core:0
64.00000 197.570
core:6
64.00000 204.362
core:12
64.00000 198.356
core:18
64.00000 197.049
core:24
64.00000 202.469
core:30
64.00000 199.367
core:36
64.00000 197.790
core:42
64.00000 197.757

rt稳定在200,之所以不符合短板原理是测试中多次取平均导致的,慢的还是慢,有的在近有的在远的地址,但平均值稳定

交错编址的时候不会把一个cacheline拆分到多个node下的内存条上

测试结果和OS的启动参数是否numa=off无关

bios off后没有机会识别内存远近,也就是反复循环分配内存不可能一直分配到本node内

bios on and os on

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.030
CPU max MHz: 2200.0000
CPU min MHz: 1200.0000
BogoMIPS: 4399.35
Virtualization: AMD-V
L1d cache: 1.5 MiB
L1i cache: 3 MiB
L2 cache: 24 MiB
L3 cache: 128 MiB
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

测试命令和参数:

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
for i in $(seq 0 6 47); do echo core:$i; numactl -C $i -m 0 ./bin/lat_mem_rd -W 5 -N 5 -t 64M; done >lat.log 2>&1

# cat lat.log |grep -E "core:|64.0000"
core:0
64.00000 113.048
core:6
64.00000 167.637
core:12
64.00000 164.398
core:18
64.00000 163.328
core:24
64.00000 277.176
core:30
64.00000 277.157
core:36
64.00000 226.747
core:42
64.00000 278.472


绑核不绑内存或者不绑核运行结果是一样的
for i in $(seq 0 6 47); do echo core:$i; taskset -c $i ./bin/lat_mem_rd -W 5 -N 5 -t 64M; done >lat.log 2>&1
for i in $(seq 0 6 47); do echo core:$i; taskset -c $i ./bin/lat_mem_rd -W 5 -N 5 -t 64M; done >lat.log 2>&1

#cat lat.log |grep -E "core:|64.0000"
core:0
64.00000 112.990
core:6
64.00000 113.358
core:12
64.00000 114.146
core:18
64.00000 112.288
core:24
64.00000 114.103
core:30
64.00000 113.331
core:36
64.00000 114.151
core:42
64.00000 113.117

结论

  • 只要是bios numa off后用 lat_mem_rd 测试的 rt 是一个大规模次数后的平均值,但是远近内存问题仍然存在,会导致抖动和卡顿
  • 如果 bios numa on,但是 OS numa off,绑核的话会分别看到不同核访问内存差异极大
  • 如果 bios numa on,同时 OS numa on,这种情况绑核后会出现完美就近访问,性能最佳
  • 默认地址分配从高地址开始,如果numa off 那么core 0 最慢;如果 numa on 且绑核会一直快;如果 numa on 但是不绑核,多次测试也是一直快

一次海光物理机资源竞争压测的记录

发表于 2021-03-07 | 分类于 CPU

一次海光物理机资源竞争压测的记录

问题描述

问题描述如下

sysbench 压200个服务节点(每个4c16G,总共800core), 发现qps不能线性增加(200节点比100节点好1.2倍而已)。

如果压单个服务节点节点QPS 2.4万,CPU跑到390%(每个服务节点独占4个核),如果压200个服务节点(分布在16台64核的海光物理机上)平均每个服务节点节点QPS才1.2万。但是每个服务节点的CPU也跑到了390%左右。 现在的疑问就是为什么CPU跑上去了QPS打了个5折。

机器集群为16*64core 为1024core,也就是每个服务节点独占4core还有冗余

因为服务节点还需要通过LVS调用后端的多个MySQL集群,所以需要排除LVS、网络等链路瓶颈,然后找到根因是什么。

海光物理机CPU相关信息

总共有16台如下的海光服务器

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
#lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 64
On-line CPU(s) list: 0-63
Thread(s) per core: 2 //每个物理core有两个超线程
Core(s) per socket: 16 //每路16个物理core
Socket(s): 2 //2路
NUMA node(s): 4
Vendor ID: HygonGenuine
CPU family: 24
Model: 1
Model name: Hygon C86 5280 16-core Processor
Stepping: 1
CPU MHz: 2455.552
CPU max MHz: 2500.0000
CPU min MHz: 1600.0000
BogoMIPS: 4999.26
Virtualization: AMD-V
L1d cache: 32K
L1i cache: 64K
L2 cache: 512K
L3 cache: 8192K
NUMA node0 CPU(s): 0-7,32-39
NUMA node1 CPU(s): 8-15,40-47
NUMA node2 CPU(s): 16-23,48-55
NUMA node3 CPU(s): 24-31,56-63
Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl nonstop_tsc cpuid extd_apicid amd_dcm aperfmperf pni pclmulqdq monitor ssse3 fma cx16 sse4_1 sse4_2 movbe popcnt xsave avx f16c rdrand lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw skinit wdt tce topoext perfctr_core perfctr_nb bpext perfctr_llc mwaitx cpb hw_pstate sme ssbd sev ibpb vmmcall fsgsbase bmi1 avx2 smep bmi2 MySQLeed adx smap clflushopt sha_ni xsaveopt xsavec xgetbv1 xsaves clzero irperf xsaveerptr arat npt lbrv svm_lock nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold avic v_vmsave_vmload vgif overflow_recov succor smca

#numactl -H
available: 4 nodes (0-3)
node 0 cpus: 0 1 2 3 4 5 6 7 32 33 34 35 36 37 38 39
node 0 size: 128854 MB
node 0 free: 89350 MB
node 1 cpus: 8 9 10 11 12 13 14 15 40 41 42 43 44 45 46 47
node 1 size: 129019 MB
node 1 free: 89326 MB
node 2 cpus: 16 17 18 19 20 21 22 23 48 49 50 51 52 53 54 55
node 2 size: 128965 MB
node 2 free: 86542 MB
node 3 cpus: 24 25 26 27 28 29 30 31 56 57 58 59 60 61 62 63
node 3 size: 129020 MB
node 3 free: 98227 MB
node distances:
node 0 1 2 3
0: 10 16 28 22
1: 16 10 22 28
2: 28 22 10 16
3: 22 28 16 10

AMD Zen 架构的CPU是胶水核,也就是把两个die拼一块封装成一块CPU,所以一块CPU内跨die之间延迟还是很高的。

验证是否是上下游的瓶颈

需要先分析问题是否在LVS调用后端的多个MySQL集群上。

先写一个简单的测试程序:

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
#cat Test.java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/*
* 目录:/home/admin/jdbc
*
* 编译:
* javac -cp /home/admin/lib/*:. Test.java
*
* 运行:
* java -cp /home/admin/MySQL-server/lib/*:. Test "jdbc:mysql://172.16.160.1:4261/qc_pay_0xwd_0002" "myhhzi0d" "jOXaC1Lbif-k" "select count(*) from pay_order where user_id=1169257092557639682 and order_no='201909292111250000102'" "100"
* */
public class Test {

public static void main(String args[]) throws NumberFormatException, InterruptedException, ClassNotFoundException {
Class.forName("com.mysql.jdbc.Driver");
String url = args[0];
String user = args[1];
String pass = args[2];
String sql = args[3];
String interval = args[4];
try {
Connection conn = DriverManager.getConnection(url, user, pass);
while (true) {
long start = System.currentTimeMillis();
for(int i=0; i<1000; ++i){
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
}
rs.close();
stmt.close();
Thread.sleep(Long.valueOf(interval));
}
long end = System.currentTimeMillis();
System.out.println("rt : " + (end - start));
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}

然后通过传入不同的jdbc参数跑2组测试:

  1. 走服务节点执行指定id的点查;
  2. 直接从服务节点节点连MySQL指定id点查

上述2组测试同时跑在三组场景下:

  • A) 服务节点和MySQL都没有压力;
  • B) 跑1、2测试的服务节点没有压力,但是sysbench 在压别的服务节点,这样后端的MySQL是有sysbench压侧压力,LVS也有流量压力的;
  • C) sysbench压所有服务节点, 包含运行 1、2测试程序节点)

这样2组测试3个场景组合可以得到6组响应时间的测试数据

从最终得到6组数据来看可以排除链路以及MySQL的问题,瓶颈似乎还是在服务节点上

image.png

单独压一个服务节点节点并在上面跑测试,服务节点 CPU被压到 390%(每个服务节点 节点固定绑到4核), 这个时候整个宿主机压力不大,但是这四个核比较紧张了

1
2
3
4
5
6
7
#cat rt.log  | awk '{ print $3 }'  | awk '{if(min==""){min=max=$1}; if($1>max) {max=$1}; if($1<min) {min=$1}; total+=$1; count+=1} END {print "avg " total/count," | max "max," | min " min, "| count ", count}' ; cat MySQL.log  | awk '{ print $3 }'  | awk '{if(min==""){min=max=$1}; if($1>max) {max=$1}; if($1<min) {min=$1}; total+=$1; count+=1} END {print "avg " total/count," | max "max," | min " min, "| count ", count }';
avg 2589.13 | max 3385 | min 2502 | count 69
avg 1271.07 | max 1405 | min 1254 | count 141

[root@d42a01107.cloud.a02.am78 /root]
#taskset -pc 48759
pid 48759's current affinity list: 19,52-54

通过这6组测试数据可以看到,只有在整个系统都有压力(服务节点所在物理机、LVS、MySQL)的时候rt飙升最明显(C组数据),如果只是LVS、MySQL有压力,服务节点没有压力的时候可以看到数据还是很好的(B组数据)

分析宿主机资源竞争

perf分析

只压单个服务节点

image.png

image.png

从以上截图,可以看到关键的 insn per cycle 能到0.51和0.66(这个数值越大性能越好)

如果同时压物理机上的所有服务节点

image.png

image.png

从以上截图,可以看到关键的 insn per cycle 能降到了0.27和0.31(这个数值越大性能越好),基本相当于单压的5折

通过 perf list 找出所有Hardware event,然后对他们进行perf:

1
sudo perf stat -e branch-instructions,branch-misses,cache-references,cpu-cycles,instructions,stalled-cycles-backend,stalled-cycles-frontend,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 -a -- `pidof java`

尝试不同的绑核后的一些数据

通过以上perf数据以及numa结构,尝试将不同服务进程绑定到指定的4个核上

试了以下三种绑核的办法:

1)docker swarm随机绑(以上测试都是用的这种默认方案);

2)一个服务节点绑连续4个core,这4个core都在同一个node;

3)一个服务节点绑4个core,这个4个core都在在同一个node,同时尽量HT在一起,也就是0,1,32,33 ; 2,3,34,35 这种绑法

结果是绑法2性能略好.

如果是绑法2,压单个服务节点 QPS能到2.3万;绑法1和3,压单个服务节点性能差别不明显,都是2万左右。

尝试将Java进程开启HugePage

从perf数据来看压满后tlab miss比较高,得想办法降低这个值

修改JVM启动参数

JVM启动参数增加如下三个(-XX:LargePageSizeInBytes=2m, 这个一定要,有些资料没提这个,在我的JDK8.0环境必须要):

-XX:+UseLargePages -XX:LargePageSizeInBytes=2m -XX:+UseHugeTLBFS

修改机器系统配置

设置HugePage的大小

cat /proc/sys/vm/nr_hugepages

nr_hugepages设置多大参考如下计算方法:

If you are using the option -XX:+UseSHM or -XX:+UseHugeTLBFS, then specify the number of large pages. In the following example, 3 GB of a 4 GB system are reserved for large pages (assuming a large page size of 2048kB, then 3 GB = 3 * 1024 MB = 3072 MB = 3072 * 1024 kB = 3145728 kB and 3145728 kB / 2048 kB = 1536):

echo 1536 > /proc/sys/vm/nr_hugepages

透明大页是没有办法减少系统tlab,tlab是对应于进程的,系统分给进程的透明大页还是由物理上的4K page组成。

Java进程用上HugePages后iTLB-load-misses从80%下降到了14%左右, dTLB也从30%下降到了20%,但是ipc变化不明显,QPS有不到10%的增加(不能确定是不是抖动所致)

image.png

在公有云ecs虚拟机上测试对性能没啥帮助,实际看到用掉的HuagPage不多,如果/proc/sys/vm/nr_hugepages 设置比较大的话JVM会因为内存不足起不来,两者内存似乎是互斥的

用sysbench验证一下海光服务器的多core能力

Intel E5 2682 2.5G VS hygon 7280 2.0G(Zen1)

image-20210813095409553

image-20210813095757299

由以上两个测试结果可以看出单核能力hygon 7280 强于 Intel 2682,但是hygon超线程能力还是没有任何提升。Intel用超线程计算能将耗时从109秒降到74秒。但是hygon(Zen1) 只是从89秒降到了87秒,基本没有变化。

再补充一个Intel(R) Xeon(R) Platinum 8269CY CPU @ 2.50GHz 对比数据

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
#taskset -c 1,53 /usr/bin/sysbench --num-threads=2 --test=cpu --cpu-max-prime=50000 run
sysbench 0.5: multi-threaded system evaluation benchmark

Running the test with following options:
Number of threads: 2
Random number generator seed is 0 and will be ignored


Primer numbers limit: 50000

Threads started!


General statistics:
total time: 48.5571s
total number of events: 10000
total time taken by event execution: 97.0944s
response time:
min: 8.29ms
avg: 9.71ms
max: 20.88ms
approx. 95 percentile: 9.71ms

Threads fairness:
events (avg/stddev): 5000.0000/2.00
execution time (avg/stddev): 48.5472/0.01

#taskset -c 1 /usr/bin/sysbench --num-threads=1 --test=cpu --cpu-max-prime=50000 run
sysbench 0.5: multi-threaded system evaluation benchmark

Running the test with following options:
Number of threads: 1
Random number generator seed is 0 and will be ignored


Primer numbers limit: 50000

Threads started!


General statistics:
total time: 83.2642s
total number of events: 10000
total time taken by event execution: 83.2625s
response time:
min: 8.27ms
avg: 8.33ms
max: 10.03ms
approx. 95 percentile: 8.36ms

Threads fairness:
events (avg/stddev): 10000.0000/0.00
execution time (avg/stddev): 83.2625/0.00

#lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 104
On-line CPU(s) list: 0-103
Thread(s) per core: 2
Core(s) per socket: 26
Socket(s): 2
NUMA node(s): 2
Vendor ID: GenuineIntel
CPU family: 6
Model: 85
Model name: Intel(R) Xeon(R) Platinum 8269CY CPU @ 2.50GHz
Stepping: 7
CPU MHz: 3200.097
CPU max MHz: 3800.0000
CPU min MHz: 1200.0000
BogoMIPS: 4998.89
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 1024K
L3 cache: 36608K
NUMA node0 CPU(s): 0-25,52-77
NUMA node1 CPU(s): 26-51,78-103
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 avx512cdavx512bw avx512vl xsaveopt xsavec xgetbv1 cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local cat_l3 mba

用sysbench 测试Hygon C86 5280 16-core Processor,分别1、8、16、24、32、40、48、56、64 个thread,32个thread前都是完美的线性增加,32core之后基本不增长了,这个应该能说明这个服务器就是32core的能力

sysbench –threads=1 –cpu-max-prime=50000 cpu run

image.png

对比下intel的 Xeon 104core,也是物理52core,但是性能呈现完美线性

image.png

openssl场景多核能力验证

1
openssl speed aes-256-ige -multi N

intel 52 VS 26,可以看到52个线程的性能大概是26个的1.8倍

image.png

intel 104 VS 52 线程,性能还能提升1.4倍

image.png

海光32 VS 16, 性能能提升大概1.8倍,跟intel一致

image.png

海光64 VS 32, 性能能提升大概1.2倍

image.png

总结下就是,在物理core数以内的线程数intel和海光性能基本增加一致;但如果超过物理core数开始使用HT后海光明显相比Intel差了很多。

intel超线程在openssl场景下性能能提升40%,海光就只能提升20%了。

对比一下鲲鹏920 ARM架构的芯片

1
2
3
4
5
6
7
8
#numactl -H
available: 1 nodes (0)
node 0 cpus: 0 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 86 87 88 89 90 91 92 93 94 95
node 0 size: 773421 MB
node 0 free: 756092 MB
node distances:
node 0
0: 10

96核一起跑openssl基本就是1核的96倍,完美线性,这是因为鲲鹏就没有超线程,都是物理核。如果并发增加到192个,性能和96个基本一样的。

image.png

用Sysbench直接压MySQL oltp_read_only的场景

image.png

从1到10个thread的时候完美呈现线性,到20个thread就只比10个thread增加50%了,30thread比20增加40%,过了32个thread后增加10个core性能加不到10%了。

在32thread前,随着并发的增加 IPC也有所减少,这也是导致thread翻倍性能不能翻倍的一个主要原因。

基本也和openssl 场景一致,海光的HT基本可以忽略,做的不是很好。超过32个thread后(物理core数)性能增加及其微弱

结论

海光的一个core只有一个fpu,所以超线程算浮点完全没用.

FP处理所有矢量操作。简单的整数向量运算(例如shift,add)都可以在一个周期内完成,是AMD以前架构延迟的一半。基本浮点数学运算具有三个周期的延迟,其中包括乘法(用于双精度需要一个额外周期)。融合乘加是五个周期。

FP具有用于128位加载操作的单个管道。实际上,整个FP端都针对128位操作进行了优化。 Zen支持所有最新指令,例如SSE和AVX1/2。 256位AVX的设计方式是可以将它们作为两个独立的128位操作来执行。 Zen通过将这些指令作为两个操作。也就是说,Zen将256位操作分为两个µOP。同样,存储也是在128位块上完成的,从而使256位加载的有效吞吐量为每两个周期一个存储。这些管道之间的平衡相当好,因此大多数操作将安排至少两个管道,以保持每个周期至少一个这样的指令的吞吐量。暗示着,256位操作将占用两倍的资源来完成操作(即2x寄存器,调度程序和端口)。这是AMD采取的一种折衷方案,有助于节省芯片空间和功耗。相比之下,英特尔的竞争产品Skylake确实具有专用的256位电路。还应注意的是,英特尔的现代服务器级型号进一步扩展了此功能,以纳入支持AVX-512的专用512位电路,而性能最高的型号则具有二个专用的AVX-512单元。

此外,Zen还支持SHA和AES(并实现了2个AES单元),以提高加密性能。这些单位可以在浮点调度程序的管道0和1上找到。

这个也是为什么浮点比Intel X86会弱的原因。

一些其他对比结论

  • 对纯CPU 运算场景,并发不超过物理core时,比如Prime运算,比如DRDS(CPU bound,IO在网络,可以加并发弥补)
    • 海光的IPC能保持稳定;
    • intel的IPC有所下降,但是QPS在IPC下降后还能完美线性
  • 在openssl和MySQL oltp_read_only场景下
    • 如果并发没超过物理core数时,海光和Intel都能随着并发的翻倍性能能增加80%
    • 如果并发超过物理core数后,Intel还能随着并发的翻倍性能增加50%,海光增加就只有20%了
    • 简单理解在这两个场景下Intel的HT能发挥半个物理core的作用,海光的HT就只能发挥0.2个物理core的作用了
  • 海光5280/7280 是Zen1/Zen2的AMD 架构,每个core只有一个fpu,综上在多个场景下HT基本上都可以忽略

系列文章

CPU的制造和概念

[CPU 性能和Cache Line](/2021/05/16/CPU Cache Line 和性能/)

[Perf IPC以及CPU性能](/2021/05/16/Perf IPC以及CPU利用率/)

Intel、海光、鲲鹏920、飞腾2500 CPU性能对比

飞腾ARM芯片(FT2500)的性能测试

十年后数据库还是不敢拥抱NUMA?

一次海光物理机资源竞争压测的记录

[Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的](/2019/12/16/Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的/)

参考资料

How to use Huge Pages with Java and Linux这个资料中提到了Java使用HugePage的时候启动进程的用户权限问题,在我的docker容器中用的admin启动的进程,测试验证是不需要按资料中的设置。

Intel PAUSE指令变化是如何影响自旋锁以及MySQL的性能的

华为TaiShan服务器ARMNginx应用调优案例 大量绑核、中断、Numa等相关调优信息

macOS下如何使用iTerm2访问水木社区

发表于 2021-03-05 | 分类于 Linux

macOS下如何使用iTerm2访问水木社区

关键字: macOS、iTerm 、Dracula、ssh、bbs.newsmth.net

windows下有各种Term软件来帮助我们通过ssh访问bbs.newsmth.net, 但是工作环境切换到macOS后发现FTerm、CTerm这样的工具都没有对应的了。但是term下访问 bbs.newsmth.net 简直是太爽了,所以本文希望解决这个问题。

问题

ssh 访问 bbs.newsmth.net 是没问题的,但是要解决配色和字符编码问题

解决编码

在iTerm2的配置中增加一个profile,如下图 smth,主要是改字符编码集为 GB 18030,然后修改配色方案,我喜欢的Dracula不适合SMTH,十大完全看不了。

image-20210602133111201

执行脚本如下:

1
2
3
4
#cat /usr/local/bin/smth
echo -e "\033]50;SetProfile=smth\a" //切换到smth的profile,也就是切换到了 GB18030
sshpass -p'密码' ssh -o ServerAliveInterval=60 水木id@bbs.newsmth.net
echo -e "\033]50;SetProfile=Default\a" //切换回UTF8

SetProfile=smth用来解决profile切换,连smth term前切换成GB 18030,断开的时候恢复成UTF-8,要不然的话正常工作的命令行就乱码了。

解决配色问题

然后还是在profile里面把smth的配色方案改成:Tango Dark, 一切简直是完美,工作灌水两不误,别人还发现不了

最终效果

目录(右边是工作窗口):

image.png

十大,这个十大颜色和右边工作模式的配色方案不一样

image.png

断开后恢复成 Dracula 配色和UTF-8编码,不影响工作,别的工作tab也还是正常使用utf8

image.png

别的term网站也是类似,比如小百合、byr、ptt等

TCP疑难问题案例汇总

发表于 2021-02-14 | 分类于 TCP

TCP疑难问题案例汇总

碰到各种奇葩的TCP相关问题,所以汇总记录一下。分析清楚这些问题的所有来龙去脉,就能帮你在TCP知识体系里建立几个坚固的抓手,让TCP知识慢慢在抓手之间生长和互通

服务不响应的现象或者奇怪异常的原因分析

一个黑盒程序奇怪行为的分析 listen端口上很快就全连接队列溢出了,导致整个程序不响应了

举三反一–从理论知识到实际问题的推导 服务端出现大量CLOSE_WAIT 个数正好 等于somaxconn(调整somaxconn大小后 CLOSE_WAIT 也会跟着变成一样的值)

活久见,TCP连接互串了 应用每过一段时间总是会抛出几个连接异常的错误,需要查明原因。排查后发现是TCP连接互串了,这个案例实在是很珍惜,所以记录一下。

如何创建一个自己连自己的TCP连接

传输速度分析

案例:TCP传输速度案例分析(长肥网络、rt升高、delay ack的影响等)

原理:就是要你懂TCP–性能和发送接收Buffer的关系:发送窗口大小(Buffer)、接收窗口大小(Buffer)对TCP传输速度的影响,以及怎么观察窗口对传输速度的影响。BDP、RT、带宽对传输速度又是怎么影响的

就是要你懂TCP–最经典的TCP性能问题 Nagle和Delay ack

就是要你懂TCP–性能优化大全

TCP队列问题以及连接数

到底一台服务器上最多能创建多少个TCP连接

就是要你懂TCP队列–通过实战案例来展示问题

就是要你懂TCP–半连接队列和全连接队列

就是要你懂TCP–握手和挥手

防火墙和reset定位分析

对ttl、identification等的运用

关于TCP连接的Keepalive和reset

就是要你懂网络–谁动了我的TCP连接

TCP相关参数

TCP相关参数解释

网络通不通是个大问题–半夜鸡叫

网络丢包

工具技巧篇

netstat定位性能案例

[netstat timer keepalive explain](/2017/08/28/netstat –timer/)

就是要你懂网络监控–ss用法大全

就是要你懂抓包–WireShark之命令行版tshark

[通过tcpdump对Unix Domain Socket 进行抓包解析](/2018/01/01/通过tcpdump对Unix Socket 进行抓包解析/)

如何追踪网络流量

一个黑盒程序奇怪行为的分析

发表于 2021-02-10 | 分类于 TCP

一个黑盒程序奇怪行为的分析

问题描述:

从银行金主baba手里拿到一个区块链程序,监听4000,在我们的环境中4000端口上很快就全连接队列溢出了,导致整个程序不响应了。这个程序是黑盒子,没有源代码,但是在金主baba自己的环境运行正常(一样的OS)

如下图所示:

image.png

ss -lnt 看到全连接队列增长到了39,但是netstat -ant找不到这39个连接,本来是想看看队列堆了这么多连接,都是哪些ip连过来的,实际看不到这就奇怪了

同时验证过程发现我们的环境 4000端口上开了slb,也就是slb会不停滴探活4000端口,关掉slb探活后一切正常了。

所以总结下来问题就是:

  1. 为什么全连接队列里面的连接netstat看不到这些连接,但是ss能看到总数

  2. 为什么关掉slb就正常了

  3. 为什么应用不accept连接,也不close(应用是个黑盒子)

分析

为什么全连接队列里面的连接netstat/ss都看不到(ss能看到总数)

这是因为这些连接都是探活连接,三次握手后很快被slb reset了,在OS层面这个连接已经被释放,所以肯定看不见。反过来想要是netstat能看见这个连接,那么它的状态是什么? reset吗?tcp连接状态里肯定是没有reset状态的。

ss看到的总数是指只要这个连接没有被accept,那么连接队列里就还有这个连接,通过ss也能看到连接队列数量。

为什么会产生这个错误理解–全连接队列里面的连接netstat一定要能看到?

那是因为正常情况都是能看到的,从没有考虑过握手后很快reset的情况。也没反问过如果能看到这个连接该是什么状态呢?

这个连接被reset后,kernel会将全连接队列数量减1吗?

不会,按照我们的理解连接被reset释放后,那么kernel要释放全连接队列里面的这个连接,因为这些动作都是kernel负责,上层没法处理这个reset。实际上内核认为所有 listen 到的连接, 必须要 accept 走, 用户有权利知道存在过这么一个连接。

也就是reset后,连接在内核层面释放了,所以netstat/ss看不到,但是全连接队列里面的应用数不会减1,只有应用accept后队列才会减1,accept这个空连接后读写会报错。基本可以认为全连接队列溢出了,主要是应用accept太慢导致的。

当应用从accept队列读取到已经被reset了的连接的时候应用层会得到一个报错信息。

什么时候连接状态变成 ESTABLISHED

三次握手成功就变成 ESTABLISHED 了,三次握手成功的第一是收到第三步的ack并且全连接队列没有满,不需要用户态来accept,如果握手第三步的时候OS发现全连接队列满了,这时OS会扔掉这个第三次握手ack,并重传握手第二步的syn+ack, 在OS端这个连接还是 SYN_RECV 状态的,但是client端是 ESTABLISHED状态的了。

下面是在4000(tearbase)端口上全连接队列没满,但是应用不再accept了,nc用12346端口去连4000(tearbase)端口的结果

1
2
3
4
5
6
# netstat -at |grep ":12346 "
tcp 0 0 dcep-blockchain-1:12346 dcep-blockchai:terabase ESTABLISHED //server
tcp 0 0 dcep-blockchai:terabase dcep-blockchain-1:12346 ESTABLISHED //client
[root@dcep-blockchain-1 cfl-sm2-sm3]# ss -lt
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 73 1024 *:terabase *:*

这是在4000(tearbase)端口上全连接队列满掉后,nc用12346端口去连4000(tearbase)端口的结果

1
2
3
4
5
6
# netstat -at |grep ":12346 "  
tcp 0 0 dcep-blockchai:terabase dcep-blockchain-1:12346 SYN_RECV //server
tcp 0 0 dcep-blockchain-1:12346 dcep-blockchai:terabase ESTABLISHED //client
# ss -lt
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 1025 1024 *:terabase *:*

为什么关掉slb就正常了

slb探活逻辑是向监听端口发起三次握手,握手成功后立即发送一个reset断开连接

这是一个完整的探活过程:

image.png

关掉就正常后要结合第三个问题来讲

为什么应用不accept连接,也不close(应用是个黑盒子)

因为应用是个黑盒子,看不到源代码,只能从行为来分析了

从行为来看,这个应用在三次握手后,会主动给client发送一个12字节的数据,但是这个逻辑写在了accept主逻辑内部,一旦主动给client发12字节数据失败(比如这个连接reset了)那么一直卡在这里导致应用不再accept也不再close。

正确的实现逻辑是,accept在一个单独的线程里,一旦accept到一个新连接,那么就开启一个新的线程来处理这个新连接的读写。accept线程专注accept。

关掉slb后应用有机会发出这12个字节,然后accept就能继续了,否则就卡死了。

一些验证

nc测试连接4000端口

1
2
3
4
5
6
7
8
9
10
11
# nc -p 12346 dcep-blockchain-1 4000
 //握手后4000返回的内容

抓包:
11:03:16.762547 IP dcep-blockchain-1.12346 > dcep-blockchain-1.terabase: Flags [S], seq 397659761, win 43690, options [mss 65495,sackOK,TS val 2329725964 ecr 0,nop,wscale 7], length 0
04:42:24.466211 IP dcep-blockchain-1.terabase > dcep-blockchain-1.12346: Flags [S.], seq 4239354556, ack 397659762, win 43690, options [mss 65495,sackOK,TS val 2329725964 ecr 2329725964,nop,wscale 7], length 0
11:03:16.762571 IP dcep-blockchain-1.12346 > dcep-blockchain-1.terabase: Flags [.], ack 1, win 342, options [nop,nop,TS val 2329725964 ecr 2329725964], length 0

----到这三次握手完毕,下面是隔了大概1.5ms,4000发了12字节给nc
11:03:16.763893 IP dcep-blockchain-1.terabase > dcep-blockchain-1.12346: Flags [P.], seq 1:13, ack 1, win 342, options [nop,nop,TS val 2329725966 ecr 2329725964], length 12
11:03:16.763904 IP dcep-blockchain-1.12346 > dcep-blockchain-1.terabase: Flags [.], ack 13, win 342, options [nop,nop,TS val 2329725966 ecr 2329725966], length 0

如果在上面的1.5ms之间nc reset了这个连接,那么这12字节就发不出来了

握手后Server主动发数据的行为非常像MySQL Server

MySQL Server在收到mysql client连接后会主动发送 Server Greeting、版本号、认证方式等给client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#nc -p 12345 127.0.0.1 3306
J
5.6.29�CuaV9v0xo�!
qCHRrGRIJqvzmysql_native_password

#tcpdump -i any port 12345 -ennt
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
In 00:00:00:00:00:00 ethertype IPv4 (0x0800), length 76: 127.0.0.1.12345 > 127.0.0.1.3306: Flags [S], seq 3186409724, win 43690, options [mss 65495,sackOK,TS val 3967896050 ecr 0,nop,wscale 7], length 0
In 00:00:00:00:00:00 ethertype IPv4 (0x0800), length 76: 127.0.0.1.3306 > 127.0.0.1.12345: Flags [S.], seq 4188709983, ack 3186409725, win 43690, options [mss 65495,sackOK,TS val 3967896051 ecr 3967896050,nop,wscale 7], length 0
In 00:00:00:00:00:00 ethertype IPv4 (0x0800), length 68: 127.0.0.1.12345 > 127.0.0.1.3306: Flags [.], ack 1, win 342, options [nop,nop,TS val 3967896051 ecr 3967896051], length 0 // 握手完毕
In 00:00:00:00:00:00 ethertype IPv4 (0x0800), length 146: 127.0.0.1.3306 > 127.0.0.1.12345: Flags [P.], seq 1:79, ack 1, win 342, options [nop,nop,TS val 3967896051 ecr 3967896051], length 78 //Server 发出Greeting
In 00:00:00:00:00:00 ethertype IPv4 (0x0800), length 68: 127.0.0.1.12345 > 127.0.0.1.3306: Flags [.], ack 79, win 342, options [nop,nop,TS val 3967896051 ecr 3967896051], length 0
In 00:00:00:00:00:00 ethertype IPv4 (0x0800), length 68: 127.0.0.1.3306 > 127.0.0.1.12345: Flags [F.], seq 79, ack 1, win 342, options [nop,nop,TS val 3967913551 ecr 3967896051], length 0
In 00:00:00:00:00:00 ethertype IPv4 (0x0800), length 68: 127.0.0.1.12345 > 127.0.0.1.3306: Flags [.], ack 80, win 342, options [nop,nop,TS val 3967913591 ecr 3967913551], length 0

如下是Server发出的长度为 78 的 Server Greeting信息内容

image.png

理论上如果slb探活连接检查MySQL Server的状态的时候也是很快reset了,如果MySQL Server程序写得烂的话也会出现同样的情况。

但是比如我们有实验验证MySQL Server 是否正常的时候会用 nc 去测试,一般以能看到

5.6.29�CuaV9v0xo�!
qCHRrGRIJqvzmysql_native_password

就认为MySQL Server是正常的。但是真的是这样吗?我们看看 nc 的如下案例

nc 6.4 快速fin

#nc –version
Ncat: Version 6.40 ( http://nmap.org/ncat )

用 nc 测试发现有一定的概率没有出现上面的Server Greeting信息,那么这是因为MySQL Server服务不正常了吗?

image.png

nc -i 3 10.97.170.11 3306 -w 4 -p 1234

-i 3 表示握手成功后 等三秒钟nc退出(发fin)

nc 6.4 握手后立即发fin断开连接,导致可能收不到Greeting,换成7.5或者mysql client就OK了

nc 7.5的抓包,明显可以看到nc在发fin前会先等4秒钟:

image.png

tcpping 模拟slb 探活

1
python tcpping.py -R -i 0.1 -t 1 dcep-blockchain-1 4000

-i 间隔0.1秒

-R reset断开连接

-t 超时时间1秒

执行如上代码,跟4000端口握手,然后立即发出reset断开连接(完全模拟slb探活行为),很快重现了问题

增加延时

-D 0.01表示握手成功后10ms后再发出reset(让应用有机会成功发出那12个字节),应用工作正常

1
python tcpping.py -R -i 0.1 -t 1 -D 0.01 dcep-blockchain-1 4000

总结

最大的错误认知就是 ss 看到的全连接队列数量,netstat也能看到。实际是不一定,而这个快速reset+应用不accept就导致了看不到这个现象

journald和rsyslogd

发表于 2021-01-28 | 分类于 Linux

journald和rsyslogd

碰到rsyslog-8.24.0-34.1.al7.x86_64 的 rsyslogd 占用内存过高,于是分析了一下原因并学习了一下系统日志、rsyslog、journald之间的关系,流水账记录此文。

rsyslogd 占用内存过高的分析

rsyslogd使用了大概1.6-2G内存,不正常(正常情况下内存占用30-50M之间)

现象:

image.png

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
KiB Mem :  7971268 total,   131436 free,  7712020 used,   127812 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 43484 avail Mem

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
24850 admin 20 0 8743896 5.1g 0 S 2.0 66.9 1413:55 java
1318 root 20 0 2380404 1.6g 536 S 0.0 21.6 199:09.36 rsyslogd

# systemctl status rsyslog
● rsyslog.service - System Logging Service
Loaded: loaded (/usr/lib/systemd/system/rsyslog.service; enabled; vendor preset: disabled)
Active: active (running) since Tue 2020-10-20 16:01:01 CST; 3 months 8 days ago
Docs: man:rsyslogd(8)
http://www.rsyslog.com/doc/
Main PID: 1318 (rsyslogd)
CGroup: /system.slice/rsyslog.service
└─1318 /usr/sbin/rsyslogd -n

Jan 28 09:10:07 iZwz95gaul6x9167sqdqz4Z rsyslogd[1318]: sd_journal_get_cursor() failed: 'Cannot assign requested address' [v8.24.0-34.1.al7]
Jan 28 09:10:07 iZwz95gaul6x9167sqdqz4Z rsyslogd[1318]: imjournal: journal reloaded... [v8.24.0-34.1.al7 try http://www.rsyslog.com/e/0 ]
Jan 28 10:27:48 iZwz95gaul6x9167sqdqz4Z rsyslogd[1318]: sd_journal_get_cursor() failed: 'Cannot assign requested address' [v8.24.0-34.1.al7]
Jan 28 10:27:49 iZwz95gaul6x9167sqdqz4Z rsyslogd[1318]: imjournal: journal reloaded... [v8.24.0-34.1.al7 try http://www.rsyslog.com/e/0 ]
Jan 28 11:45:23 iZwz95gaul6x9167sqdqz4Z rsyslogd[1318]: sd_journal_get_cursor() failed: 'Cannot assign requested address' [v8.24.0-34.1.al7]
Jan 28 11:45:24 iZwz95gaul6x9167sqdqz4Z rsyslogd[1318]: imjournal: journal reloaded... [v8.24.0-34.1.al7 try http://www.rsyslog.com/e/0 ]
Jan 28 13:03:00 iZwz95gaul6x9167sqdqz4Z rsyslogd[1318]: sd_journal_get_cursor() failed: 'Cannot assign requested address' [v8.24.0-34.1.al7]
Jan 28 13:03:01 iZwz95gaul6x9167sqdqz4Z rsyslogd[1318]: imjournal: journal reloaded... [v8.24.0-34.1.al7 try http://www.rsyslog.com/e/0 ]
Jan 28 14:20:42 iZwz95gaul6x9167sqdqz4Z rsyslogd[1318]: sd_journal_get_cursor() failed: 'Cannot assign requested address' [v8.24.0-34.1.al7]
Jan 28 14:20:42 iZwz95gaul6x9167sqdqz4Z rsyslogd[1318]: imjournal: journal reloaded... [v8.24.0-34.1.al7 try http://www.rsyslog.com/e/0 ]


# grep HUPed /var/log/messages
Jan 24 03:39:15 iZwz95gaul6x9167sqdqz4Z rsyslogd: [origin software="rsyslogd" swVersion="8.24.0-34.1.al7" x-pid="1318" x-info="http://www.rsyslog.com"] rsyslogd was HUPed

# journalctl --verify
PASS: /var/log/journal/20190829214900434421844640356160/system@efef6fd56e2e4c9f861d0be25c8c0781-0000000001567546-0005b9e2e02a0a4f.journal
PASS: /var/log/journal/20190829214900434421844640356160/system@efef6fd56e2e4c9f861d0be25c8c0781-00000000015ae56b-0005b9ea76e922e9.journal
1be1e0: Data object references invalid entry at 1d03018
File corruption detected at /var/log/journal/20190829214900434421844640356160/system.journal:1d02d80 (of 33554432 bytes, 90%).
FAIL: /var/log/journal/20190829214900434421844640356160/system.journal (Bad message)

journalctl --verify命令检查发现系统日志卷文件损坏

问题根因

来自redhat官网的描述

image.png

以下是现场收集到的日志:

image.png

主要是rsyslogd的sd_journal_get_cursor报错,然后导致内存泄露。

journald 报Bad message, 跟rsyslogd内存泄露完全没关系,实际上升级rsyslogd后也有journald bad message,但是rsyslogd的内存一直稳定在30M以内

这个CSDN的文章中有完全一样的症状 但是作者的结论是:这是systemd的bug,在journald需要压缩的时候就会发生这个问题。实际上我用的是 systemd-219-62.6.al7.9.x86_64 比他描述的已经修复的版本还要要新,也还是有这个问题,所以这个结论是不对的

解决办法

1、重启rsyslog systemctl restart rsyslog 可以释放内存

2、升级rsyslog到rsyslog-8.24.0-38.1.al7.x86_64或更新的版本才能彻底修复这个问题

一些配置方法

修改配置/etc/rsyslog.conf,增加如下两行,然后重启systemctl restart rsyslog

1
2
3
$imjournalRatelimitInterval 0
$imjournalRatelimitBurst 0
12

1、关掉journal压缩配置

vi /etc/systemd/journald.conf,把#Compress=yes改成Compress=no,之后systemctl restart systemd-journald即可

2、限制rsyslogd 内存大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cat /etc/systemd/system/multi-user.target.wants/rsyslog.service

在Service配置中添加MemoryAccounting=yes,MemoryMax=80M,MemoryHigh=8M三项如下所示。
[Service]
Type=notify
EnvironmentFile=-/etc/sysconfig/rsyslog
ExecStart=/usr/sbin/rsyslogd -n $SYSLOGD_OPTIONS
Restart=on-failure
UMask=0066
StandardOutput=null
Restart=on-failure
MemoryAccounting=yes
MemoryMax=80M
MemoryHigh=8M

OOM kill

rsyslogd内存消耗过高后导致了OOM Kill

image.png

RSS对应物理内存,单位是4K(page大小),红框两个进程用了5G+2G,总内存是8G,所以触发OOM killer了

每次OOM Kill日志前后总带着systemd-journald的重启

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Jan 28 19:03:04 iZwz95gaul6x9167sqdqz5Z journal: Permanent journal is using 520.0M (max allowed 500.0M, trying to leave 4.0G free of 83.7G available → current limit 520.0M).
Jan 28 19:03:04 iZwz95gaul6x9167sqdqz5Z journal: Journal started
Jan 28 19:03:04 iZwz95gaul6x9167sqdqz5Z kernel: AliYunDun invoked oom-killer: gfp_mask=0x6200ca(GFP_HIGHUSER_MOVABLE), nodemask=(null), order=0, oom_score_adj=0
Jan 28 19:03:04 iZwz95gaul6x9167sqdqz5Z kernel: AliYunDun cpuset=/ mems_allowed=0
Jan 28 19:03:04 iZwz95gaul6x9167sqdqz5Z kernel: CPU: 3 PID: 13296 Comm: AliYunDun Tainted: G OE 4.19.57-15.1.al7.x86_64 #1
Jan 28 19:03:04 iZwz95gaul6x9167sqdqz5Z kernel: Hardware name: Alibaba Cloud Alibaba Cloud ECS, BIOS 8c24b4c 04/01/2014
Jan 28 19:03:04 iZwz95gaul6x9167sqdqz5Z kernel: Call Trace:
Jan 28 19:03:04 iZwz95gaul6x9167sqdqz5Z kernel: dump_stack+0x5c/0x7b
Jan 28 19:03:04 iZwz95gaul6x9167sqdqz5Z kernel: dump_header+0x77/0x29f
***
Jan 28 19:03:04 iZwz95gaul6x9167sqdqz5Z kernel: [ 18118] 0 18118 28218 255 245760 0 0 sshd
Jan 28 19:03:04 iZwz95gaul6x9167sqdqz5Z kernel: Out of memory: Kill process 18665 (java) score 617 or sacrifice child
Jan 28 19:03:04 iZwz95gaul6x9167sqdqz5Z kernel: Killed process 18665 (java) total-vm:8446992kB, anon-rss:4905856kB, file-rss:0kB, shmem-rss:0kB
Jan 28 19:03:04 iZwz95gaul6x9167sqdqz5Z kernel: oom_reaper: reaped process 18665 (java), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
Jan 28 19:03:04 iZwz95gaul6x9167sqdqz5Z systemd: systemd-journald.service watchdog timeout (limit 3min)!
Jan 28 19:03:04 iZwz95gaul6x9167sqdqz5Z rsyslogd: sd_journal_get_cursor() failed: 'Cannot assign requested address' [v8.24.0-34.1.al7]
Jan 28 19:03:04 iZwz95gaul6x9167sqdqz5Z rsyslogd: imjournal: journal reloaded... [v8.24.0-34.1.al7 try http://www.rsyslog.com/e/0 ]
Jan 28 20:14:38 iZwz95gaul6x9167sqdqz5Z rsyslogd: imjournal: journal reloaded... [v8.24.0-57.1.al7 try http://www.rsyslog.com/e/0 ]

image.png

OOM kill前大概率伴随着systemd-journald 重启是因为watch dog timeout(limit 3min),造成timeout的原因是journald定期要把日志刷到磁盘上,然后要么是内存不够,要么是io负载太重,导致刷磁盘这个过程非常慢,于是就timeout了。

当然systemd-journald 重启也不一定意味着OOM Killer,只是肯定是内存比较紧张了。

What is the difference between syslog, rsyslog and syslog-ng?

Basically, they are all the same, in the way they all permit the logging of data from different types of systems in a central repository.

But they are three different project, each project trying to improve the previous one with more reliability and functionalities.

The Syslog project was the very first project. It started in 1980. It is the root project to Syslog protocol. At this time Syslog is a very simple protocol. At the beginning it only supports UDP for transport, so that it does not guarantee the delivery of the messages.

Next came syslog-ng in 1998. It extends basic syslog protocol with new features like:

  • content-based filtering
  • Logging directly into a database
  • TCP for transport
  • TLS encryption

Next came Rsyslog in 2004. It extends syslog protocol with new features like:

  • RELP Protocol support
  • Buffered operation support

rsyslog和journald的基础知识

systemd-journald是用来协助rsyslog记录系统启动服务和服务启动失败的情况等等. systemd-journald使用内存保存记录, 系统重启记录会丢失. 所有还要用rsyslog来记录分类信息, 如上面/etc/rsyslog.d/listen.conf中的syslog分类.

systemd-journald跟随systemd开机就启动,能及时记录所有日志:

1
2
3
4
5
6
7
# systemd-analyze critical-chain systemd-journald.service
The time after the unit is active or started is printed after the "@" character.
The time the unit takes to start is printed after the "+" character.

systemd-journald.service +13ms
└─system.slice
└─-.slice

systemd-journald 由于是使用于内存的登录文件记录方式,因此重新开机过后,开机前的登录文件信息当然就不会被记载了。 为此,我们还是建议启动 rsyslogd 来协助分类记录!也就是说, systemd-journald 用来管理与查询这次开机后的登录信息,而 rsyslogd 可以用来记录以前及现在的所以数据到磁盘文件中,方便未来进行查询喔!

Tips 虽然 systemd-journald 所记录的数据其实是在内存中,但是系统还是利用文件的型态将它记录到 /run/log/ 下面! 不过我们从前面几章也知道, /run 在 CentOS 7 其实是内存内的数据,所以重新开机过后,这个 /run/log 下面的数据当然就被刷新,旧的当然就不再存在了!

其实鸟哥是这样想的,既然我们还有 rsyslog.service 以及 logrotate 的存在,因此这个 systemd-journald.service 产生的登录文件, 个人建议最好还是放置到 /run/log 的内存当中,以加快存取的速度!而既然 rsyslog.service 可以存放我们的登录文件, 似乎也没有必要再保存一份 journal 登录文件到系统当中就是了。单纯的建议!如何处理,依照您的需求即可喔!

system-journal服务监听 /dev/log socket获取日志, 保存在内存中, 并间歇性的写入/var/log/journal目录中.

rsyslog服务启动后监听/run/systemd/journal/socket 获取syslog类型日志, 并写入/var/log/messages文件中.

获取日志时需要记录日志条目的position到/var/lib/rsyslog/imjournal.state文件中.

比如haproxy日志配置:

1
2
3
4
# cat /etc/haproxy/haproxy.cfg
global
# log发给journald(journald监听 /dev/log)
log /dev/log local1 warning

以下是drds 的iptables日志配置,将tcp reset包记录下来,默认iptable日志输出到/varlog/messages中(dmesg也能看到),然后可以通过rsyslog.d 配置将这部分日志输出到单独的文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 配置iptables 日志,增加 [drds] 标识
# cat /home/admin/drds-worker/install/drds_filter.conf
# Generated by iptables-save v1.4.21 on Wed Apr 1 11:39:31 2020
*filter
:INPUT ACCEPT [557:88127]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [527:171711]
-A INPUT -p tcp -m tcp ! --sport 3406 --tcp-flags RST RST -j LOG --log-prefix "[drds] " --log-level 7 --log-tcp-sequence --log-tcp-options --log-ip-options
# -A INPUT -p tcp -m tcp ! --dport 3406 --tcp-flags RST RST -j LOG --log-prefix "[drds] " --log-level7 --log-tcp-sequence --log-tcp-options --log-ip-options
-A OUTPUT -p tcp -m tcp ! --sport 3406 --tcp-flags RST RST -j LOG --log-prefix "[drds] " --log-level 7 --log-tcp-sequence --log-tcp-options --log-ip-options
COMMIT
# Completed on Wed Apr 1 11:39:31 2020

#通过rsyslogd将日志写出到指定位置(不配置的话默认输出到 dmesg)
# cat /etc/rsyslog.d/drds_filter_log.conf
:msg, startswith, "[drds]" -/home/admin/logs/tcp-rt/drds-tcp.log

journald log持久化

创建 /var/log/journal 文件夹后默认会持久化,设置持久化后 /run/log 里面就没有日志了

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
# cat /etc/systemd/journald.conf
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# Entries in this file show the compile time defaults.
# You can change settings by editing this file.
# Defaults can be restored by simply deleting this file.
#
# See journald.conf(5) for details.

[Journal]
#Storage=auto //默认如果有 /var/log/journal 目录就会持久化到这里
Compress=no
#Seal=yes
#SplitMode=uid
#SyncIntervalSec=5m
#RateLimitInterval=30s
#RateLimitBurst=1000
SystemMaxUse=500M //最多保留500M日志文件,免得撑爆磁盘
#SystemKeepFree=
#SystemMaxFileSize=
#RuntimeMaxUse=
#RuntimeKeepFree=
#RuntimeMaxFileSize=
#MaxRetentionSec=
#MaxFileSec=1month
#ForwardToSyslog=yes
#ForwardToKMsg=no
#ForwardToConsole=no
#ForwardToWall=yes
#TTYPath=/dev/console
#MaxLevelStore=debug
#MaxLevelSyslog=debug
#MaxLevelKMsg=notice
#MaxLevelConsole=info
#MaxLevelWall=emerg
#LineMax=48K

清理日志保留1M:journalctl –vacuum-size=1M

设置最大保留500M日志: journalctl –vacuum-size=500

rsyslogd

以下内容来自鸟哥的书:

CentOS 7 除了保有既有的 rsyslog.service 之外,其实最上游还使用了 systemd 自己的登录文件日志管理功能喔!他使用的是 systemd-journald.service 这个服务来支持的。基本上,系统由 systemd 所管理,那所有经由 systemd 启动的服务,如果再启动或结束的过程中发生一些问题或者是正常的讯息, 就会将该讯息由 systemd-journald.service 以二进制的方式记录下来,之后再将这个讯息发送给 rsyslog.service 作进一步的记载。

基本上, rsyslogd 针对各种服务与讯息记录在某些文件的配置文件就是 /etc/rsyslog.conf, 这个文件规定了“(1)什么服务 (2)的什么等级讯息 (3)需要被记录在哪里(设备或文件)” 这三个咚咚,所以设置的语法会是这样:

1
2
3
4
5
6
$cat /etc/rsyslog.conf
服务名称[.=!]讯息等级 讯息记录的文件名或设备或主机
# 下面以 mail 这个服务产生的 info 等级为例:
mail.info /var/log/maillog_info
# 这一行说明:mail 服务产生的大于等于 info 等级的讯息,都记录到
# /var/log/maillog_info 文件中的意思。

syslog 所制订的服务名称与软件调用的方式

CentOS 7.x 默认的 rsyslogd 本身就已经具有远程日志服务器的功能了, 只是默认并没有启动该功能而已。你可以通过 man rsyslogd 去查询一下相关的选项就能够知道啦! 既然是远程日志服务器,那么我们的 Linux 主机当然会启动一个端口来监听了,那个默认的端口就是 UDP 或 TCP 的 port 514

image.png

Server配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ cat /etc/rsyslog.conf
# 找到下面这几行:
# Provides UDP syslog reception
#$ModLoad imudp
#$UDPServerRun 514

# Provides TCP syslog reception
#$ModLoad imtcp
#$InputTCPServerRun 514
# 上面的是 UDP 端口,下面的是 TCP 端口!如果你的网络状态很稳定,就用 UDP 即可。
# 不过,如果你想要让数据比较稳定传输,那么建议使用 TCP 啰!所以修改下面两行即可!
$ModLoad imtcp
$InputTCPServerRun 514

# 2\. 重新启动与观察 rsyslogd 喔!
[root@study ~]# systemctl restart rsyslog.service
[root@study ~]# netstat -ltnp &#124; grep syslog
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:514 0.0.0.0:* LISTEN 2145/rsyslogd
tcp6 0 0 :::514 :::* LISTEN 2145/rsyslogd
# 嘿嘿!你的登录文件主机已经设置妥当啰!很简单吧!

client配置:

1
2
3
$ cat /etc/rsyslog.conf
*.* @@192.168.1.100
#*.* @192.168.1.100 # 若用 UDP 传输,设置要变这样!

常见的几个系统日志有哪些呢?一般而言,有下面几个:

  • /var/log/boot.log: 开机的时候系统核心会去侦测与启动硬件,接下来开始各种核心支持的功能启动等。这些流程都会记录在 /var/log/boot.log 里面哩! 不过这个文件只会存在这次开机启动的信息,前次开机的信息并不会被保留下来!
  • /var/log/cron: 还记得第十五章例行性工作调度吧?你的 crontab 调度有没有实际被进行? 进行过程有没有发生错误?你的 /etc/crontab 是否撰写正确?在这个登录文件内查询看看。
  • /var/log/dmesg: 记录系统在开机的时候核心侦测过程所产生的各项信息。由于 CentOS 默认将开机时核心的硬件侦测过程取消显示, 因此额外将数据记录一份在这个文件中;
  • /var/log/lastlog: 可以记录系统上面所有的帐号最近一次登陆系统时的相关信息。第十三章讲到的 lastlog 指令就是利用这个文件的记录信息来显示的。
  • /var/log/maillog 或 /var/log/mail/*: 记录邮件的往来信息,其实主要是记录 postfix (SMTP 协定提供者) 与 dovecot (POP3 协定提供者) 所产生的讯息啦。 SMTP 是发信所使用的通讯协定, POP3 则是收信使用的通讯协定。 postfix 与 dovecot 则分别是两套达成通讯协定的软件。
  • /var/log/messages: 这个文件相当的重要,几乎系统发生的错误讯息 (或者是重要的信息) 都会记录在这个文件中; 如果系统发生莫名的错误时,这个文件是一定要查阅的登录文件之一。
  • /var/log/secure: 基本上,只要牵涉到“需要输入帐号密码”的软件,那么当登陆时 (不管登陆正确或错误) 都会被记录在此文件中。 包括系统的 login 程序、图形接口登陆所使用的 gdm 程序、 su, sudo 等程序、还有网络连线的 ssh, telnet 等程序, 登陆信息都会被记载在这里;
  • /var/log/wtmp, /var/log/faillog: 这两个文件可以记录正确登陆系统者的帐号信息 (wtmp) 与错误登陆时所使用的帐号信息 (faillog) ! 我们在第十章谈到的 last 就是读取 wtmp 来显示的, 这对于追踪一般帐号者的使用行为很有帮助!
  • /var/log/httpd/*, /var/log/samba/*: 不同的网络服务会使用它们自己的登录文件来记载它们自己产生的各项讯息!上述的目录内则是个别服务所制订的登录文件。

journalctl 常用参数

1
2
3
4
5
6
7
-n or –lines= Show the most recent **n** number of log lines

-f or –follow Like a tail operation for viewing live updates

-S, –since=, -U, –until= Search based on a date. “2019-07-04 13:19:17”, “00:00:00”, “yesterday”, “today”, “tomorrow”, “now” are valid formats. For complete time and date specification, see systemd.time(7)

-u service unit

清理journald日志

journalctl –vacuum-size=1M && journalctl –vacuum-size=500

logrotate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/var/log/cron
{
sharedscripts
postrotate
/bin/kill -HUP `cat /var/run/syslogd.pid 2> /dev/null` 2> /dev/null || true
endscript
}

执行:
sudo /usr/sbin/logrotate --force --verbose /etc/logrotate.d/drds
debug:
sudo /usr/sbin/logrotate -d --verbose /etc/logrotate.d/drds
查看日志:
cat /var/lib/logrotate/logrotate.status

kill -HUP

Generally services keep the log files opened while they are running. This mean that they do not care if the log files are renamed/moved or deleted they will continue to write to the open file handled.

When logrotate move the files, the services keep writing to the same file.

Example: syslogd will write to /var/log/cron.log. Then logrotate will rename the file to /var/log/cron.log.1, so syslogd will keep writing to the open file /var/log/cron.log.1.

Sending the HUP signal to syslogd will force him to close existing file handle and open new file handle to the original path /var/log/cron.log which will create a new file.

The use of the HUP signal instead of another one is at the discretion of the program. Some services like php-fpm will listen to the USR1 signal to reopen it’s file handle without terminating itself.

不过还得看应用是否屏蔽了 HUP 信号

systemd

sudo systemctl list-unit-files –type=service | grep enabled //列出启动项

journalctl -b -1 //复审前一次启动, -2 复审倒数第 2 次启动. 重演你的系统启动的所有消息

sudo systemd-analyze blame sudo systemd-analyze critical-chain

systemd-analyze critical-chain –fuzz 1h

sudo systemd-analyze blame networkd

systemd-analyze critical-chain network.target local-fs.target

img

参考资料

一模一样的症状,但是根因找错了:rsyslog占用内存高

https://access.redhat.com/solutions/3705051

https://sunsea.im/rsyslogd-systemd-journald-high-memory-solution.html

鸟哥 journald 介绍

journalctl tail and cheatsheet

Journal的由来

TCP传输速度案例分析

发表于 2021-01-15 | 分类于 TCP

TCP传输速度案例分析

前言

TCP传输速度受网络带宽和传输窗口的影响(接收、发送、拥塞窗口),带宽我们没办法改变,以下案例主要是讨论rt、窗口如何影响速度。

详细的buffer、rt对TCP传输速度的影响请看这篇:

就是要你懂TCP–性能和发送接收Buffer的关系:发送窗口大小(Buffer)、接收窗口大小(Buffer)对TCP传输速度的影响,以及怎么观察窗口对传输速度的影响。BDP、RT、带宽对传输速度又是怎么影响的

以及 就是要你懂TCP–最经典的TCP性能问题 Nagle和Delay ack

上面两篇以及下面几个案例读完,应该所有TCP传输速度问题都能解决了。

前后端rtt差异大+vip下载慢的案例

来源:https://mp.weixin.qq.com/s/er8vTKZUcahA6-Pf8DZBng 文章中的trace-cmd工具也不错

如下三个链路,有一个不正常了

image.png

首先通过 ss -it dst “ip:port” 来分析cwnd、ssthresh、buffer,到底是什么导致了传输慢

原因TCPLossProbe:

如果尾包发生了丢包,没有新包可发送触发多余的dup ack来实现快速重传,完全依赖RTO超时来重传,代价太大,那如何能优化解决这种尾丢包的情况。也就是在某些情况下一个可以的重传包就能触发ssthresh减半,从而导致传输速度上不来。

本案例中,因为client到TGW跨了地域,导致rtt增大,但是TGW和STGW之间的rtt很小,导致握手完毕后STGW认为和client的rtt很小,所以很快就触发了丢包重传,实际没有丢包,只是rtt变大了,所以触发了如上的TLP( PTO=max(2rtt, 10ms) , 因为只有一次重传并收到了 dup,还是不应该触发TLP,但是因为老版本kernel bug导致,4.0的kernel修复了这个问题, 函数 is_tlp_dupack)

握手完毕后第七号包很快重传了

image.png

观察:

netstat -s |grep TCPLossProbes

解决:

tcp_early_retrans可用于开启和关闭ER和TLP,默认是3(enable TLP and delayed ER),sysctl -w net.ipv4.tcp_early_retrans=2 关掉TLP

小结

kernel版本小于4.0+TLP开启+VIP代理导致RS认为rtt很小,实际比较大,这两个条件下就会出现如上问题。

这个问题一看就是跟client和VIP代理之间的rtt扩大有关系,不过不是因为扩大后发送窗口不够之类导致的。

长肥网络(高rtt)场景下tcp_metrics记录的ssthresh太小导致传输慢的案例

https://www.atatech.org/articles/109967

tcp_metrics会记录下之前已关闭tcp 连接的状态,包括发送端拥塞窗口和拥塞控制门限,如果之前网络有一段时间比较差或者丢包比较严重,就会导致tcp 的拥塞控制门限ssthresh降低到一个很低的值,这个值在连接结束后会被tcp_metrics cache 住,在新连接建立时,即使网络状况已经恢复,依然会继承 tcp_metrics 中cache 的一个很低的ssthresh 值,在长肥管道情况下,新连接经历短暂的“慢启动”后,随即进入缓慢的拥塞控制阶段, 导致连接速度很难在短时间内上去。而后面的连接,需要很特殊的场景之下才能将ssthresh 再次推到一个比较高的值缓存下来,因此很有很能在接下来的很长一段时间,连接的速度都会处于一个很低的水平

因为 tcp_metrics记录的ssthresh非常小,导致后面新的tcp连接传输数据时很快进入拥塞控制阶段,如果传输的文件不大的话就没有机会将ssthresh撑大。除非传输一个特别大的文件,忍受拥塞控制阶段的慢慢增长,最后tcp_metrics记录下撑大后的ssthresh,整个网络才会恢复正常。

所以关闭 tcp_metrics其实是个不错的选择: net.ipv4.tcp_no_metrics_save = 1

或者清除: sudo ip tcp_metrics flush all

从系统cache中查看 tcp_metrics item

$sudo ip tcp_metrics show | grep  100.118.58.7
100.118.58.7 age 1457674.290sec tw_ts 3195267888/5752641sec ago rtt 1000us rttvar 1000us ssthresh 361 cwnd 40 ----这两个值对传输性能很重要

192.168.1.100 age 1051050.859sec ssthresh 4 cwnd 2 rtt 4805us rttvar 4805us source 192.168.0.174 ---这条记录有问题,缓存的ssthresh 4 cwnd 2都太小,传输速度一定慢 

清除 tcp_metrics, sudo ip tcp_metrics flush all 
关闭 tcp_metrics 功能,net.ipv4.tcp_no_metrics_save = 1
sudo ip tcp_metrics delete 100.118.58.7

每个连接的ssthresh默认是个无穷大的值,但是内核会cache对端ip上次的ssthresh(大部分时候两个ip之间的拥塞窗口大小不会变),这样大概率到达ssthresh之后就基本拥塞了,然后进入cwnd的慢增长阶段。

长肥网络(rt很高、带宽也高)下接收窗口对传输性能的影响

最后通过一个实际碰到的案例,涉及到了接收窗口、发送Buffer以及高延时情况下的性能问题

案例描述:从中国访问美国的服务器下载图片,只能跑到220K,远远没有达到带宽能力,其中中美之间的网络延时时150ms,这个150ms已经不能再优化了。业务结构是:

client ——150ms—–>>>LVS—1ms–>>>美国的统一接入server—–1ms—–>>>nginx

通过下载一个4M的文件大概需要20秒,分别在client和nginx上抓包来分析这个问题(统一接入server没权限上去)

Nginx上抓包

image.png

从这里可以看到Nginx大概在60ms内就将4M的数据都发完了

client上抓包

image.png

从这个图上可以清楚看到大概每传输大概30K数据就有一个150ms的等待平台,这个150ms基本是client到美国的rt。

从我们前面的阐述可以清楚了解到因为rt比较高,统一接入server每发送30K数据后要等150ms才能收到client的ack,然后继续发送,猜是因为上面设置的发送buffer大概是30K。

检查统一接入server的配置,可以看到接入server的配置里面果然有个32K buffer设置

将buffer改大

速度可以到420K,但是还没有跑满带宽:

image.png

image.png

接着看一下client上的抓包

image.png

可以清楚看到 client的接收窗口是64K, 64K*1000/150=426K 这个64K很明显是16位的最大值,应该是TCP握手有一方不支持window scaling factor

那么继续分析一下握手包,syn:

image.png

说明client是支持的,再看 syn+ack:

image.png

可以看到服务端不支持,那就最大只能用到64K。需要修改服务端代理程序,这主要是LVS或者代理的锅。

如果内网之间rt很小这个锅不会爆发,一旦网络慢一点就把问题恶化了

比如这是这个应用的开发人员的反馈:

image.png

长肥网络就像是很长很宽的高速公路,上面可以同时跑很多车,而如果发车能力不够,就容易跑不满高速公路。
在rt很短的时候可以理解为高速公路很短,所以即使发车慢也还好,因为车很快就到了,到了后就又能发新车了。rt很长的话就要求更大的仓库了。

整个这个问题,我最初拿到的问题描述结构是这样的(不要笑程序员连自己的业务结构都描述不清):

client ——150ms—–>>>nginx

实际开发人员也不能完全描述清楚结构,从抓包中慢慢分析反推他们的结构,到最后问题的解决。

这个案例综合了发送窗口(32K)、接收窗口(64K,因为握手LVS不支持window scale)、rt很大将问题暴露出来(跨国网络,rt没法优化)。

nginx buffer 分析参考案例:https://juejin.cn/post/6875223721615818765 nginx上下游收发包速率不一致导致nginx buffer打爆, 关闭nginx proxy_buffering 可解 (作者:挖坑的张师傅)

image.png

应用层发包逻辑影响了BDP不能跑满

来自 dog250: 一行代码解决scp在Internet传输慢的问题(RT高的网络环境)

用scp在长链路上传输文件竟然慢到无法忍受!100~200毫秒往返时延的链路,wget下载文件吞吐可达40MBps,scp却只有9MBps。

这次不是因为buffer导致BDP跑不满,而是也scp业务层有自己流控的逻辑导致发包慢了

SSH允许在一个TCP连接上复用多个channel,需要对每一个channel做流控以保证公平,所以每个channel必须自己做而不是使用TCP的流控,OpenSSH的实现有问题。

delay ack拉高实际rt的案例

这个案例跟速度没有关系,只是解析监控图表上的rt为什么不符合逻辑地偏高了。

如下业务监控图:实际处理时间(逻辑服务时间1ms,rtt2.4ms,加起来3.5ms),但是系统监控到的rt(蓝线)是6ms,如果一个请求分很多响应包串行发给client,这个6ms是正常的(1+2.4*N),但实际上如果send buffer足够的话,按我们前面的理解多个响应包会并发发出去,所以如果整个rt是3.5ms才是正常的。

image.png

抓包来分析原因:

image.png

实际看到大量的response都是3.5ms左右,符合我们的预期,但是有少量rt被delay ack严重影响了

从下图也可以看到有很多rtt超过3ms的,这些超长时间的rtt会最终影响到整个服务rt

image.png

参考资料

SSH Performance

Why when I transfer a file through SFTP, it takes longer than FTP?

mac 路由和DSN相关知识

发表于 2021-01-03 | 分类于 others

mac 路由和DSN相关知识

Mac 下上网,尤其是在双网卡一起使用的时候, 一个网卡连内网,一个网卡连外网,经常会碰到ip不通(路由问题,比较好解决)或者dns解析不了问题. 或者是在通过VPN连公司网络会插入一些内网route,导致部分网络访问不了.

即使对Linux下的DNS解析无比熟悉了,但是在Mac下还是花了一些时间来折腾,配置不好路由和DNS是不配使用Mac的,所以记录下。

route

如果ip不通就看路由表, 根据内外网IP增加/删除相应的路由信息,常用命令如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
sudo route -n add 10.176/16 192.168.3.1
sudo route -n add -net 10.176.0.0/16 192.168.3.1 //添加路由, 访问10.176.0.0/16 走192.168.3.1
sudo route -n delete -net 10.176.0.0/16 192.168.3.1
sudo route -n delete 0.0.0.0 192.168.184.1
sudo route -n add 0.0.0.0 192.168.184.1 //添加默认路由访问外网

sudo route -n delete 0.0.0.0 192.168.3.1
sudo route -n add 10.176/16 192.168.3.1
sudo route -n delete 0.0.0.0 192.168.184.1 -ifscope en0
sudo route -n add 0.0.0.0 192.168.184.1
sudo networksetup -setdnsservers 'Apple USB Ethernet Adapter' 202.106.196.115 202.106.0.20 114.114.114.114

sudo networksetup -setdnsservers 'USB 10/100/1000 LAN' 223.5.5.5 30.30.30.30 114.114.114.114

ip route get 8.8.8.8 //linux
route get 8.8.8.8 //macOS
netstat -rn //查看路由
netstat -nr -f inet //只看ipv4相关路由

如果本来IP能通,连上VPN后就通不了,那一定是VPN加入了一些更精细的路由导致原来的路由不通了,那么很简单停掉VPN就能恢复或者增加一条更精确的路有记录进去,或者删掉VPN增加的某条路由.

DNS 解析

mac下DNS解析问题搞起来比较费劲,相应的资料也不多, 经过上面的操作后如果IP能通,域名解析有问题,一般都是DNS解析出了问题

mac下 /etc/resolv.conf 不再用来解析域名, 只有nslookup能用到resolv.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
cat /etc/resolv.conf                                                
#
# macOS Notice
#
# This file is not consulted for DNS hostname resolution, address
# resolution, or the DNS query routing mechanism used by most
# processes on this system.
#
# To view the DNS configuration used by this system, use:
# scutil --dns

scutil --dns //查看DNS 解析器
scutil --nwi //查看网络

解析出了问题先检查nameserver

scutil –dns 一般会展示一大堆的resolver, 每个resolver又可以有多个nameserver

A scoped DNS query can use only specified network interfaces (e.g. Ethernet or WiFi), while non-scoped can use any available interface.

More verbosely, an application that wants to resolve a name, sends a request (either scoped or non-scoped) to a resolver (usually a DNS client application), if the resolver does not have the answer cached, it sends a DNS query to a particular nameserver (and this goes through one interface, so it is always “scoped”).

In your example resolver #1 “for scoped queries” can use only en0 interface (Ethernet).

修改 nameserver

默认用第一个resolver, 如果第一个resolver没有nameserver那么域名没法解析, 可以修改dns resolver的nameserver:

1
2
3
4
5
6
7
8
9
$networksetup -listallnetworkservices  //列出网卡service, 比如 wifi ,以下是我的 macOS 输出
An asterisk (*) denotes that a network service is disabled.
USB 10/100/1000 LAN
Apple USB Ethernet Adapter
Wi-Fi
Bluetooth PAN
Thunderbolt Bridge
$sudo networksetup -setdnsservers 'Wi-Fi' 202.106.196.115 202.106.0.20 114.114.114.114 //修改nameserver
$networksetup -getdnsservers Wi-Fi //查看对应的nameserver, 跟 scutil --dns 类似

如上, 只要是你的nameserver工作正常那么DNS就肯定回复了

删掉所有DNS nameserver:

One note to anyone wanting to remove the DNS, just write “empty” (without the quotes) instead of the DNS: sudo networksetup -setdnsservers <networkservice> empty

networksetup用法

查看设备和配置

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
$networksetup -listallnetworkservices
An asterisk (*) denotes that a network service is disabled.
USB 10/100/1000 LAN
Apple USB Ethernet Adapter
Wi-Fi
Bluetooth PAN
Thunderbolt Bridge
Thunderbolt Bridge 2

#查看网卡配置
$networksetup -getinfo "USB 10/100/1000 LAN"
DHCP Configuration
IP address: 30.25.25.195
Subnet mask: 255.255.255.128
Router: 30.25.25.254
Client ID:
IPv6 IP address: none
IPv6 Router: none
Ethernet Address: 44:67:52:02:16:d4

$networksetup -listallhardwareports
Hardware Port: USB 10/100/1000 LAN
Device: en7
Ethernet Address: 44:67:52:02:16:d4

Hardware Port: Wi-Fi
Device: en0
Ethernet Address: 88:66:5a:10:e4:2b

Hardware Port: Thunderbolt Bridge
Device: bridge0
Ethernet Address: 82:0a:d5:01:b4:00

VLAN Configurations
===================
$networksetup -getinfo "Thunderbolt Bridge"
DHCP Configuration
Client ID:
IPv6: Automatic
IPv6 IP address: none
IPv6 Router: none

//查看wifi和热点
networksetup -listpreferredwirelessnetworks en0
networksetup -getairportnetwork "en0"

dhcp、route、domain配置

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
[-setmanual networkservice ip subnet router]

[-setdhcp networkservice [clientid]]

[-setbootp networkservice]

[-setmanualwithdhcprouter networkservice ip]

[-getadditionalroutes networkservice]

[-setadditionalroutes networkservice [dest1 mask1 gate1] [dest2 mask2 gate2] ..

. [destN maskN gateN]]

#给网卡配置ip、网关
$ networksetup -getinfo "Apple USB Ethernet Adapter" DHCP Configuration
Client ID:
IPv6: Automatic
IPv6 IP address: none
IPv6 Router: none
Ethernet Address: (null)
$networksetup -setmanual "Apple USB Ethernet Adapter" 192.168.100.100 255.255.255.0 192.168.100.1
$networksetup -getinfo "Apple USB Ethernet Adapter"
Manual Configuration
IP address: 192.168.100.100
Subnet mask: 255.255.255.0
Router: 192.168.100.1
IPv6: Automatic
IPv6 IP address: none
IPv6 Router: none
Ethernet Address: (null)

代理配置

1
2
3
4
5
6
//ftp
[-getftpproxy networkservice]

[-setftpproxy networkservice domain portnumber authenticated username password]

[-setftpproxystate networkservice on | off]

网页

1
2
3
4
5
6
[-getwebproxy networkservice]
[-setwebproxy networkservice domain portnumber authenticated username password]
[-setwebproxystate networkservice on | off]

$networksetup -setwebproxy "Built-in Ethernet" proxy.company.com 80
$networksetup -setwebproxy "Built-In Ethernet" proxy.company.com 80 On authusername authpassword

Socks5 代理

1
2
3
4
5
6
$networksetup -setsocksfirewallproxy "USB 10/100/1000 LAN" 127.0.0.1 13659
$networksetup -getsocksfirewallproxy "USB 10/100/1000 LAN"
Enabled: Yes
Server: 127.0.0.1
Port: 13659
Authenticated Proxy Enabled: 0

总结

mac同时连wifi(外网或者vpn)和有线(内网), 如果内网干扰了访问外部ip, 就检查路由表,调整顺序. 如果内网干扰了dns,可以通过scutil –dns查看dns顺序到系统配置里去掉不必要的resolver

参考资料

macOS的networksetup命令来管理网络

在Mac下使用脚本重载proxy自动配置脚本(pac)

1…345…10
twitter @plantegg

twitter @plantegg

191 日志
18 分类
282 标签
RSS
© 2025 twitter @plantegg
由 Hexo 强力驱动
主题 - NexT.Mist
本站总访问量次 本站访客数人次
访问人数 人 次