第五组 多核多线程编程及性能分析
1 实验要求
参照参考文献“利用多核多线程进行程序优化”,在 Linux 环境下,编写多线程程序,分析以下几个因素对程序运行时间的影响:
-
程序并行化
-
线程数目
-
共享资源加锁
-
CPU 亲和
-
cache 优化
掌握多 CPU、多核硬件环境下基本的多线程并行编程技术。
实验内容包括:
-
实验 5.1 观察实验平台物理 cpu、cpu 核和逻辑 cpu 的数目
-
实验 5.2. 单线程/进程串行 vs 两线程并行 vs 三线程加锁并行程序对比
-
实验 5.3. 三线程加锁 vs 三线程不加锁 对比
-
实验 5.4. 针对 Cache 的优化
-
实验 5.5. CPU 亲和力对并行程序影响
-
八种实现方案运行时间对比总结
2 实验环境
硬件:Intel(R) Xeon(R) Platinum 8163 CPU,主频3.5GHz,内存30G
软件:CentOS Linux release 7.9.2009,内核版本Linux-3.10.0
3 实验内容1 观察实验平台物理 cpu、CPU 核和逻辑 cpu 的数目
3.1 实验目的
观察实验所采用的计算机(微机、笔记本电脑)物理 cpu、CPU 核和逻辑 cpu 的数目
-
物理 cpu 数目:主板上实际插入的 cpu 数量,可以数不重复的 physical id 有几个(physical id)。
多路服务器、大型主机系统、集群系统一般可以配置多个物理 CPU;常规微机、笔记本电脑一般只配备 1 个物理 CPU;
-
cpu 核(cpu cores)的数目:单块 CPU 上面能处理数据的芯片组的数量,如双核、四核等;
-
逻辑 cpu 数目:
对不支持超线程 HT 的 CPU,逻辑 cpu 数目=物理 CPU 个数×每颗 CPU 核数,
对支持超线程 HT 的 CPU,逻辑 cpu 数目=物理 CPU 个数×每颗 CPU 核数*2
3.2 实验方法
通过下列命令查看 cpu 相关信息
物理cpu数:
grep 'physical id' /proc/cpuinfo|sort|uniq|wc -l
cpu 核数:
grep 'cpu cores' /proc/cpuinfo|uniq|awk -F ':' '{print $2}'
逻辑 cpu:
cat /proc/cpuinfo| grep "processor"|wc -l
3.3 运行结果及分析
可以看出我的服务器,单cpu,2核,有超线程。
执行命令lscpu
也可以看到相关信息:
4 实验内容2 单线程/进程串行 vs 2 线程并行 vs 3 线程加锁并行程序
4.1 实验目的
参照参考文献“利用多核多线程进行程序优化”,在 Linux 环境下,编写多线程程序,分析以下几个因素对程序运行时间的影响:
-
程序并行化
-
线程数目
-
共享资源加锁
-
CPU 亲和
-
cache 优化
掌握多 CPU、多核硬件环境下基本的多线程并行编程技术。
4.2 实验内容
-
实验 5.1 观察实验平台物理 cpu、cpu 核和逻辑 cpu 的数目
-
实验 5.2. 单线程/进程串行 vs 两线程并行 vs 三线程加锁并行程序对比
-
实验 5.3. 三线程加锁 vs 三线程不加锁 对比
-
实验 5.4. 针对 Cache 的优化
-
实验 5.5. CPU 亲和力对并行程序影响
-
八种实现方案运行时间对比总结
4.3 实验设计原理
医生治病首先要望闻问切,然后才确定病因,最后再对症下药,如果胡乱医治一通,不死也残废。说起来大家都懂的道理,但在软件优化过程中,往往都喜欢犯这样的错误。不分青红皂白,一上来这里改改,那里改改,其结果往往不如人意。
一般将软件优化可分为三个层次:系统层面,应用层面及微架构层面。首先从宏观进行考虑,进行望闻问切,即系统层面的优化,把所有与程序相关的信息收集上来,确定病因。确定病因后,开始从微观上进行优化,即进行应用层面和微架构方面的优化。
-
系统层面的优化:内存不够,CPU 速度过慢,系统中进程过多等
-
应用层面的优化:算法优化、并行设计等
-
微架构层面的优化:分支预测、数据结构优化、指令优化等
软件优化可以在应用开发的任一阶段进行,当然越早越好,这样以后的麻烦就会少很多。
利用并行程序设计模型来设计应用程序,就必须把自己的思维从线性模型中拉出来,重新审视整个处理流程,从头到尾梳理一遍,将能够并行执行的部分识别出来。
可以将应用程序看成是众多相互依赖的任务的集合。将应用程序划分成多个独立的任务,并确定这些任务之间的相互依赖关系,这个过程被称为分解(Decomosition)。分解问题的方式主要有三种:任务分解、数据分解和数据流分解。
在实际应用程序中,采用最多的是应用层面的优化,也会采用微架构层面的优化。将某些优化和维护成本进行对比,往往选择的都是后者。如分支预测优化和指令优化,在大型应用程序中,往往采用的比较少,因为维护成本过高。
本文将从应用层面和微架构层面,对样例程序进行优化。
-
对于应用层面的优化,将采用多线程和 CPU 亲和力技术;
-
在微架构层面,采用 Cache 优化。
最后,总结对比单线程/进程串行程序和其它 7 种多线程并行程序的运行时间,并以图表方式给出具体结果。
要求:
-
以柱状图形式表示上述 5 种情况下程序运行时间的定量测试结果(运行时间)
-
分析对比程序并行化、线程数目、共享资源加、CPU 亲和、cache 优化对程序运行时间的影响,结合程序/进程自身业务逻辑、相互间同步互斥关系等分析解释运行时间产生差异的原因
4.4 实验步骤
步骤一 单线程/样例程序
首先,定义如下数据结构:
#define ORANGE_MAX_VALUE 1000000
#define APPLE_MAX_VALUE 100000000
struct apple {
unsigned long long a;
unsigned long long b;
};
struct orange {
int a[ORANGE_MAX_VALUE];
int b[ORANGE_MAX_VALUE];
};
程序功能为:
求从1一直到 APPLE_MAX_VALUE (100000000) 相加累计的和,并赋值给 apple 的 a 和 b ;求 orange 数据结构中的 a[i]+b[i] 的和,循环ORANGE_MAX_VALUE(1000000) 次。
步骤二 2线程
仔细分析样例程序,运用任务分解的方法 ,不难发现计算 apple 的值和计算 orange 的值,属于完全不相关的两个操作,因此可以并行,改造为两线程程序。
步骤三 3线程加锁
更甚一步,通过数据分解的方法,还可以发现,计算 apple 的值可以分解为两个线程,一个用于计算 apple a 的值,另外一个线程用于计算 apple b 的值 (说明:本方案抽象于实际的应用程序)。但两个线程存在同时访问 apple 的可能性,所以需要加锁访问该数据结构。因此,程序可以改造为3线程加锁程序。
步骤四 3线程不加锁
针对加锁的三线程方案,由于两个线程访问的是 apple 的不同元素,根本没有加锁的必要,所以修改 apple 的数据结构(删除读写锁代码),通过不加锁来提高性能,将程序改造为3线程不加锁的程序。
步骤五 cache优化3线程不加锁
不加锁三线程方案的瓶颈在于 cache,那么让 apple 的两个成员 a 和 b 位于不同的 cache 行中,使用cache优化3线程不加锁的方案,查看效率是否有提高。
步骤六 cache优化3线程加锁
如果对加锁三线程方案中的 apple 数据结构也增加一行类似功能的代码,使用cache优化3线程加锁的方案,效率也是否会提升呢?性能不会有所提升,其原因是加锁的三线程方案效率低下的原因不是 Cache 失效造成的,而是那把锁。
步骤七 2线程设置cpu硬亲和力
CPU 硬亲和力是指进程固定在某个处理器上运行,而不是在不同的处理器之间进行频繁的迁移。这样不仅改善了程序的性能,还提高了程序的可靠性。在双核机器上,针对两线程的方案,如果将计算 apple 的线程绑定到一个 CPU 上,将计算 orange 的线程绑定到另外一个 CPU 上,效率是否会有所提高呢?观察2线程设置cpu硬亲和力的方案。
步骤八 3线程设置cpu硬亲和力
样例程序大部分时间都消耗在计算 apple 上,如果将计算 a 和 b 的值,分布到不同的 CPU 上进行计算,同时考虑 Cache 的影响,效率是否也会有所提升呢?观察3线程设置cpu硬亲和力的方案。
步骤九 使用k-best方法计算每种方案的执行时间并统计
假设重复的执行一个程序,并纪录 K 次最快的时间,如果发现测量的误差 ε 很小,那么用测量的最快值表示过程的真正执行时间, 称这种方法为“ K 次最优(K-Best)方法”,要求设置三个参数:
K: 要求在某个接近最快值范围内的测量值数量。
ε 测量值必须多大程度的接近,即测量值按照升序标号 V1, V2, V3, … , Vi, … ,同时必须满足(1+ ε)Vi >= Vk
M: 在结束测试之前,测量值的最大数量。
这种方法是说,对于测试数据而言,可选择测量时间值最快的K个数据,这K个数据需满足误差不超过给定上限e,并且给定一个测量数据的上限M。测量方法的收敛定义为:已经选出K个数据并满足误差e,且测量总数未超过M。如果 M 次后,不能满足误差标准,则称为不能收敛。
在接下来的所有试验中,采用 K=10,ε=2%,M=200 来获取程序运行时间,同时也对 K 次最优测量方法进行了改进,不是采用最小值来表示程序执行的时间,而是采用 K 次测量值的平均值来表示程序的真正运行时间。
4.5 实验结果及分析
首先使用以下命令编译程序:
gcc core.c -o core -lpthread
注:为了便于阅读,以下实验结果均使用文字粘贴的形式。完整实验结果以及截图请查看附件。
方案一 单线程/样例程序运行结果
运行命令./core 1
,结果如下:
[root@iZuf67lmfh9fmh4kt2tj1tZ ~]# ./core 1
0: 247079 us
1: 251961 us
2: 242715 us
3: 246068 us
4: 244605 us
5: 244865 us
6: 246890 us
7: 249237 us
8: 245200 us
9: 252912 us
10: 248470 us
11: 246115 us
12: 245750 us
13: 252464 us
14: 249290 us
15: 244586 us
break when i = 15
242715
244586
244605
244865
245200
245750
246068
246115
246890
247079
ave = 245387
该结果的含义为,在运行到i = 15的次数时,按照误差e = 2%的要求,结果已经收敛。此时输出最短时间的十次,与这十次的平均时间(um)。
方案二 2线程运行结果
运行命令./core 2
,结果如下:
[root@iZuf67lmfh9fmh4kt2tj1tZ ~]# ./core 2
0: 330504 us
1: 329925 us
2: 329197 us
3: 331537 us
4: 331201 us
5: 331827 us
6: 331890 us
7: 330583 us
8: 331868 us
9: 331370 us
break when i = 9
329197
329925
330504
330583
331201
331370
331537
331827
331868
331890
ave = 330990
方案三 3线程加锁运行结果
运行命令./core 3
,结果如下:
[root@iZuf67lmfh9fmh4kt2tj1tZ ~]# ./core 3
0: 584167 us
1: 576717 us
2: 578642 us
3: 581102 us
4: 577009 us
5: 576732 us
6: 577949 us
7: 579110 us
8: 578838 us
9: 582147 us
break when i = 9
576717
576732
577009
577949
578642
578838
579110
581102
582147
584167
ave = 579241
方案四 3线程不加锁运行结果
运行命令./core 3
,结果如下:
[root@iZuf67lmfh9fmh4kt2tj1tZ ~]# ./core 4
0: 669841 us
1: 661561 us
2: 680345 us
3: 650117 us
4: 643086 us
5: 645143 us
6: 1225782 us
7: 1050413 us
8: 985881 us
9: 868815 us
10: 1244577 us
11: 1231272 us
12: 1229973 us
13: 630369 us
14: 1282727 us
15: 1275831 us
16: 1224734 us
17: 1235469 us
18: 639672 us
19: 646181 us
20: 653500 us
21: 1026539 us
22: 1309693 us
23: 1201069 us
24: 1178396 us
25: 646340 us
26: 1287034 us
27: 699103 us
28: 786617 us
29: 722786 us
30: 621392 us
31: 655178 us
32: 589689 us
33: 626177 us
34: 637984 us
35: 631458 us
36: 638873 us
37: 1266524 us
38: 1281039 us
39: 1275692 us
40: 1319328 us
41: 1320941 us
42: 673564 us
43: 1270562 us
44: 1082509 us
45: 624231 us
46: 631817 us
47: 651681 us
48: 1205875 us
49: 1207337 us
50: 1024041 us
51: 1178656 us
52: 1135735 us
53: 668482 us
54: 669178 us
55: 1158942 us
56: 654793 us
57: 653304 us
58: 1148715 us
59: 1130186 us
60: 1213313 us
61: 1233556 us
62: 1116415 us
63: 653683 us
64: 629235 us
65: 625234 us
66: 1112471 us
67: 666282 us
68: 676149 us
69: 724026 us
70: 957132 us
71: 664699 us
72: 1170905 us
73: 1208428 us
74: 1236392 us
75: 1214011 us
76: 1241794 us
77: 639737 us
78: 625502 us
79: 1142231 us
80: 1074249 us
81: 1209275 us
82: 1192816 us
83: 1159676 us
84: 1197565 us
85: 641503 us
86: 1248510 us
87: 1176431 us
88: 915673 us
89: 637940 us
90: 1232331 us
91: 1210449 us
92: 1153815 us
93: 636252 us
94: 1266479 us
95: 870960 us
96: 1245260 us
97: 693877 us
98: 669818 us
99: 666391 us
100: 681687 us
101: 1281317 us
102: 1282064 us
103: 1266929 us
104: 893189 us
105: 615314 us
106: 1214084 us
107: 656264 us
108: 631186 us
109: 631721 us
110: 1233366 us
111: 1234887 us
112: 1225858 us
113: 1227546 us
114: 1303756 us
115: 1257103 us
116: 684435 us
117: 686757 us
118: 653773 us
119: 694059 us
120: 655205 us
121: 791860 us
122: 616708 us
123: 675172 us
124: 1086611 us
125: 1193082 us
126: 1188875 us
127: 1315580 us
128: 1279353 us
129: 1169398 us
130: 642543 us
131: 629593 us
132: 624152 us
133: 639702 us
134: 625417 us
135: 626926 us
136: 1087482 us
137: 789271 us
138: 1162537 us
139: 1127592 us
140: 1176000 us
141: 1261535 us
142: 827132 us
143: 1088817 us
144: 1263486 us
145: 1216488 us
146: 1247795 us
147: 691489 us
148: 633648 us
149: 1240512 us
150: 1210885 us
151: 1165709 us
152: 1237301 us
153: 964392 us
154: 623031 us
155: 1195233 us
156: 685824 us
157: 1225670 us
158: 986932 us
159: 605027 us
160: 609472 us
161: 646258 us
162: 608477 us
163: 629688 us
164: 741802 us
165: 1219227 us
166: 646038 us
167: 649299 us
168: 650314 us
169: 644190 us
170: 1178048 us
171: 734806 us
172: 1226330 us
173: 677354 us
174: 616153 us
175: 1075455 us
176: 630885 us
177: 662670 us
178: 672546 us
179: 665495 us
180: 677727 us
181: 674083 us
182: 672168 us
183: 678293 us
184: 769666 us
185: 775147 us
186: 608388 us
187: 723804 us
188: 636905 us
189: 608653 us
190: 712307 us
191: 643355 us
192: 1282720 us
193: 1268194 us
194: 644625 us
195: 628791 us
196: 629463 us
197: 666222 us
198: 632263 us
199: 638246 us
Done!
589689
605027
608388
608477
608653
609472
615314
616153
616708
621392
ave = 609927
可以看出,在3线程不加锁的方案非常不稳定,测量时间有时相差两倍。使用k-best测量方法,实验达到200次后,最快十次的误差也没有小于2%,该方案不收敛。最后统计使用最快十次的测量结果,但实际上由于该方案的不稳定,每一次的运行结果都有差异。
方案五 cache优化3线程不加锁运行结果
首先,运行命令./core 51
,验证设置cache = 32的情况。
运行结果一:
[root@iZuf67lmfh9fmh4kt2tj1tZ ~]# ./core 51
0: 290052 us
1: 290421 us
2: 290265 us
3: 288412 us
4: 290176 us
5: 289710 us
6: 289928 us
7: 289610 us
8: 292888 us
9: 291353 us
break when i = 9
288412
289610
289710
289928
290052
290176
290265
290421
291353
292888
ave = 290281
运行结果二:
[root@iZuf67lmfh9fmh4kt2tj1tZ ~]# ./core 51
0: 686163 us
1: 1260278 us
2: 1220356 us
3: 900088 us
4: 771120 us
5: 644669 us
6: 1006439 us
7: 701787 us
8: 1018133 us
9: 1103947 us
10: 705373 us
11: 641914 us
12: 636652 us
13: 652450 us
14: 704889 us
15: 709462 us
16: 697347 us
17: 687845 us
18: 685733 us
19: 691915 us
20: 708688 us
21: 654586 us
22: 1062518 us
23: 829309 us
24: 581543 us
25: 1056024 us
26: 1092542 us
27: 620537 us
28: 665656 us
29: 659054 us
30: 1123204 us
/*由于结果过程手动省略了部分结果*/
190: 1053045 us
191: 1175651 us
192: 682393 us
193: 664193 us
194: 665383 us
195: 662883 us
196: 669583 us
197: 741275 us
198: 1187675 us
199: 1220206 us
Done!
537098
548066
552782
552790
563695
568153
571391
576706
577719
581543
ave = 562994
多次运行改程序,发现运行结果相差甚远。有时可以快速收敛;有时则时分不稳定,不能收敛。将在后文中分析原因。
其次,运行命令./core 52
,验证设置cache = 64的情况,结果如下:
[root@iZuf67lmfh9fmh4kt2tj1tZ ~]# ./core 52
0: 290694 us
1: 289706 us
2: 291507 us
3: 292389 us
4: 292741 us
5: 291248 us
6: 291763 us
7: 291429 us
8: 292275 us
9: 292499 us
break when i = 9
289706
290694
291248
291429
291507
291763
292275
292389
292499
292741
ave = 291625
最后,运行命令./core 53
,验证设置cache = 128的情况,结果如下:
[root@iZuf67lmfh9fmh4kt2tj1tZ ~]# ./core 53
0: 291675 us
1: 292284 us
2: 292018 us
3: 291331 us
4: 292066 us
5: 292317 us
6: 292437 us
7: 292156 us
8: 291722 us
9: 292431 us
break when i = 9
291331
291675
291722
292018
292066
292156
292284
292317
292431
292437
ave = 292043
方案六 cache优化3线程加锁运行结果
运行命令./core 6
,结果如下:
[root@iZuf67lmfh9fmh4kt2tj1tZ ~]# ./core 6
0: 580039 us
1: 583007 us
2: 583409 us
3: 584047 us
4: 583779 us
5: 581629 us
6: 580125 us
7: 579715 us
8: 579770 us
9: 574687 us
break when i = 9
574687
579715
579770
580039
580125
581629
583007
583409
583779
584047
ave = 581020
方案七 2线程设置cpu硬亲和力运行结果
运行命令./core 7
,结果如下:
[root@iZuf67lmfh9fmh4kt2tj1tZ ~]# ./core 7
0: 326898 us
1: 330095 us
2: 329717 us
3: 329027 us
4: 329315 us
5: 330513 us
6: 329084 us
7: 329384 us
8: 329624 us
9: 327609 us
break when i = 9
326898
327609
329027
329084
329315
329384
329624
329717
330095
330513
ave = 329126
方案八 3线程设置cpu硬亲和力运行结果
运行命令./core 8
,结果如下:
[root@iZuf67lmfh9fmh4kt2tj1tZ ~]# ./core 8
0: 291216 us
1: 294163 us
2: 294740 us
3: 294756 us
4: 292137 us
5: 292881 us
6: 291323 us
7: 358787 us
8: 359635 us
9: 358513 us
10: 358705 us
11: 293633 us
12: 292052 us
13: 293695 us
break when i = 13
291216
291323
292052
292137
292881
293633
293695
294163
294740
294756
ave = 293059
实验结果统计与分析
首先观察单线程、2线程和3线程的结果。结果显示,2线程的耗时大于单线程,这是由于线程启停以及线程上下文切换都会引起额外的开销,所以消耗的时间比单线程多。另外主要耗时的线程是apple线程,orrange线程的开销小,所以由于多线程带来的收益没有开销大。从中可以吸取到的经验是,在采用多线程方法设计程序时,如果产生的额外开销大于线程的工作任务,就没有并行的必要。
而3线程的耗时巨大的原因,addx线程和addy线程,由于要竞争同一把锁,所以实际上是串行运行的。比2线程多出的时间则是3线程产生的系统开销。由于访问apple的两个线程访问的是 apple 的不同元素,根本没有加锁的必要,所以可以删除读写锁代码。
3线程不加锁的效果反而不如3线程加锁,这是由于cache伪共享。伪共享指的是多个线程同时读写同一个缓存行的不同变量时导致的 CPU 缓存失效。在本问题中,尽管 addx 线程中操作的a变量与 addy 线程操作的b变量之间没有任何关系,但由于在主内存中邻近,存在于同一个缓存行之中。如果多个线程的变量共享了同一个 CacheLine,任意一方的修改操作都会使得整个 CacheLine 失效,因为 CacheLine 是 CPU 缓存的最小单位。也就意味着,频繁的多线程操作,CPU 缓存将会彻底失效,降级为 CPU core 和主内存的直接交互。
伪共享问题的解决方法便是字节填充。我们只需要保证不同线程的变量存在于不同的 CacheLine 即可,使用多余的字节来填充可以做点这一点,这样就不会出现伪共享问题。针对这种思路,将apple的数据结构更改如下,并测试不同大小的字节填充带来的结果。
struct apple
{
unsigned long long a;
char c[128]; /*32,64,128*/
unsigned long long b;
};
前文中提到,填充32字节的方案每次运行结果大不相同。这是因为在我的处理器,cache 行的大小为64bytes,可以用如下命令查看cache行大小:
cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size
因此,如果使用32字节做填充,a与b有时会被分配到同一个cache行,从而产生cache伪共享的问题,有时则不会。
因为使用32字节填充的不稳定性,之后再考虑字节填充时不会采用这种方案。
观察3线程加锁使用cache填充会如何?
结果是没有什么变化。其原因是加锁的三线程方案效率低下的原因不是 Cache 失效造成的,而是那把锁。
3线程不加锁并行程序带给我们的启示是,在多核和多线程程序设计过程中,要全盘考虑多个线程的访存需求,不要单独考虑一个线程的需求。在选择并行任务分解方法时,要综合考虑访存带宽和竞争问题,将不同处理器和不同线程使用的数据放在不同的 Cache 行中,将只读数据和可写数据分离开。
接下来观察cpu硬亲和力的影响。如果将计算 apple 的线程绑定到一个 CPU 上,将计算 orange 的线程绑定到另外一个 CPU 上,效率是否会有所提高呢?
调整上图的坐标轴,可以得到:
可以看出,设置cpu硬亲和力比不设置的效率略有提升。这是因为在同一个cpu上执行,则cache和LTB中缓存的数据在下一次运行时仍然存在,而进程在cpu之间迁移会提升消耗。Linux 内核进程调度器天生就具有被称为 CPU 软亲和力的特性,这意味着进程通常不会在处理器之间频繁迁移,但是不频繁迁移不代表不迁移。而设置硬亲和力之后,避免这种迁移造成的消耗,在某种程度上硬亲和力比软亲和力具有一定的优势,同时还提高了程序的可靠性。
但是2线程设置cpu硬亲和力的结果仍然不如3线程使用cache优化的结果。这是由于两线程的瓶颈在于大部分时间都消耗在计算 apple 上。如果将apple中addx的部分和addy的部分分别绑定到不同的cpu,效率是否会有提升?
调整上图的坐标轴,可以得到:
设置亲和力的程序所花费的时间略高于采用 Cache 的三线程方案。由于考虑了 Cache 的影响,排除了一级缓存造成的瓶颈,多出的时间主要消耗在系统调用及内核上,可以通过 time 命令来验证
其中./core5
是方案五(不设置cpu硬亲和力),./core8
是方案八(设置cpu硬亲和力)。这给我们了启示,设置硬亲和力也会提升系统开销。
在实际问题中,我们需要综合考虑复杂均衡、数据竞争、系统开销的问题,以免事倍功半。
根据以上分析及实验,对所有改进方案的测试时间做一个综合对比,如下图所示:
最终的实验结果与实验指导书中的不同之处在于,单线程仍然是最优的方案。推测这是由于多线程造成的系统开销的缘故。其他的结果均与实验指导书一致。
4.6 程序代码
实验代码同附件core.c。
#define _GNU_SOURCE
#include <ctype.h>
#include <malloc.h>
#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#define gettid() syscall(__NR_gettid)
#define ORANGE_MAX_VALUE 1000000
#define APPLE_MAX_VALUE 100000000
#define MSECOND 1000000
int set_cpu(int i);
long *k_best(int k, int e, int m, void (*fuc)(void));
int cmp(const void *_a, const void *_b);
struct apple4 {
unsigned long long a;
char c[128];
unsigned long long b;
pthread_rwlock_t rwLock;
};
struct apple3 {
unsigned long long a;
char c[64];
unsigned long long b;
};
struct apple2 {
unsigned long long a;
char c[32];
unsigned long long b;
};
struct apple1 {
unsigned long long a;
unsigned long long b;
pthread_rwlock_t rwLock;
};
struct orange {
int a[ORANGE_MAX_VALUE];
int b[ORANGE_MAX_VALUE];
};
int cpu_nums;
inline int set_cpu(int i) {
cpu_set_t mask;
CPU_ZERO(&mask);
if (2 <= cpu_nums) {
CPU_SET(i, &mask);
if (-1 == sched_setaffinity(gettid(), sizeof(&mask), &mask)) {
perror("set cup failed");
return -1;
}
}
return 0;
}
void *addx__(void *x) {
//设置cpu硬亲和力
if (-1 == set_cpu(1)) {
return NULL;
}
int sum;
for (sum = 0; sum < APPLE_MAX_VALUE; sum++) {
((struct apple1 *)x)->a += sum;
}
return NULL;
}
void *addx__4(void *x) {
//设置cpu硬亲和力,且考虑cache优化
if (-1 == set_cpu(1)) {
return NULL;
}
int sum;
for (sum = 0; sum < APPLE_MAX_VALUE; sum++) {
((struct apple4 *)x)->a += sum;
}
return NULL;
}
void *addx_(void *x) {
//加锁
pthread_rwlock_wrlock(&((struct apple1 *)x)->rwLock);
int sum;
for (sum = 0; sum < APPLE_MAX_VALUE; sum++) {
((struct apple1 *)x)->a += sum;
}
pthread_rwlock_unlock(&((struct apple1 *)x)->rwLock);
return NULL;
}
void *addx(void *x) {
int sum;
for (sum = 0; sum < APPLE_MAX_VALUE; sum++) {
((struct apple1 *)x)->a += sum;
}
return NULL;
}
void *addx2(void *x) {
int sum;
for (sum = 0; sum < APPLE_MAX_VALUE; sum++) {
((struct apple2 *)x)->a += sum;
}
return NULL;
}
void *addx3(void *x) {
int sum;
for (sum = 0; sum < APPLE_MAX_VALUE; sum++) {
((struct apple3 *)x)->a += sum;
}
return NULL;
}
void *addx4(void *x) {
int sum;
for (sum = 0; sum < APPLE_MAX_VALUE; sum++) {
((struct apple4 *)x)->a += sum;
}
return NULL;
}
void *addx4_(void *x) {
int sum;
pthread_rwlock_wrlock(&((struct apple4 *)x)->rwLock);
for (sum = 0; sum < APPLE_MAX_VALUE; sum++) {
((struct apple4 *)x)->a += sum;
}
pthread_rwlock_unlock(&((struct apple4 *)x)->rwLock);
return NULL;
}
void *addy__(void *y) {
if (-1 == set_cpu(2)) {
return NULL;
}
int sum;
for (sum = 0; sum < APPLE_MAX_VALUE; sum++) {
((struct apple1 *)y)->b += sum;
}
return NULL;
}
void *addy__4(void *y) {
if (-1 == set_cpu(2)) {
return NULL;
}
int sum;
for (sum = 0; sum < APPLE_MAX_VALUE; sum++) {
((struct apple4 *)y)->b += sum;
}
return NULL;
}
void *addy_(void *y) {
pthread_rwlock_wrlock(&((struct apple1 *)y)->rwLock);
int sum;
for (sum = 0; sum < APPLE_MAX_VALUE; sum++) {
((struct apple1 *)y)->b += sum;
}
pthread_rwlock_unlock(&((struct apple1 *)y)->rwLock);
return NULL;
}
void *addy(void *y) {
int sum;
for (sum = 0; sum < APPLE_MAX_VALUE; sum++) {
((struct apple1 *)y)->b += sum;
}
return NULL;
}
void *addy2(void *y) {
int sum;
for (sum = 0; sum < APPLE_MAX_VALUE; sum++) {
((struct apple2 *)y)->b += sum;
}
return NULL;
}
void *addy3(void *y) {
int sum;
for (sum = 0; sum < APPLE_MAX_VALUE; sum++) {
((struct apple3 *)y)->b += sum;
}
return NULL;
}
void *addy4(void *y) {
int sum;
for (sum = 0; sum < APPLE_MAX_VALUE; sum++) {
((struct apple4 *)y)->b += sum;
}
return NULL;
}
void *addy4_(void *y) {
int sum;
pthread_rwlock_wrlock(&((struct apple4 *)y)->rwLock);
for (sum = 0; sum < APPLE_MAX_VALUE; sum++) {
((struct apple4 *)y)->b += sum;
}
pthread_rwlock_unlock(&((struct apple4 *)y)->rwLock);
return NULL;
}
void* add__(void* x) {
if (-1 == set_cpu(1)) {
return NULL;
}
int sum;
for (sum = 0; sum < APPLE_MAX_VALUE; sum++) {
((struct apple1*)x)->a += sum;
((struct apple1*)x)->b += sum;
}
return NULL;
}
void* add(void* x) {
int sum;
for (sum = 0; sum < APPLE_MAX_VALUE; sum++) {
((struct apple1*)x)->a += sum;
((struct apple1*)x)->b += sum;
}
return NULL;
}
void func1() {
// 单线程
int sum, index;
struct orange test;
struct apple1 test1;
struct apple2 test2;
struct apple3 test3;
struct apple4 test4;
pthread_t ThreadA, ThreadB;
for (sum = 0; sum < APPLE_MAX_VALUE; sum++) {
test1.a += sum;
test1.b += sum;
}
sum = 0;
for (index = 0; index < ORANGE_MAX_VALUE; index++) {
sum += test.a[index] + test.b[index];
}
}
void func2() {
// 双线程
struct orange test;
struct apple1 test1;
struct apple2 test2;
struct apple3 test3;
struct apple4 test4;
pthread_t ThreadA, ThreadB;
pthread_create(&ThreadA, NULL, add, &test1);
int sum, index;
for (index = 0; index < ORANGE_MAX_VALUE; index++) {
sum += test.a[index] + test.b[index];
}
pthread_join(ThreadA, NULL);
}
void func3() {
//3线程加锁
struct orange test;
struct apple1 test1;
struct apple2 test2;
struct apple3 test3;
struct apple4 test4;
pthread_t ThreadA, ThreadB;
pthread_rwlock_init(&test1.rwLock, NULL);
pthread_create(&ThreadA, NULL, addx_, &test1);
pthread_create(&ThreadB, NULL, addy_, &test1);
int sum, index;
for (index = 0; index < ORANGE_MAX_VALUE; index++) {
sum += test.a[index] + test.b[index];
}
pthread_join(ThreadA, NULL);
pthread_join(ThreadB, NULL);
pthread_rwlock_destroy(&test1.rwLock);
}
void func4() {
// 3线程不加锁
struct orange test;
struct apple1 test1;
struct apple2 test2;
struct apple3 test3;
struct apple4 test4;
pthread_t ThreadA, ThreadB;
pthread_create(&ThreadA, NULL, addx, &test1);
pthread_create(&ThreadB, NULL, addy, &test1);
int sum, index;
for (index = 0; index < ORANGE_MAX_VALUE; index++) {
sum += test.a[index] + test.b[index];
}
pthread_join(ThreadA, NULL);
pthread_join(ThreadB, NULL);
}
void func5_1() {
// 3线程不加锁cache优化1
struct orange test;
struct apple1 test1;
struct apple2 test2;
struct apple3 test3;
struct apple4 test4;
pthread_t ThreadA, ThreadB;
pthread_create(&ThreadA, NULL, addx2, &test2);
pthread_create(&ThreadB, NULL, addy2, &test2);
int sum, index;
for (index = 0; index < ORANGE_MAX_VALUE; index++) {
sum += test.a[index] + test.b[index];
}
pthread_join(ThreadA, NULL);
pthread_join(ThreadB, NULL);
}
void func5_2() {
// 3线程不加锁cache优化2
struct orange test;
struct apple1 test1;
struct apple2 test2;
struct apple3 test3;
struct apple4 test4;
pthread_t ThreadA, ThreadB;
pthread_create(&ThreadA, NULL, addx3, &test3);
pthread_create(&ThreadB, NULL, addy3, &test3);
int sum, index;
for (index = 0; index < ORANGE_MAX_VALUE; index++) {
sum += test.a[index] + test.b[index];
}
pthread_join(ThreadA, NULL);
pthread_join(ThreadB, NULL);
}
void func5_3() {
// 3线程不加锁cache优化3
struct orange test;
struct apple1 test1;
struct apple2 test2;
struct apple3 test3;
struct apple4 test4;
pthread_t ThreadA, ThreadB;
pthread_create(&ThreadA, NULL, addx4, &test4);
pthread_create(&ThreadB, NULL, addy4, &test4);
int sum, index;
for (index = 0; index < ORANGE_MAX_VALUE; index++) {
sum += test.a[index] + test.b[index];
}
pthread_join(ThreadA, NULL);
pthread_join(ThreadB, NULL);
}
void func6() {
// 3线程加锁cache优化3
struct orange test;
struct apple1 test1;
struct apple2 test2;
struct apple3 test3;
struct apple4 test4;
pthread_t ThreadA, ThreadB;
pthread_rwlock_init(&test4.rwLock, NULL);
pthread_create(&ThreadA, NULL, addx4_, &test4);
pthread_create(&ThreadB, NULL, addy4_, &test4);
int sum, index;
for (index = 0; index < ORANGE_MAX_VALUE; index++) {
sum += test.a[index] + test.b[index];
}
pthread_join(ThreadA, NULL);
pthread_join(ThreadB, NULL);
pthread_rwlock_destroy(&test4.rwLock);
}
void func7() {
// 2线程设置cpu硬亲和力
struct orange test;
struct apple1 test1;
struct apple2 test2;
struct apple3 test3;
struct apple4 test4;
pthread_t ThreadA, ThreadB;
pthread_create(&ThreadA, NULL, add__, &test1);
if (-1 == set_cpu(0)) {
return;
}
int sum, index;
for (index = 0; index < ORANGE_MAX_VALUE; index++) {
sum += test.a[index] + test.b[index];
}
pthread_join(ThreadA, NULL);
}
void func8() {
// 3线程不加锁设置cpu亲和力
struct orange test;
struct apple1 test1;
struct apple2 test2;
struct apple3 test3;
struct apple4 test4;
pthread_t ThreadA, ThreadB;
pthread_create(&ThreadA, NULL, addx__4, &test4);
pthread_create(&ThreadB, NULL, addy__4, &test4);
if (-1 == set_cpu(0)) {
return;
}
int sum, index;
for (index = 0; index < ORANGE_MAX_VALUE; index++) {
sum += test.a[index] + test.b[index];
}
pthread_join(ThreadA, NULL);
pthread_join(ThreadB, NULL);
}
int main(int argc, const char *argv[]) {
// insert code here...
long *arr;
int k = 10;
int e = 2;
int m = 200;
int i;
long ave, all = 0;
int funcnum;
cpu_nums = sysconf(_SC_NPROCESSORS_CONF);
if(argc <2){
printf("too few args\n");
return 1;
}
funcnum = atoi(argv[1]);
switch(funcnum){
case 1:
arr = k_best(k, e, m, func1);
break;
case 2:
arr = k_best(k, e, m, func2);
break;
case 3:
arr = k_best(k, e, m, func3);
break;
case 4:
arr = k_best(k, e, m, func4);
break;
case 51:
arr = k_best(k, e, m, func5_1);
break;
case 52:
arr = k_best(k, e, m, func5_2);
break;
case 53:
arr = k_best(k, e, m, func5_3);
break;
case 6:
arr = k_best(k, e, m, func6);
break;
case 7:
arr = k_best(k, e, m, func7);
break;
case 8:
arr = k_best(k, e, m, func8);
break;
default:
printf("wrong arg\n");
return 1;
}
for (i = 0; i < k; i++) {
printf("%ld\n", arr[i]);
all += arr[i];
}
ave = all / k;
printf("ave = %ld\n", ave);
return 0;
}
long *k_best(int k, int e, int m, void (*fuc)(void)) {
long *arr = (long *)malloc(sizeof(long) * k);
memset(arr, 0, sizeof(long) * k);
struct timeval begin, end;
int i;
long time_use;
for (i = 0; i < m; i++) {
if (gettimeofday(&begin, NULL) < 0) {
perror("get time failed1");
}
fuc();
if (gettimeofday(&end, NULL) < 0) {
perror("get time failed2");
}
time_use =
(end.tv_sec - begin.tv_sec) * MSECOND + (end.tv_usec - begin.tv_usec);
printf("%d: %ld us\n", i, time_use);
if (i < k) {
arr[i] = time_use;
if (i == k - 1) {
qsort(arr, k, sizeof(long), cmp);
if (arr[k - 1] <= arr[0] * (1.0 + (float)e / 100)) {
printf("break when i = %d\n", i);
return arr;
}
}
} else {
if (time_use < arr[k - 1]) {
arr[k - 1] = time_use;
qsort(arr, k, sizeof(long), cmp);
}
if (arr[k - 1] <= arr[0] * (1.0 + (float)e / 100)) {
printf("break when i = %d\n", i);
return arr;
}
}
}
printf("Done!\n");
return arr;
}
int cmp(const void *_a, const void *_b) {
long *a = (long *)_a;
long *b = (long *)_b;
return *a - *b;
}
Comments NOTHING