-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy path04.ass
705 lines (703 loc) · 101 KB
/
04.ass
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
[Script Info]
Title: 现代C++进阶:模板元编程与函数式
ScriptType: v4.00+
PlayResX: 1920
PlayResY: 1080
Original Script: woclass
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,微软雅黑,80,&H00FFFFFF,&H0000FFFF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2.0,1,2,10,10,10,1
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:00.13,0:00:07.33,Default,,0,0,0,,我们现在开始上课,嗯,走进今天的主题是编译器优化
Dialogue: 0,0:00:07.33,0:00:10.27,Default,,0,0,0,,然后编译器是什么呢?
Dialogue: 0,0:00:10.27,0:00:27.62,Default,,0,0,0,,众所周知,编译器就是从源代码生成汇编语言,所以这次要从汇编语言的角度来理解编译器是怎么优化的,以及我们如何用好它
Dialogue: 0,0:00:27.62,0:00:34.49,Default,,0,0,0,,首先这是一个系列课,我们今天已经是第四讲了
Dialogue: 0,0:00:34.49,0:00:44.64,Default,,0,0,0,,然后这次我们会基于GCC,然后还有64位某86架,后来具体讲讲
Dialogue: 0,0:00:44.64,0:01:02.19,Default,,0,0,0,,然后第零章首先要讲解一下什么是汇编语言,就是首先是586的64位架构,还有这些寄存器寄存器呢,它又是在CPU里面的
Dialogue: 0,0:01:02.19,0:01:10.32,Default,,0,0,0,,然后它的速度与内存快,如果读到寄存器里,然后再计算就比较高效
Dialogue: 0,0:01:10.32,0:01:19.90,Default,,0,0,0,,所以某六四它提供了这么多寄存器,其中白色的这些是32位模式下就有的
Dialogue: 0,0:01:19.90,0:01:34.71,Default,,0,0,0,,而64位以后,不仅把原来32位的这几个扩充到64位,他还额外追加了八个寄存器,这样我们用起来就更方便了
Dialogue: 0,0:01:34.71,0:01:44.50,Default,,0,0,0,,然后RIP是它的当前执行的这个代码的地址,嗯,也是扩充到64位
Dialogue: 0,0:01:44.50,0:01:56.95,Default,,0,0,0,,然后还有这些妈妈差,还有Y妈妈X妈妈这些这些都是那个用于存储浮点数的寄存器
Dialogue: 0,0:01:56.95,0:02:15.64,Default,,0,0,0,,它们能够就是一个就是这个不是一个有128位宽嘛,然后float 它不是有32位宽嘛,所以这里面可以塞得下四个float 或者塞两个double
Dialogue: 0,0:02:15.64,0:02:24.47,Default,,0,0,0,,然后他们在运算加法的时候就可以两个double 一起来算,这样他效率就更高了
Dialogue: 0,0:02:24.47,0:02:28.05,Default,,0,0,0,,为什么说浮点数用的是这个呢?
Dialogue: 0,0:02:28.05,0:02:43.03,Default,,0,0,0,,因为浮点数就是我们高性能计算中经常用到浮点数,所以就干脆把他们认为是那个嗯你懂的就是比较高效嘛
Dialogue: 0,0:02:43.52,0:02:59.24,Default,,0,0,0,,你爸妈哦,对我得开一下弹幕,嗯,然后我这个就关掉了
Dialogue: 0,0:03:02.07,0:03:12.97,Default,,0,0,0,,然后就是刚才说的32位,只有这八个寄存器,然后到64位以后又新增了八个寄存器
Dialogue: 0,0:03:12.97,0:03:23.59,Default,,0,0,0,,可以看到这几个没有在命名强迫症了,他们直接用数字来编号,然后寄存器多有什么好处呢?
Dialogue: 0,0:03:23.59,0:03:34.50,Default,,0,0,0,,就是比如你的局部变量有15个,16个,那他们就都能够存进寄存器里,而不需要存到内存上
Dialogue: 0,0:03:34.50,0:03:43.72,Default,,0,0,0,,这样编译器它就可以自由的把你的变量存到这个这个局部的这个那个那个变
Dialogue: 0,0:03:43.72,0:03:50.99,Default,,0,0,0,,就是说把你的局部变量变成一个寄存器,这样它读写就更快了
Dialogue: 0,0:03:50.99,0:03:56.87,Default,,0,0,0,,所以说64位除了内存更大,它还有性能的优势
Dialogue: 0,0:03:57.31,0:04:08.36,Default,,0,0,0,,然后就是说有刚刚不是看到就是32位角EAX到64位变成2X他们是什么关系呢?
Dialogue: 0,0:04:08.36,0:04:19.41,Default,,0,0,0,,他们是共用前32个人的关系,就是EAX这个地方的值和2AX的第32位是共用的
Dialogue: 0,0:04:19.41,0:04:30.17,Default,,0,0,0,,同理还有16位的AX它和EX共用的低16位,然后AH是AX高八位,然后ASD8位
Dialogue: 0,0:04:30.17,0:04:41.63,Default,,0,0,0,,当然二开头的这些数字编号寄存器也有,他们是通过BWD,然后没有来区别的,有问题吗?
Dialogue: 0,0:04:42.46,0:04:57.32,Default,,0,0,0,,对,ABX它用的是ZMMABX512 ,它是ZMM,然后然后ABX用的呢是YM它是256
Dialogue: 0,0:04:57.32,0:05:07.59,Default,,0,0,0,,然后还有最普通的SSE,用的是XMM,他们只有那个128位
Dialogue: 0,0:05:07.59,0:05:34.97,Default,,0,0,0,,然后然后就是我们说就是汇编语言大致分为两种,一种是英特尔模式的汇编,他写寄存器就直接写EAX,然后他写操作数,就比如EAX复制给EDX,就把EDX写在前面,EX写后面
Dialogue: 0,0:05:35.34,0:05:42.28,Default,,0,0,0,,然后用MOV指令就把EX赋给EDX,而AT和T会变它
Dialogue: 0,0:05:42.28,0:05:53.12,Default,,0,0,0,,恰恰相反,它读的那个数字写在前面的写的那个数字写在后面的,也就是说这个是往右移动的
Dialogue: 0,0:05:53.12,0:06:00.07,Default,,0,0,0,,而英特尔是往左移动的,包括它的立即数,也是用一个S符号
Dialogue: 0,0:06:00.07,0:06:10.55,Default,,0,0,0,,然后它也是往右移的,嗯,然后降落指令它还要以额外的乘法符号以及它的操作数长度
Dialogue: 0,0:06:10.55,0:06:28.19,Default,,0,0,0,,就是英特尔就直接MOV,而我们这个AT和T它后面要加一个MOVL代表是long ,也就是32位,而B呢就是八位W呢就是16位
Dialogue: 0,0:06:29.42,0:06:33.45,Default,,0,0,0,,然后他的这个访问地址呢也不一样
Dialogue: 0,0:06:33.45,0:06:50.10,Default,,0,0,0,,这里英特尔是更直观的一个括号,然后这里写偏移量,而它是把偏移量写在括号前面,然后后面来一个括号,DX代表它偏移的继承,你在吗?
Dialogue: 0,0:06:52.57,0:07:03.83,Default,,0,0,0,,然后他访问全局变量也不需要这个括号直接以及全局它的地址就用地秤这个立即送的符号
Dialogue: 0,0:07:06.45,0:07:16.53,Default,,0,0,0,,然后然后就是这些种子非常复杂,但是GCC用的就是这一种,没办法,我只好用它
Dialogue: 0,0:07:16.53,0:07:26.90,Default,,0,0,0,,然后就是说就不是有一种函数嘛,然后这个是函数的返回指令,然后这个是赋值指令
Dialogue: 0,0:07:26.90,0:07:30.46,Default,,0,0,0,,所以可以看到这个phone returns
Dialogue: 0,0:07:30.46,0:07:45.66,Default,,0,0,0,,二它被编译成了把42复制给EAX然后返回就可以看出我们这个返回值啊是通过EAX传出去的,明白吗?
Dialogue: 0,0:07:47.29,0:07:56.28,Default,,0,0,0,,就是我这里是用这个指令来编译一个CPP软件,变成一个点S文件
Dialogue: 0,0:07:56.28,0:08:05.88,Default,,0,0,0,,然后杠S就代表生成的不是点O文件哦,而是而是这个点S的汇编语言文件
Dialogue: 0,0:08:05.88,0:08:11.28,Default,,0,0,0,,然后这个呢就是可以让生成代码更简简洁
Dialogue: 0,0:08:11.28,0:08:25.38,Default,,0,0,0,,然后这个呢只是嗯就是说让它的每一条柜边前面有一个注释来提示这一条指令代表的是原文件当中的第几行
Dialogue: 0,0:08:25.38,0:08:34.08,Default,,0,0,0,,比如return 42就代表了目42到1X这一行,这就能够便于我们学习
Dialogue: 0,0:08:36.07,0:08:44.29,Default,,0,0,0,,对的,它会自动设置的,不需要你去指定,明白吗?
Dialogue: 0,0:08:49.97,0:09:11.89,Default,,0,0,0,,然后就是这个解释,然后可以看到我们这里有一个有很多很多参数的那个函数,然后它有123456个参数,它们是通过寄存器这些寄存器传入了
Dialogue: 0,0:09:12.46,0:09:23.32,Default,,0,0,0,,然后可以看到它这里先是把所有传入的寄存器给先存到一个对战上面
Dialogue: 0,0:09:23.32,0:09:30.64,Default,,0,0,0,,RSP就代表对战,而这个负就代表是对战上的某一个地
Dialogue: 0,0:09:35.26,0:09:39.31,Default,,0,0,0,,然后它的返回值不是返回了A吗?
Dialogue: 0,0:09:39.31,0:09:43.95,Default,,0,0,0,,就是说EDI它不是存到了A变量吗?
Dialogue: 0,0:09:43.95,0:09:55.98,Default,,0,0,0,,它现在又取出来设为这个返回值,然后调用它的一看EX就知道它返回了多少,没办法
Dialogue: 0,0:09:58.14,0:10:13.71,Default,,0,0,0,,然后就是这个访问地址这个边这个符号就是先弄一个密集数,就代表它的偏移量,然后是它的寄存器写在括号,是这个意思啊
Dialogue: 0,0:10:15.14,0:10:19.33,Default,,0,0,0,,然后就是刚才不是用这个指令吗?
Dialogue: 0,0:10:19.33,0:10:28.62,Default,,0,0,0,,现在我们可以通过加一个杠O3选项,加了这个flag 之后,他就比刚才更优化
Dialogue: 0,0:10:28.62,0:10:34.62,Default,,0,0,0,,就刚才不是还把BCDF都成了一遍到栈里吗?
Dialogue: 0,0:10:34.62,0:10:47.30,Default,,0,0,0,,现在直接简单了,直接把A传进去,就给送到EAX,就他只需要一行指令就够了,这就是编译器的自动优化
Dialogue: 0,0:10:47.30,0:10:53.97,Default,,0,0,0,,他发现这几个存到站,你后来没有用过啊,就是只有这个是用到的
Dialogue: 0,0:10:53.97,0:10:57.67,Default,,0,0,0,,所以他直接把这些指令全部砍掉了
Dialogue: 0,0:10:57.67,0:11:03.85,Default,,0,0,0,,然后他又一想,哎,我这写入了一遍,赞,又读了一遍,赞没意思啊
Dialogue: 0,0:11:03.85,0:11:10.77,Default,,0,0,0,,我直接把EDI复制给EAX5就好了嘛,所以他就优化成了这样
Dialogue: 0,0:11:11.10,0:11:19.28,Default,,0,0,0,,所以开启这个选项之后,它就能够生成更精炼更高效的代码
Dialogue: 0,0:11:23.72,0:11:25.58,Default,,0,0,0,,我在录吗?
Dialogue: 0,0:11:25.97,0:11:27.39,Default,,0,0,0,,应该在录
Dialogue: 0,0:11:28.74,0:11:41.61,Default,,0,0,0,,然后这个就然后就是除了可以move 来复制之外,这个汇编语言还支持这个乘法,还有加法之类的指令
Dialogue: 0,0:11:41.61,0:11:52.83,Default,,0,0,0,,比如乘法就是IMUL后面又多了一个L和MVL是一样的,代表它的操作数是32位的
Dialogue: 0,0:11:52.83,0:11:56.86,Default,,0,0,0,,然后看一看这是做了什么事呢?
Dialogue: 0,0:11:56.86,0:12:08.37,Default,,0,0,0,,EDI我们知道是代表的ADA的参数,然后ESI就是这样的参数代表B然后这是为什么呢?
Dialogue: 0,0:12:08.37,0:12:24.72,Default,,0,0,0,,看一下这个IMULAES,IEAX就代表把EAX乘等于ESI,也就是EAX乘以EXI以后,再把结果写回到也某86汇编
Dialogue: 0,0:12:24.72,0:12:36.18,Default,,0,0,0,,就是这样,他都是就地层没有写入到另一个寄存器的这种,它是一种CRSC的
Dialogue: 0,0:12:36.18,0:13:05.86,Default,,0,0,0,,是你就所以就是然后这里不是首先是把EDI赋值给ENX嘛,所以它就相当于先就是把EX变成了A,然后再把B乘以到A里面,就变成这个返回值了
Dialogue: 0,0:13:07.79,0:13:17.59,Default,,0,0,0,,然后就是64位的乘法,就是刚才不是说L代表32位嘛,而Q就代表64位
Dialogue: 0,0:13:17.59,0:13:31.32,Default,,0,0,0,,所以我们这个浪浪它是64位的,然后64位也从一系列寄存器变成2系列寄存器,嗯,就更宽了,能存储64位
Dialogue: 0,0:13:36.65,0:13:48.44,Default,,0,0,0,,然后就是整数加,我们以为它会生成一个ADDL,也就是and 没想到它却变成一个LEA指令
Dialogue: 0,0:13:48.44,0:13:50.55,Default,,0,0,0,,LEA是什么呢?
Dialogue: 0,0:13:50.55,0:14:04.42,Default,,0,0,0,,可以看到这里是一个地址的那个操作数,也就是这个地址其实是相当于相当于这个的嗯就是这样的啊
Dialogue: 0,0:14:04.42,0:14:13.04,Default,,0,0,0,,LEA呢又是加载一个表达式的地址,而不是把这个表达式纸给取出来
Dialogue: 0,0:14:13.04,0:14:26.56,Default,,0,0,0,,他取的是这个地址,所以这个就相当于一X等于和乘,也就是这两个会抵消,就相当于1X等于21 2加2X海浪
Dialogue: 0,0:14:27.54,0:14:40.04,Default,,0,0,0,,所以说这是在妙用这个加载地址指令,把它当做按着来操作,它就可以更简练
Dialogue: 0,0:14:40.04,0:14:43.74,Default,,0,0,0,,明白吧?
Dialogue: 0,0:14:45.88,0:14:54.37,Default,,0,0,0,,然后就是除了可以有,也可以直接执行加法,它还可以执行任何一次函数
Dialogue: 0,0:14:54.94,0:15:01.31,Default,,0,0,0,,比如这个表达式,因为它是这里多了一个逗号
Dialogue: 0,0:15:01.31,0:15:04.90,Default,,0,0,0,,八就代表把第二个数乘以了8
Dialogue: 0,0:15:05.46,0:15:15.87,Default,,0,0,0,,因为这种线性变换经常用于地址访问中,所以某86把它做成了这个地址访问的操作符里
Dialogue: 0,0:15:15.87,0:15:25.18,Default,,0,0,0,,然而这个LEA呢却能把这个地址不是去读它的值,而是去读它这个地址读错了
Dialogue: 0,0:15:25.18,0:15:33.13,Default,,0,0,0,,所以说这就变成了就我们可以把任何一个一次函数给写在一条指令
Dialogue: 0,0:15:33.46,0:15:41.81,Default,,0,0,0,,所以说这种一次函数在某86上都是很高效的
Dialogue: 0,0:15:43.27,0:15:51.14,Default,,0,0,0,,然后然后就是刚才说到这里是为什么要线性变换做成指令呢?
Dialogue: 0,0:15:51.14,0:16:14.78,Default,,0,0,0,,就是为了针对这种情况,比如放开AB然后A是一个指针,然后我要返回A括号B这时候你会想A这个是怎么怎么知道A括号B的地址,然后读它呢,你可能会想哦,那简单不就是A加B吗?
Dialogue: 0,0:16:14.78,0:16:28.11,Default,,0,0,0,,不行,因为A它是一个internet 类型的指针,而如果你直接在汇编里面去A加B的话,它会变成一个叉类型的加
Dialogue: 0,0:16:28.11,0:16:46.69,Default,,0,0,0,,所以说你要加类型的加加上这个size of in 成语B就是像这样就是因为inter 的大小是4 ,所以这个偏移量也要乘以4才能访问到正确的地址的
Dialogue: 0,0:16:46.69,0:16:55.34,Default,,0,0,0,,所以这里就用了这个586提供的这个很方便的线性函数来访问
Dialogue: 0,0:16:58.07,0:17:01.49,Default,,0,0,0,,那么这个语句又是啥意思呢?
Dialogue: 0,0:17:01.49,0:17:13.69,Default,,0,0,0,,为什么MOVSLQ哦,原来就是因为我们刚刚用的int 是32位的,而指针和地址都是64位的
Dialogue: 0,0:17:13.69,0:17:25.23,Default,,0,0,0,,所以说要用这个这个64位的A去加上一个32位的B之前首先要把B转换成64位的
Dialogue: 0,0:17:25.23,0:17:34.25,Default,,0,0,0,,可以看到我们这里是把ESI这个32位变成ISI这个64位的了
Dialogue: 0,0:17:34.70,0:17:39.15,Default,,0,0,0,,这样以后才能进行这个线性访问
Dialogue: 0,0:17:39.15,0:17:56.16,Default,,0,0,0,,那么如果要避免呢,就可以用我们SGDinternet 里提供的STGS杠这个塞子呢,它能够保证在64位系统上就是internet 64
Dialogue: 0,0:17:56.16,0:18:02.59,Default,,0,0,0,,而32位系统上相当于unix 32 ,所以就不需要再进行
Dialogue: 0,0:18:02.59,0:18:09.03,Default,,0,0,0,,刚才那个先从32扩展到64 ,它直接就是64位的
Dialogue: 0,0:18:09.03,0:18:26.82,Default,,0,0,0,,所以说它有可能更高效,而且它还有一个好处,就是比如你这个数组的大小超过internet x 也就是你的速度达到超过那个按的31次方了
Dialogue: 0,0:18:26.82,0:18:32.05,Default,,0,0,0,,这时候it 就不仅是效率的问题,它是会出错的
Dialogue: 0,0:18:32.05,0:18:39.77,Default,,0,0,0,,所以我们就要用64位的size 杠T来表示超过了那个大小的索引
Dialogue: 0,0:18:40.14,0:18:57.59,Default,,0,0,0,,你爸爸所以说建议就是你如果再用于这个下标的话,最好用size 杠T包括用于循环体的那个int I 等于0
Dialogue: 0,0:18:57.59,0:19:02.41,Default,,0,0,0,,I小于什么什么,这个也推荐用三个杠7
Dialogue: 0,0:19:03.68,0:19:11.37,Default,,0,0,0,,然后就是刚才说到的浮点数,什么是浮点数呢?
Dialogue: 0,0:19:11.37,0:19:34.60,Default,,0,0,0,,就比如这种123 ,这个是那个这个是整数,然后这个呢就是浮点数,然后浮点数有一个著名的笑话,就是总之就是0.2加0.1不等于0.3 ,这是为什么呢?
Dialogue: 0,0:19:34.60,0:19:57.60,Default,,0,0,0,,因为浮点数它有精度误差,不会手写汇编应该不会吧,不会手写汇编,但是会手写一些编译器指令,比如MM开头的那些东西,行吧,然后就是说浮点数它不是完美,它是有误差的
Dialogue: 0,0:19:57.60,0:20:12.09,Default,,0,0,0,,就所以说发明了定点数,就是2000加100 ,然后再除以1000 ,这样就我们A这样就能够保证它没有误差
Dialogue: 0,0:20:12.09,0:20:20.65,Default,,0,0,0,,所以浮点数呢它的好处就是能够表示很大范围内的数,但是它有误差
Dialogue: 0,0:20:20.65,0:20:31.68,Default,,0,0,0,,不过我们高性能编程中经常会用到浮点数,所以说CPU也对他们做了专门的指令
Dialogue: 0,0:20:31.68,0:20:42.39,Default,,0,0,0,,比如I的指令就是这刚才不是说A的L是代表那个两个六两个32位整数相加嘛
Dialogue: 0,0:20:43.92,0:20:49.29,Default,,0,0,0,,然后这个S呢就代表single ,也就是单晶共浮点数
Dialogue: 0,0:20:49.29,0:21:21.77,Default,,0,0,0,,我们的float 就是单精度浮点,然后add SS就代表这两个数相加,然后他传参数也不是EDIESI了,他直接用XMM系列,就是XMM0 ,那就是长的A一呢就是长的B然后我们把一加到0 ,然后因为正好零是用于返回值的,所以我们这里直接返回了,就可以是A加B了,明白吗?
Dialogue: 0,0:21:26.43,0:21:47.31,Default,,0,0,0,,参数是零和1 ,返回值是0 ,然后就是提刚才提到了XMM0这个系列的寄存器,它们都有128位宽的,它可以容纳四个float 或者两个double
Dialogue: 0,0:21:47.66,0:21:59.37,Default,,0,0,0,,刚才的案例,因为只有一个float 存在一个大128位的计算器,所以只用到了它的最低32位
Dialogue: 0,0:21:59.37,0:22:01.66,Default,,0,0,0,,但是这样也没问题
Dialogue: 0,0:22:01.66,0:22:07.37,Default,,0,0,0,,因为我们刚才说的是,and SS它会只会加最低位
Dialogue: 0,0:22:07.37,0:22:12.23,Default,,0,0,0,,这就要说到这里就是NSS什么意思呢?
Dialogue: 0,0:22:12.23,0:22:14.51,Default,,0,0,0,,它要分成三个部分
Dialogue: 0,0:22:14.51,0:22:23.09,Default,,0,0,0,,首先是第一个S它代表的是标量,也就是说只对它最低32位去计算
Dialogue: 0,0:22:23.09,0:22:31.95,Default,,0,0,0,,然后也可以艾特的PS,这时候就会把XMM里所有的四个float 都进行运算
Dialogue: 0,0:22:32.42,0:22:46.83,Default,,0,0,0,,然后就是第二个S它不一样,它代表的是单精度浮点,也就是float 类型,它也可以是D表示双精度浮点double 类
Dialogue: 0,0:22:47.16,0:22:51.09,Default,,0,0,0,,然后就所有的排列组合共这四种
Dialogue: 0,0:22:53.19,0:23:07.69,Default,,0,0,0,,第一种是add SS是一个floor ,add SD一个double and PS因为XMM有128位可以成四个,所以他会把四个都进行那个加法
Dialogue: 0,0:23:07.69,0:23:14.25,Default,,0,0,0,,就像这样,就是比如这是XMM0 ,这是XM1 ,这是XM0
Dialogue: 0,0:23:14.25,0:23:21.91,Default,,0,0,0,,然后我们把这个加这个乘进去,这个加这个就是同时计算四个的加
Dialogue: 0,0:23:21.91,0:23:35.00,Default,,0,0,0,,但是如果是NSS的话,它只会对最低位进行一个加法,然后其他的位都保留叉MM0原来的值明白了吧?
Dialogue: 0,0:23:37.86,0:23:56.38,Default,,0,0,0,,然后这里是省流助手,就是如果编译器生成了很多大量SS结尾的指令,那就说明他只是在最最低位进行计算,没有进行那个矢量化,就他可能是比较低效的
Dialogue: 0,0:23:56.38,0:24:03.04,Default,,0,0,0,,但如果大多数都是PS的话,就说明一次能够处理四个float
Dialogue: 0,0:24:03.04,0:24:06.37,Default,,0,0,0,,那这个生成代码就是比较高
Dialogue: 0,0:24:07.01,0:24:11.10,Default,,0,0,0,,然后为什么我们需要这些指令呢?
Dialogue: 0,0:24:11.10,0:24:16.66,Default,,0,0,0,,就就我直接对单一一个去做加法不就好了吗?
Dialogue: 0,0:24:16.66,0:24:37.43,Default,,0,0,0,,为什么四个打包到一起,因为这样更快呀,就是把四个打包到一起的话,它就可以大约就是相当于本来是四个标量的指令,它现在只要一个标矢量的指令,从而它可以快四倍
Dialogue: 0,0:24:37.43,0:24:49.13,Default,,0,0,0,,所以说它在对于那种以计算为主的程序,能够加快它的运行,编译器也会自动去对它进行优化
Dialogue: 0,0:24:50.15,0:24:59.12,Default,,0,0,0,,然后编译器呢它能够把你对着标亮写的代码转换成一个针对矢量的代码
Dialogue: 0,0:24:59.12,0:25:10.64,Default,,0,0,0,,我们待会会给出例子,这种技术称之为单指令多数据,然后英文简写就是CMDE
Dialogue: 0,0:25:12.22,0:25:31.63,Default,,0,0,0,,对呀对呀,没错,两个double 和四个float ,我们通常用的是四个float ,这个然后就来看一看编译器还有哪些能做的优化
Dialogue: 0,0:25:31.63,0:25:37.17,Default,,0,0,0,,就是首先要使它能够执行一些基本代数化
Dialogue: 0,0:25:37.17,0:25:50.10,Default,,0,0,0,,比如看这个例子,这里C是A加BD是A减B那么它们相加起来是不是B就会被抵消啊,是吧?
Dialogue: 0,0:25:50.10,0:25:53.44,Default,,0,0,0,,然后A加A不是等于2A吗?
Dialogue: 0,0:25:53.44,0:26:07.89,Default,,0,0,0,,那这个括号里就是一个二乘A,而2乘A除二自然就等于A所以编译器很聪明,他直接把A作为返回值,也就是直接给他A了
Dialogue: 0,0:26:08.26,0:26:27.06,Default,,0,0,0,,然后他还有一个点,就是如果我看到A和B都是常量,然后他们还相加起来,那我不是已经知道这两个常量了,我直接把它拿过来,然后相加变成42 ,它就直接优化成return 2了
Dialogue: 0,0:26:29.31,0:26:40.20,Default,,0,0,0,,自己去测一下,然后他更疯狂的是,你甚至可以弄一个for 循环
Dialogue: 0,0:26:40.20,0:26:47.08,Default,,0,0,0,,他看到这个初始值是一个常数,而I每次的值也都是常数
Dialogue: 0,0:26:47.08,0:26:54.83,Default,,0,0,0,,那么它可以把一加到100给优化成直接返回5050 ,没问题的
Dialogue: 0,0:26:56.51,0:27:08.44,Default,,0,0,0,,对呀,第一技师很厉害啊,所以你要用好它的话,你甚至不需要影响可读性,你也能够提升性能
Dialogue: 0,0:27:08.94,0:27:34.59,Default,,0,0,0,,然后当然编译器它没那么聪明,就是如果你把代码写的很复杂,比如用了一些STL容器库,那它就哎这个every push backs 什么意思啊,他就停止思考了,他就会放弃优化,干脆就给你去一个个的去调用维克特
Dialogue: 0,0:27:34.59,0:27:44.88,Default,,0,0,0,,就嗯所以说像victor 这种会在堆上分配内存的容器,编译器通常是不会去进行优化
Dialogue: 0,0:27:44.88,0:27:53.40,Default,,0,0,0,,所以说尽量把你的代码写的简单一点,编译器能够看得懂,它才能帮你优化
Dialogue: 0,0:27:53.40,0:28:14.83,Default,,0,0,0,,如果你用一些很fancy 的,像这种它虽然和原来的结果是一样的,但你用这么复杂的话,编译器就认为我去优化这段很复杂的那个抽象语法树,他需要花很长时间,他就觉得不划算,他就放弃优化
Dialogue: 0,0:28:14.83,0:28:24.72,Default,,0,0,0,,就是就是你不要把答案藏的太深,他的那个搜索范围是有限度的,他不会无限制的搜索
Dialogue: 0,0:28:24.72,0:28:34.41,Default,,0,0,0,,所以你要把代码简化,从而它的搜索速度能变短,然后它就能成功的找到正确的优化
Dialogue: 0,0:28:35.77,0:28:42.93,Default,,0,0,0,,堆上那看一下这个这些东西都是存在堆上的
Dialogue: 0,0:28:43.26,0:28:48.61,Default,,0,0,0,,然后这些是存在栈上的,明白吗?
Dialogue: 0,0:28:51.43,0:28:53.94,Default,,0,0,0,,我去喝口水啊
Dialogue: 0,0:29:01.03,0:29:09.43,Default,,0,0,0,,就是比如victim map cent 这种,一般来说这些老的容器都是在的
Dialogue: 0,0:29:12.42,0:29:15.11,Default,,0,0,0,,为什么他们要分呢?
Dialogue: 0,0:29:15.11,0:29:18.48,Default,,0,0,0,,就全部存在站上不好吗?
Dialogue: 0,0:29:18.48,0:29:29.10,Default,,0,0,0,,有区别就是any 它是固定大小的,它这是一个缺点,但是它的优点是它可以存在站上
Dialogue: 0,0:29:29.10,0:29:32.87,Default,,0,0,0,,那为什么victor 不能存在站上呢?
Dialogue: 0,0:29:32.87,0:29:47.35,Default,,0,0,0,,因为它可以动态的push banks ,也就是它的大小是变化的,而站上呢它只能一次性扩充一定的大小,而不能多次反复的进行扩充
Dialogue: 0,0:29:47.35,0:29:55.47,Default,,0,0,0,,所以这时候就是像map set string 这种可以动态扩展的,它的存储在堆上
Dialogue: 0,0:29:55.47,0:30:09.95,Default,,0,0,0,,也是在战队上,就是你一旦用了一个是一战,其中一个是存在堆上,那全部都上堆了,知道吧?
Dialogue: 0,0:30:12.73,0:30:21.63,Default,,0,0,0,,就是像unique point 这种,他肯定是去调用了new 和delete 嘛,所以也是在堆上有相片呢
Dialogue: 0,0:30:21.63,0:30:37.00,Default,,0,0,0,,就是你要判断最简单的办法就是size of victor call every 你算一下它的大小,它的大小是24 ,这个大小肯定容纳不了多少数据的
Dialogue: 0,0:30:37.00,0:30:43.23,Default,,0,0,0,,所以说它这个存的只是一个指针和victor 的大小数据
Dialogue: 0,0:30:43.23,0:30:54.26,Default,,0,0,0,,而如果你signs of error float 32的话,你会发现它等于32乘4 ,也就是说它的大小是很大的
Dialogue: 0,0:30:54.26,0:31:06.01,Default,,0,0,0,,你只要算这个of ,你就知道它存的是一个指针还是实际的数据没法用
Dialogue: 0,0:31:06.01,0:31:22.09,Default,,0,0,0,,左值艾瑞怎么没法用左子啊,我记得这种也可以的吧,这种也可以的吧,触碰也可以用左子的呀
Dialogue: 0,0:31:24.02,0:31:30.79,Default,,0,0,0,,没理解你的意思,像这种是不行的,但trouble 肯定是可以用桌子的
Dialogue: 0,0:31:33.21,0:31:42.93,Default,,0,0,0,,总之就是刚才那个如果你改成艾瑞,是不是能优化成功呢?
Dialogue: 0,0:31:42.93,0:32:02.47,Default,,0,0,0,,试试看,就是我现在改了,哎,还是优化失败,那我再改一下,感觉用手写的人丢失,它还是优化失败,然后再改呢,把这个一百改成十呢,它优化成功了,哎,这是怎么回事呢?
Dialogue: 0,0:32:03.73,0:32:15.31,Default,,0,0,0,,原来就是代码如果很复杂的话,编译器就放弃优化,因为这样会让它的编译时间变长
Dialogue: 0,0:32:15.31,0:32:21.75,Default,,0,0,0,,所以如果你的代码很简单,那你其实不需要什么优化手段
Dialogue: 0,0:32:21.75,0:32:26.85,Default,,0,0,0,,他能够理解你在干什么,那他就能优化,知道吧?
Dialogue: 0,0:32:27.82,0:32:37.27,Default,,0,0,0,,当然如果你这个的确是想要一百这么大,让它自动在编译器求值,也可以用constant 语法
Dialogue: 0,0:32:37.27,0:32:46.45,Default,,0,0,0,,这个时候这个函数就强制编译器在运行时求值,从而就可以让它花费很长的时间
Dialogue: 0,0:32:46.45,0:32:50.51,Default,,0,0,0,,但只要会让变硬变慢,这是一个缺点
Dialogue: 0,0:32:55.35,0:33:01.93,Default,,0,0,0,,所以说如果你觉得编译器想要pos ,它优化,就用contract support 函数
Dialogue: 0,0:33:01.93,0:33:06.49,Default,,0,0,0,,这样不管你这个花多少时间,他都会去那个
Dialogue: 0,0:33:06.49,0:33:19.47,Default,,0,0,0,,但是是一个support ,有个问题,因为它必须让编译器理解全部,所以它必须全部都是分配在站上的,而不能分配在堆上
Dialogue: 0,0:33:19.47,0:33:39.88,Default,,0,0,0,,就是据说 C++ 200开始victor 就可以分配在编译期分配了,然后new 和delete 也可以cos exponent ,但是 C++ 17还不行, C++ 17的conscience还是只能用STDIV这些在栈上的容
Dialogue: 0,0:33:42.23,0:33:49.67,Default,,0,0,0,,然后你甚至可以就是把它作为一个红那个那个这叫什么来着?
Dialogue: 0,0:33:49.67,0:33:57.39,Default,,0,0,0,,总之就是它即使这个数在哪,它也会去算,但是会非常非常非常的慢
Dialogue: 0,0:33:57.39,0:34:02.62,Default,,0,0,0,,因为这5000次5万次迭代是在编译器进行的
Dialogue: 0,0:34:02.62,0:34:14.48,Default,,0,0,0,,然后是我们第二章很多同学感兴趣,他就是说那个内联函数到底要怎么用啊,他们问你今天来解释一下
Dialogue: 0,0:34:14.48,0:34:21.09,Default,,0,0,0,,首先函数分为两种,一种是外部的函数,一种是内部的函数
Dialogue: 0,0:34:21.09,0:34:23.30,Default,,0,0,0,,什么是外部函数呢?
Dialogue: 0,0:34:23.30,0:34:32.40,Default,,0,0,0,,就是像这种只要一个声明,然后实际的实现在另一个文件的这种就叫外部函数
Dialogue: 0,0:34:33.19,0:34:40.02,Default,,0,0,0,,这种呢编译器没办法优化,它只能生成一个靠指令来调用这个函数
Dialogue: 0,0:34:40.54,0:34:48.14,Default,,0,0,0,,然后P17如果你感兴趣的话,可以看一下这里
Dialogue: 0,0:34:48.56,0:35:04.57,Default,,0,0,0,,然后这个PLT出现就代表这个函数是外面的函数,它会在链接的时候,如果另一个文件定义了Z52的这个符号,它就会把那个符号的地址给填充到这里
Dialogue: 0,0:35:04.91,0:35:14.31,Default,,0,0,0,,所以说如果你别的函数里没有定义Z5S它这里就没办法填充,从而链接器就会报错
Dialogue: 0,0:35:16.59,0:35:28.13,Default,,0,0,0,,然后还有一个就是这是没开优化,开优化以后就没有靠指令了,他直接跳转到这个键盘的儿子这个地区
Dialogue: 0,0:35:28.13,0:35:35.38,Default,,0,0,0,,因为他一样是要返回的嘛,不如让他代为返回,这样也是可以的呀
Dialogue: 0,0:35:41.21,0:35:46.30,Default,,0,0,0,,然后刚才说外部函数会让编译器无法优化
Dialogue: 0,0:35:46.68,0:35:51.54,Default,,0,0,0,,但是如果我们是内部函数呢,什么叫内部函数?
Dialogue: 0,0:35:51.98,0:36:05.79,Default,,0,0,0,,就是声明在声明和定义在同一个文件,就是它定义在这个funk 用它的文件,编译器在编译funk 的同时,它是看得到这个定义的
Dialogue: 0,0:36:06.12,0:36:13.88,Default,,0,0,0,,你们就是说他知道那个里面就是什么也没做,直接返回了A从而它可以优化
Dialogue: 0,0:36:13.88,0:36:20.19,Default,,0,0,0,,也就是说这样的话,它本来是没有开优化,它是会直接去调用的
Dialogue: 0,0:36:20.19,0:36:24.07,Default,,0,0,0,,而开了优化之后,就是这是另一个函数
Dialogue: 0,0:36:24.07,0:36:29.65,Default,,0,0,0,,然后这是调用了它,这个是funk 的函数,这个是other 的函数题
Dialogue: 0,0:36:29.65,0:36:41.54,Default,,0,0,0,,然后如果开启优化的话呢,呃就是它定义在同一个文件里是没有艾特PRT的这样链接器也不用去,到时候把它替换
Dialogue: 0,0:36:41.54,0:36:46.15,Default,,0,0,0,,然后如果在同一个文件里,而且开启了O3
Dialogue: 0,0:36:46.15,0:36:52.94,Default,,0,0,0,,这时候我们发现份额根本没有调用aza ,他直接返回了233 ,为啥呢?
Dialogue: 0,0:36:52.94,0:37:01.43,Default,,0,0,0,,因为编辑器看到就是直接返回A啊,那我这里是不是可以直接替换成return 2加3呢?
Dialogue: 0,0:37:01.43,0:37:03.62,Default,,0,0,0,,它只需要这样就够了
Dialogue: 0,0:37:04.96,0:37:14.64,Default,,0,0,0,,这就是那年他会看到那个函数的实现,从而就直接相当于把这个函数的定义贴到整
Dialogue: 0,0:37:16.06,0:37:18.56,Default,,0,0,0,,没声明音浪也会被内联
Dialogue: 0,0:37:18.56,0:37:21.57,Default,,0,0,0,,你看这个案例,我用音量了吗?
Dialogue: 0,0:37:21.57,0:37:23.58,Default,,0,0,0,,你看我用音量了吗?
Dialogue: 0,0:37:23.58,0:37:31.86,Default,,0,0,0,,它自动帮你音量了,所以音量这个关键字它不是不是说一定要加好吗?
Dialogue: 0,0:37:31.86,0:37:41.33,Default,,0,0,0,,这个关键字是没用的,它不是用来做这个的,它有别的用,但它不是用来那年的,知道吗?
Dialogue: 0,0:37:41.77,0:37:51.18,Default,,0,0,0,,你看我这没有音了,编译器只要看得到这个函数题就行了,它自动帮你晕烂,不需要你去指定
Dialogue: 0,0:37:51.75,0:38:02.35,Default,,0,0,0,,可以看到这里是直接优化了,就是说只要在同一个文件就行了,不要什么阴暗关键字啊
Dialogue: 0,0:38:03.01,0:38:12.58,Default,,0,0,0,,然后就是局部可见函数,就是哎呀这里截图错了,这里应该有个status
Dialogue: 0,0:38:12.58,0:38:23.26,Default,,0,0,0,,也就是说如果我按照声明为static,就代表只有这文件可见,然后它就干脆不生成二的函数嘛
Dialogue: 0,0:38:23.26,0:38:26.56,Default,,0,0,0,,然后这样的话它还是可以内容
Dialogue: 0,0:38:26.56,0:38:33.43,Default,,0,0,0,,如果你不是titanic,它也是可以内容,不过它额外生成给暴露出来了
Dialogue: 0,0:38:35.16,0:38:38.97,Default,,0,0,0,,然后就是我要电视一下这个关键字
Dialogue: 0,0:38:38.97,0:38:58.56,Default,,0,0,0,,首先在现代编译器中,他们都足够智能,知道哪些还是要内灵,你不需要去提醒他,你看这是提醒前是这个代码提醒后他还是这个代码,他都帮你那年了,就是说只要他看得见阿哲的函数题
Dialogue: 0,0:38:58.56,0:39:04.29,Default,,0,0,0,,他就能够那个你加这个关键词根本没用,知道吧?
Dialogue: 0,0:39:04.81,0:39:13.94,Default,,0,0,0,,inline 在 C++ 中有其他功能,但它的功能绝对和内联是没关系的,知道吧?
Dialogue: 0,0:39:14.52,0:39:27.73,Default,,0,0,0,,就是如果你觉得加了音量哦,我觉得加了音量,编译器可以,那一年性能可以提升,你就试试看,你去实测一下时间是不是变快了
Dialogue: 0,0:39:28.21,0:39:33.62,Default,,0,0,0,,你去测一下,你肯定会发现根本没有任何效果
Dialogue: 0,0:39:33.62,0:39:43.03,Default,,0,0,0,,所以说就是我推荐一下你去看这个网站,在这个网站里你能够嗯可以试一下哦
Dialogue: 0,0:39:49.10,0:39:53.81,Default,,0,0,0,,总之这个网站就是相当于一个在线编程网站
Dialogue: 0,0:39:55.50,0:40:02.82,Default,,0,0,0,,你在左边输入那个 C++ 代码,然后右边就能够生成它的汇编代码
Dialogue: 0,0:40:02.82,0:40:06.49,Default,,0,0,0,,然后这个好像还是英特尔支持的
Dialogue: 0,0:40:07.01,0:40:24.98,Default,,0,0,0,,看比如我这里写了一个平方函数,可以看它这里是自动变成了这些指令哦,当然你也可以加上这个叫什么优化开关,可以看到它就优化成这样了
Dialogue: 0,0:40:24.98,0:40:32.23,Default,,0,0,0,,然后你这里也可以选其他产品,其他人家的编译器版本啊什么的都可以
Dialogue: 0,0:40:33.70,0:41:04.13,Default,,0,0,0,,比如我这里换成float ,然后返回自己换成float ,可以看到这里就生成了乘法的SIMD指令的标量法是不是很方便啊,这个网站所以就是现在有些所谓的面试官喜欢考什么原杰斯特和inline ,我是看不懂的这两个东西对优化一点作用都没有
Dialogue: 0,0:41:04.13,0:41:16.06,Default,,0,0,0,,register 在 C++ 中被废除了,就是说你看编译器都能把一个等差数列优化成直接返回5050了
Dialogue: 0,0:41:16.06,0:41:24.42,Default,,0,0,0,,因为你还用提醒他啊,这个变量需要用寄存器保存,你觉得他有那么蠢吗?
Dialogue: 0,0:41:24.42,0:41:25.26,Default,,0,0,0,,没有的
Dialogue: 0,0:41:25.72,0:41:41.17,Default,,0,0,0,,所以以后就是如果你有什么不懂的,就比如你觉得这个东西哎呀,人家是他float x 等于那么然后这样哎呀,这样是不是更快呢?
Dialogue: 0,0:41:41.50,0:41:43.80,Default,,0,0,0,,你看有任何区别吗?
Dialogue: 0,0:41:43.80,0:41:46.96,Default,,0,0,0,,我去要看看有任何区别吗?
Dialogue: 0,0:41:46.96,0:41:48.98,Default,,0,0,0,,没有任何区别啊
Dialogue: 0,0:41:48.98,0:41:53.59,Default,,0,0,0,,其实这个关键字就是一个笑话,知道吗?
Dialogue: 0,0:41:53.59,0:42:01.89,Default,,0,0,0,,阴暗关键字也是个笑话,就是他加不加变音器都能够自动优化的
Dialogue: 0,0:42:01.89,0:42:08.13,Default,,0,0,0,,但是总有某些叫什么所谓的老师哦,他喜欢教这种
Dialogue: 0,0:42:08.13,0:42:16.30,Default,,0,0,0,,呦,同学们要擅长写register in line ,我怕怕不都是这个人
Dialogue: 0,0:42:16.30,0:42:25.00,Default,,0,0,0,,这个人导致就是这个人的教材里说,哎呀,in line 才能内联其实是没有关系的
Dialogue: 0,0:42:25.00,0:42:35.39,Default,,0,0,0,,就是在这个网站多试验一下,然后就能发现这个谈什么强的某些东西都是假的,都是假的
Dialogue: 0,0:42:37.85,0:42:49.21,Default,,0,0,0,,总之笑话到此结束,然后就是指针这件事,就是我们知道C语言的历史遗产就是指针
Dialogue: 0,0:42:49.21,0:42:55.51,Default,,0,0,0,,然后我们这里弄一个这个分化函数,可以看到它在做什么呢?
Dialogue: 0,0:42:55.51,0:43:19.85,Default,,0,0,0,,它把A指向的值取出来,然后写入到C然后再把B指向的值写入到C那你肯定想你看你先复制A到C然后再复制B到C那第一个语句不是浪费了嘛,就是我直接把B复制给C不就行了,因为A的值一样被覆盖了嘛
Dialogue: 0,0:43:19.85,0:43:25.48,Default,,0,0,0,,为啥编译器明明是开了优化,它,还还是复制了两遍呢?
Dialogue: 0,0:43:25.88,0:43:27.59,Default,,0,0,0,,这是为什么呢?
Dialogue: 0,0:43:29.18,0:43:30.72,Default,,0,0,0,,你明白吗?
Dialogue: 0,0:43:32.78,0:43:44.50,Default,,0,0,0,,就是你可以想一想,如果调用者是这样搞的,就是他ABB他传进去的参数前两个是不一样
Dialogue: 0,0:43:44.50,0:44:00.65,Default,,0,0,0,,那最后一个C和B是同样的地址,那这样优化以后,就是如果我把这个去掉,那就直接是C等于B也就是C是BC就代表B等于B所以B的值没有改变
Dialogue: 0,0:44:00.65,0:44:11.78,Default,,0,0,0,,但是如果不去掉的话,那就是A的值付先付给了B然后B的值又付给了B这样的最后B里面的值是A里面的值
Dialogue: 0,0:44:11.78,0:44:18.64,Default,,0,0,0,,所以说就是因为这编译器不知道你B和C是不是纸箱的同一个地址
Dialogue: 0,0:44:18.64,0:44:25.98,Default,,0,0,0,,所以如果它优化掉的话,如果你这两个指甲同一个地址,它的结果就不对了
Dialogue: 0,0:44:25.98,0:44:36.00,Default,,0,0,0,,所以编译器它是保守的,它尽可能的就是他宁愿变慢也不要出错,他宁可慢不要错
Dialogue: 0,0:44:36.00,0:44:54.95,Default,,0,0,0,,这是他的哲学,空是另一回事空是另一回事了,空他不考虑,他只管指针别名就是比如像这样,就是如果这个C和B相等的话,那你想想看这是什么?
Dialogue: 0,0:44:54.95,0:44:57.98,Default,,0,0,0,,这最后的结果就是B等于A呀
Dialogue: 0,0:44:57.98,0:45:02.65,Default,,0,0,0,,而如果你这个优化掉,那这个函数就没有结果了
Dialogue: 0,0:45:02.98,0:45:08.93,Default,,0,0,0,,所以说最后优化之后的结果是不一样的,编译器就不敢优化
Dialogue: 0,0:45:08.93,0:45:20.63,Default,,0,0,0,,如果你想让他放心档摆告诉他B永远不会等于C的,你可以这样写,这是ECC特有的人strict 关键字
Dialogue: 0,0:45:20.63,0:45:35.43,Default,,0,0,0,,它在C语言里是标准,而在 C++ 里却没有标准,所以需要加上这两个前缀代表它是当前编译器特定的,所以也是最后的提示
Dialogue: 0,0:45:35.43,0:45:40.24,Default,,0,0,0,,以后就是他不会再重复两遍了,他只会重复一遍
Dialogue: 0,0:45:40.24,0:45:50.56,Default,,0,0,0,,也就是说他你向他保证B和C是不同的值,从而他就可以安全的把这个给优化掉,知道吧?
Dialogue: 0,0:45:50.56,0:45:58.14,Default,,0,0,0,,不是空的问题,不是空编译器不考虑空不空的问题,空它自动会出错的
Dialogue: 0,0:46:00.10,0:46:09.66,Default,,0,0,0,,然后呢,是这个他你可能觉得哎呀,是不是我程序里每一个出现指针的地方都得加个restrict 呀
Dialogue: 0,0:46:09.66,0:46:14.69,Default,,0,0,0,,不一定你只需要非const 的加一个就行了,为什么呢?
Dialogue: 0,0:46:14.69,0:46:22.75,Default,,0,0,0,,因为只有写入会造成这个指针别名的问题,而如果只读的话就没任何问题啊
Dialogue: 0,0:46:23.74,0:46:31.59,Default,,0,0,0,,就是说你在C里面加个月是这样子的,我是说C和任何一个指针都不重复
Dialogue: 0,0:46:31.59,0:46:34.88,Default,,0,0,0,,而B如果再再声明一遍,没必要
Dialogue: 0,0:46:34.88,0:46:42.74,Default,,0,0,0,,就是说所以我说就是所有非抗日的指针都是命restrict 的话就不要删名了啊
Dialogue: 0,0:46:44.38,0:46:56.99,Default,,0,0,0,,可以看到这样它也是优化成功,这其实和乐视特有点像,就是它禁止多个指针指向同一个地址,除非这些指针是constants
Dialogue: 0,0:46:57.79,0:47:06.36,Default,,0,0,0,,然后除了the strength 是帮助由别人一起知道,哎呦,这个可以放心优化
Dialogue: 0,0:47:06.36,0:47:26.19,Default,,0,0,0,,还有一个让兵器不要优化的那个修饰符,就是可以看到我们这里先是把A赋值为了42 ,然后又返回了乘A也就是说这个时候是先把42写入,然后这里直接被编译器优化成returns I了
Dialogue: 0,0:47:26.19,0:47:36.73,Default,,0,0,0,,因为他想刚刚我写入的是I嘛,但是万一万一你这个A指针是指向一个多个线程,同时在写入的地方
Dialogue: 0,0:47:36.73,0:47:45.26,Default,,0,0,0,,也就是比如我这里负责涉案之后,你这里又有某一个线程插进来,然后把A给修改
Dialogue: 0,0:47:45.26,0:47:49.28,Default,,0,0,0,,这时候这个返回值就可能是不一样的
Dialogue: 0,0:47:49.28,0:47:56.06,Default,,0,0,0,,所以这个时候你可以告诉编译器不要优化这个变量,就是intervale
Dialogue: 0,0:47:56.06,0:48:00.51,Default,,0,0,0,,这时候他就会硬生生的去写一遍,再读一遍
Dialogue: 0,0:48:00.51,0:48:03.98,Default,,0,0,0,,他生怕你这里再插入了什么东西
Dialogue: 0,0:48:03.98,0:48:07.44,Default,,0,0,0,,如果你想优化的话,就不要加过了
Dialogue: 0,0:48:07.44,0:48:26.55,Default,,0,0,0,,如果你想要就是有时候是多线程或者是我想要测试这个内存读写有多快,我就可以用volunteer 关键字,然后它就不会优化掉你的读写,从而你可以测试它的性能,嗯,测试处理器的
Dialogue: 0,0:48:28.20,0:48:32.15,Default,,0,0,0,,然后注意一下他们两个区别很大
Dialogue: 0,0:48:34.27,0:48:43.34,Default,,0,0,0,,use volunteer 是在乘号前面的,而restrict 它毕竟是在乘号后面,这是语法上区别
Dialogue: 0,0:48:43.34,0:48:57.90,Default,,0,0,0,,功能上,volunteers 禁止优化,而且strength 是一个帮助优化的形式啊,它是一个 C++ 标准,而且strength 的全部标准它只是C语言的标准
Dialogue: 0,0:48:57.90,0:49:06.30,Default,,0,0,0,,但 C++ 里不标准,然后restrict 则是一个GCC编译器特有的那个修饰符
Dialogue: 0,0:49:06.30,0:49:13.58,Default,,0,0,0,,但是碰巧在其他编辑器里,也是前面加两个斜杠子这个修饰符
Dialogue: 0,0:49:13.58,0:49:23.38,Default,,0,0,0,,所以说这又是一个不是标准的标准,但是C超委员会却没有把它加入,这是为什么呢?
Dialogue: 0,0:49:23.38,0:49:35.13,Default,,0,0,0,,小潘老师也不知道,对,他们主要就是volunteer 是可以针对不是指针的,而且是的,必定是针对指针
Dialogue: 0,0:49:35.78,0:49:38.42,Default,,0,0,0,,就是比如你可以这样写
Dialogue: 0,0:49:44.77,0:49:56.64,Default,,0,0,0,,就是你这样可以选一个空循环,编译器一定不会优化掉它,因为它的写入它必须被写入,所以编译器就不会优化掉
Dialogue: 0,0:49:56.64,0:50:05.24,Default,,0,0,0,,比如你这里写一个想要拖时间用的东西就很好用啊,就编译器不会把它优化掉了
Dialogue: 0,0:50:05.90,0:50:12.68,Default,,0,0,0,,这时候就不是指针哦,但通常都是伴随着指针来用的
Dialogue: 0,0:50:12.68,0:50:26.81,Default,,0,0,0,,像这种一般都是就是哎呀我跟老板说,哎,你看这个程序性能好差,其实你是在这里偷偷安插一个假的死循环,然后来拖慢他的性能
Dialogue: 0,0:50:26.81,0:50:33.62,Default,,0,0,0,,然后你说老板给我打钱,然后你就把这一行给删掉,然后他变快
Dialogue: 0,0:50:33.62,0:50:38.60,Default,,0,0,0,,老板说,哎呀,你真了不起,就有这种应用,知道吧?
Dialogue: 0,0:50:40.65,0:51:03.17,Default,,0,0,0,,然后就是说到指针,还有一点,就是我们这里有两个写入,第一个是针对A括号0 ,然后是A括号一这两个分别是it 也就是32位的写入在编译之后出来什么变成一个Q变成一个64位的写入
Dialogue: 0,0:51:03.17,0:51:11.95,Default,,0,0,0,,因为两个32位它是连续的地址,所以它能够自动优化成一个64位的写入
Dialogue: 0,0:51:13.62,0:51:29.04,Default,,0,0,0,,对,是没什么关系,但是就是有点关系,就是实际要弄这个多线程一起访问的那个变量的话,会用这个会用这个
Dialogue: 0,0:51:30.49,0:51:38.42,Default,,0,0,0,,俺不是用volatile ,总之就是volatile ,有时候是有点用的
Dialogue: 0,0:51:38.42,0:51:46.19,Default,,0,0,0,,就比比如这个懂的都懂,呃,好像是这个懂的都懂
Dialogue: 0,0:51:46.86,0:51:55.70,Default,,0,0,0,,还有一些就是内存IO中的东西,这时候写入它是写入到硬件的某一个地址上
Dialogue: 0,0:51:56.03,0:52:01.04,Default,,0,0,0,,就是他有可能随时会改变的意思
Dialogue: 0,0:52:03.17,0:52:09.08,Default,,0,0,0,,然后就是刚才说这种写入能够变成单独一个64位的
Dialogue: 0,0:52:09.08,0:52:26.05,Default,,0,0,0,,但是如果你这中间跳了,比如这是零一可以优化的,你这变成02了,中间一这个地方空了,那他没办法优化了呀,他只能变成两个写入32位的,对吧?
Dialogue: 0,0:52:27.38,0:52:32.71,Default,,0,0,0,,你看这是一个四字节,然后这一下子跳到8字节了
Dialogue: 0,0:52:32.71,0:52:40.83,Default,,0,0,0,,他如果再优化成128位的话,这当中有一个A一就要被错误的写错了
Dialogue: 0,0:52:40.83,0:52:43.87,Default,,0,0,0,,他不想写错,他只好放弃优化
Dialogue: 0,0:52:43.87,0:52:57.33,Default,,0,0,0,,所以说我们在设计数据结构的时候,尽可能把它设计的紧凑点,不要把很多无关的变量交错的排列,这样它就很难优化了,知道吧?
Dialogue: 0,0:52:57.93,0:53:04.08,Default,,0,0,0,,然后就是刚才说到是可以把两个int 变成一个int 64
Dialogue: 0,0:53:04.80,0:53:13.98,Default,,0,0,0,,那其实还可以把两四个inter 优化成一个M128 ,也就是XM寄存器
Dialogue: 0,0:53:18.19,0:53:31.99,Default,,0,0,0,,比如这里有四个inter ,因为四个32位就是128位嘛,所以就可以用我们的叉MM来实现一次写入四个in
Dialogue: 0,0:53:34.05,0:53:38.53,Default,,0,0,0,,这是SSE引入的,它是128味道
Dialogue: 0,0:53:38.53,0:53:45.25,Default,,0,0,0,,它本来是存储四个float 的,但它其实也可以存四个inter 没问题
Dialogue: 0,0:53:46.55,0:53:50.39,Default,,0,0,0,,然后为什么叫MOVUPS呢?
Dialogue: 0,0:53:50.39,0:54:00.84,Default,,0,0,0,,之前都是没有这个油的,这个油就是幕布特有的,它代表这个地址不一定会对齐到16字节
Dialogue: 0,0:54:01.36,0:54:04.06,Default,,0,0,0,,也可以是MOBAPS
Dialogue: 0,0:54:04.06,0:54:13.16,Default,,0,0,0,,这时候他就是你向他保证阿迪亚一定是对齐到16字节的,这样可能有微乎其微的提升
Dialogue: 0,0:54:13.78,0:54:24.98,Default,,0,0,0,,然后就是刚才说的四个可以变成128 ,然后八个就可以变成256 ,这个是YMN寄存器
Dialogue: 0,0:54:24.98,0:54:33.74,Default,,0,0,0,,但为什么我明明这样写,他却用了两个叉L的寄存器,而没有优化成一个YMM呢?
Dialogue: 0,0:54:33.74,0:54:35.03,Default,,0,0,0,,这是为什么?
Dialogue: 0,0:54:35.03,0:54:53.76,Default,,0,0,0,,这是因为他不敢保证你的电脑支持AVX,就是说编译器它只能生成,它只能保证因为所有的64位电脑都支持叉MM,所以说它只能保证XMM是可以用的
Dialogue: 0,0:54:53.76,0:55:01.74,Default,,0,0,0,,而256位的YMM只有一些比较新的电脑上才有,他就不敢用了
Dialogue: 0,0:55:03.04,0:55:13.56,Default,,0,0,0,,所以说你要让他敢用,可以加这个加这个flat ,跟他说,你来检测一下我的电脑是否支持IVX
Dialogue: 0,0:55:13.56,0:55:19.98,Default,,0,0,0,,如果我的电脑支持,那你就可以用YMM优化了
Dialogue: 0,0:55:19.98,0:55:29.01,Default,,0,0,0,,比如这里我写了八个,他也的确用了YMN读取一次,然后写入嗯就更高效了
Dialogue: 0,0:55:33.69,0:55:45.25,Default,,0,0,0,,然后就是说但是这个有一个问题,就是如果你这个编出来的程序,你要送给一个没有AVX的电脑上去运行就不行
Dialogue: 0,0:55:45.25,0:55:53.96,Default,,0,0,0,,所以如果你用于发布的话,最好还是不要用这个分量高,然后就是受阻的清零
Dialogue: 0,0:55:53.96,0:56:02.35,Default,,0,0,0,,就是我们可以看到这里什么事也没做,他只是清零,没有调任何标准库函数哦
Dialogue: 0,0:56:02.35,0:56:08.38,Default,,0,0,0,,而看到这里,他却变成了一个对memo session 的调用,为什么呢?
Dialogue: 0,0:56:08.38,0:56:14.15,Default,,0,0,0,,因为编译器哎呀,编译器那个是和那个标准库绑定的
Dialogue: 0,0:56:14.15,0:56:23.59,Default,,0,0,0,,所以说如果你在这里生成一个像是把整个数字清零操作,它会识别到,然后就能够变成
Dialogue: 0,0:56:23.59,0:56:40.38,Default,,0,0,0,,他说哦你记得 C++ 是吧,那标准库肯定是可以用的嘛,它直接变成了一个对标准库的调用了吧,就不需要你自己手动写面部set a doctor ,0doctorand the of in 的
Dialogue: 0,0:56:40.38,0:56:49.85,Default,,0,0,0,,你只要这样写,它能够自动优化成对标准库的调用,而标准库里面的通常是更高效的
Dialogue: 0,0:56:53.46,0:56:56.53,Default,,0,0,0,,然后就是面目靠背也是同理
Dialogue: 0,0:56:56.53,0:57:06.53,Default,,0,0,0,,比如这有个B然后AI等于BI它也能够自动变成这个对memo copy 的调用,就你不需要自己去写
Dialogue: 0,0:57:06.94,0:57:11.56,Default,,0,0,0,,然后就是SIMD加速的问题
Dialogue: 0,0:57:11.56,0:57:21.18,Default,,0,0,0,,就是刚才不是说这里有一个从0到10 20 ,然后填入到这个A数组,对吧?
Dialogue: 0,0:57:23.39,0:57:29.05,Default,,0,0,0,,就是说首先他这是在干什么呢?
Dialogue: 0,0:57:29.05,0:57:39.74,Default,,0,0,0,,是不是挺难看懂的,我来分析一下他在做什么,他就是把这个优化成这样看懂了吗?
Dialogue: 0,0:57:39.74,0:57:48.13,Default,,0,0,0,,就是intel I 等于0 ,I小于0 20 ,这里变成加等于四了,就是一次可以写出四个
Dialogue: 0,0:57:48.13,0:58:16.03,Default,,0,0,0,,然后我们这里在AI写入这101234个数,然后在下一步的时候把CONT给全部加上4 ,然后这里就变成了4567了,这时候再写入就是4567 ,然后再加一下,就是八九十十一,这样和原来的那个运行结果完全一致,但却能够就是更加高效
Dialogue: 0,0:58:16.03,0:58:27.94,Default,,0,0,0,,因为它是用SIME指令并行的去加入,明白了吧?
Dialogue: 0,0:58:27.94,0:58:37.15,Default,,0,0,0,,但是这样有一个缺点,就是因为我这是A加等于4
Dialogue: 0,0:58:37.15,0:58:52.07,Default,,0,0,0,,如果这里不是1024 ,而是1023的话,那他这里就会多写入一个int 查这个地址可能是别人的数据就把它覆盖掉,怎么办呢?
Dialogue: 0,0:58:52.57,0:59:06.21,Default,,0,0,0,,可以用边界测判法,我们用这个主要是我们刚才是一个固定的1024边1起看得到,这是一个四的倍数,可以直接这样写
Dialogue: 0,0:59:06.21,0:59:11.09,Default,,0,0,0,,但是如果NN是不确定的,可能不是四的倍数呀
Dialogue: 0,0:59:11.09,0:59:16.95,Default,,0,0,0,,所以这时候他就生成了这个超复杂的代码,这是啥意思呢?
Dialogue: 0,0:59:16.95,0:59:26.70,Default,,0,0,0,,他做的事就假如N是1023的话,他会先把它变成一个刚才优化过的1020个元素的填入
Dialogue: 0,0:59:26.70,0:59:36.46,Default,,0,0,0,,然后每次可以除以四个,然后还剩下三个是没办法直接一次写入了,它就变成了传统的标量模式
Dialogue: 0,0:59:36.46,0:59:38.66,Default,,0,0,0,,然后每一个处理一个
Dialogue: 0,0:59:38.66,0:59:52.85,Default,,0,0,0,,所以这种思想呢就是如果我的N不是四的倍数,那我先取出其中和4整除的部分,然后剩下不整除的部分,再用第一项的标样代码去做处理
Dialogue: 0,0:59:52.85,1:00:01.61,Default,,0,0,0,,这样大部分1020个数据都是矢量化的家,而剩下的三个数据才是标量加
Dialogue: 0,1:00:01.61,1:00:11.83,Default,,0,0,0,,所以说它既高效又不会犯错,这就是边界测,就是编译器做优化的时候,它能够自动去判断
Dialogue: 0,1:00:11.83,1:00:19.18,Default,,0,0,0,,可以看到这里做了什么,做了一个campaign,然后他就会比较嗯是不是是被刷
Dialogue: 0,1:00:19.18,1:00:22.36,Default,,0,0,0,,如果是的话,我就直接全部填入
Dialogue: 0,1:00:22.36,1:00:31.91,Default,,0,0,0,,如果不是的话,我先取出它剩余的那三个元素,然后先把全部填入之后,我再进行一个收尾工作
Dialogue: 0,1:00:31.91,1:00:34.12,Default,,0,0,0,,编译器能自动处理
Dialogue: 0,1:00:34.12,1:00:43.67,Default,,0,0,0,,但如果你要自己手动去写MM什么什么的,SIMD指令的话,你就要手自己去判断这个边界
Dialogue: 0,1:00:43.67,1:00:45.74,Default,,0,0,0,,哦,明白了吧?
Dialogue: 0,1:00:51.38,1:01:02.99,Default,,0,0,0,,然后如果你能够保证N总归是四的倍数,不想让它这么复杂生成两个版本,你可以用这个N先除以4
Dialogue: 0,1:01:02.99,1:01:07.86,Default,,0,0,0,,因为除是整除就让N会落入四的倍数当中
Dialogue: 0,1:01:07.86,1:01:17.59,Default,,0,0,0,,这样的话编译器能够发现N始终是四的倍数,从而它就不会生成边界侧畔的身份之外
Dialogue: 0,1:01:18.08,1:01:34.20,Default,,0,0,0,,是不是很智能编译器甚至能够理解这个术语函数的用途啊,是啊是啊,所以我推荐推荐所有的数组都是四的整数倍
Dialogue: 0,1:01:34.20,1:01:42.74,Default,,0,0,0,,如果可以的话,最好再弄成16的整数倍,这样能够避免边界侧畔的发生
Dialogue: 0,1:01:42.74,1:01:48.73,Default,,0,0,0,,然后再写一下这个告诉编译器,我肯定是四的倍数
Dialogue: 0,1:01:49.10,1:02:10.11,Default,,0,0,0,,然后就是这个这个GCC内置的那个函数,它都是以两个下划线,然后built in 开头的,就是这个就告诉编译器,我保证A是16字节对齐的
Dialogue: 0,1:02:10.46,1:02:30.86,Default,,0,0,0,,然后看看和刚才有啥区别,刚才是MOVUPS,这里面呈MO与NPS,这个就让他让CPU知道啊,这个保证是16字节对齐的,传CPU可能就更快一点,可能不快
Dialogue: 0,1:02:33.35,1:02:37.92,Default,,0,0,0,,但是这样的话就是调用GCC采用的函数
Dialogue: 0,1:02:37.92,1:02:52.13,Default,,0,0,0,,如果在微软编译器上,就不能通过其所谓的通用 C++ 20在memory头里引入这个模板函数,然后通过模板参数来指定对齐到16字节
Dialogue: 0,1:02:52.13,1:02:56.20,Default,,0,0,0,,但是它是20的色,我们目前还用不上
Dialogue: 0,1:03:03.17,1:03:10.74,Default,,0,0,0,,然后是小鹏老师也没看过,就是我是在对A的所有值进行一个加
Dialogue: 0,1:03:10.74,1:03:18.60,Default,,0,0,0,,就是把数组里所有的元素加起来,结果它生成了这么复杂的一串弹幕
Dialogue: 0,1:03:20.75,1:03:34.29,Default,,0,0,0,,总之他就是高校小鹏老师也不知道为什嗯,然后就是讲循环了
Dialogue: 0,1:03:34.29,1:03:45.10,Default,,0,0,0,,因为在那个我们的程序中就是有热和冷这两个术语,他说的是热,就是经常被访问的代码
Dialogue: 0,1:03:45.10,1:03:58.24,Default,,0,0,0,,就比如刚才这一段float right 等于0 ,它只会调用一次,而这个for 循环里面的这个东西被调用了1024次
Dialogue: 0,1:03:59.39,1:04:07.61,Default,,0,0,0,,所以说这个调用次数多的地方称之为热,然后调用数少的地方称之为冷
Dialogue: 0,1:04:07.61,1:04:11.72,Default,,0,0,0,,一般我们优化都是在热的地方优化
Dialogue: 0,1:04:11.72,1:04:24.32,Default,,0,0,0,,因为热的地方通常它执行过很多遍,也就是说更容易成为瓶颈,而往往循环中的那个那个地方都能执行很多遍
Dialogue: 0,1:04:24.32,1:04:32.00,Default,,0,0,0,,所以说优化通常都是针对循环的优化,而循环也往往是优化的重点
Dialogue: 0,1:04:32.38,1:04:54.61,Default,,0,0,0,,所以说这里出现了一个又循环的那个分函数,它的工作就是把B里面的数加1 ,然后复制到A可以看到这发生了什么这发生了什么,就是按我们的理解,这个应该是可以死掉化的吧
Dialogue: 0,1:04:55.42,1:05:00.57,Default,,0,0,0,,但是编译器却生成两个版本,为什么呢?
Dialogue: 0,1:05:00.57,1:05:10.19,Default,,0,0,0,,因为他生怕就比如B等于A加一的话,这样写入A之后,B的值就会被改变
Dialogue: 0,1:05:10.19,1:05:19.90,Default,,0,0,0,,然后这时候他就有可能他就优化之后这个这个运行的结果就会不一样
Dialogue: 0,1:05:19.90,1:05:26.63,Default,,0,0,0,,所以说编译器就不敢优化,但是他又想要追求性能怎么办呢?
Dialogue: 0,1:05:26.63,1:05:47.38,Default,,0,0,0,,他离天下之大普,他竟然对指针进行了一个判断,就是判断A加1024是否小于B或者B加1024小于A如果这两个小于了,也就是不重合,它才会去调用SIMD这个高效的版本
Dialogue: 0,1:05:47.38,1:05:54.38,Default,,0,0,0,,如果如果我们这个出现了重合,它就会跳转到这个低效的版本
Dialogue: 0,1:05:54.38,1:06:03.49,Default,,0,0,0,,但是它能够保证不出错,嗯,是不是很离谱,就是因为他怕这两个指针是有重合的
Dialogue: 0,1:06:03.49,1:06:12.74,Default,,0,0,0,,虽然我们知道它没有重合,是不是这合理吗?
Dialogue: 0,1:06:12.74,1:06:15.38,Default,,0,0,0,,然后就有这个表情包
Dialogue: 0,1:06:15.38,1:06:27.40,Default,,0,0,0,,所以说我们为了让编译器放心,可以加上正的float 乘restrict ,就告诉别人你这两个数组保证不会重合
Dialogue: 0,1:06:27.40,1:06:37.07,Default,,0,0,0,,这样的话它就只生成了一个CM底板,而不需要在运行时判断是不是有重合了
Dialogue: 0,1:06:37.07,1:06:48.24,Default,,0,0,0,,这样的话它效率可能就能够提升,因为不需要分支嘛,这才叫很合理的解决方案
Dialogue: 0,1:06:49.77,1:06:54.82,Default,,0,0,0,,多一个判断,但是生成的代码不是变多了吗?
Dialogue: 0,1:06:54.82,1:07:05.48,Default,,0,0,0,,就如果你这不是1024 ,而是比如64的话,那这个多一个分支就有可能就能够成为瓶颈了
Dialogue: 0,1:07:05.48,1:07:14.53,Default,,0,0,0,,总之就是还有一种情况,就是如果我这里不止A和BSABCDEFG有很多个
Dialogue: 0,1:07:14.53,1:07:18.52,Default,,0,0,0,,那这从编译器就是每个都判断一遍
Dialogue: 0,1:07:18.52,1:07:31.84,Default,,0,0,0,,而由于刚刚说的编译器不会优化过于复杂的代码,他会认为把ABCDEFG每一个去俾一遍,是一个非常复杂的操作
Dialogue: 0,1:07:31.84,1:07:41.68,Default,,0,0,0,,所以他甚至有可能放弃这个去进行判断,然后直接生成一个标量版给你,知道吧?
Dialogue: 0,1:07:42.85,1:07:53.08,Default,,0,0,0,,所以说加上一个restrict 就告诉他,哎呀,我保证他们不会重复,然后他就能生成矢量的代码了
Dialogue: 0,1:07:55.94,1:08:04.56,Default,,0,0,0,,然后除了可以用这个GCC特有的原始jacket ,也可以使用比较跨平台的open MP
Dialogue: 0,1:08:05.16,1:08:07.36,Default,,0,0,0,,oppo MP是什么呢?
Dialogue: 0,1:08:07.36,1:08:22.27,Default,,0,0,0,,它是高性能计算的一个框架,它可以通过杠f open MP来打开,然后使用起来只需要用pro 罐码语句,然后OMPCMD就行了
Dialogue: 0,1:08:22.27,1:08:46.76,Default,,0,0,0,,他就告诉你,哎呀,下面这个有可能会出现那个数据依赖,但是不要担心,我放心你你一定能把它C五级优化的这样编译器就知道你都显示说的CMD啊,那我应该能够默认你这两个指针不会打架了,从而它也是只会生成一份代码
Dialogue: 0,1:08:46.76,1:09:00.67,Default,,0,0,0,,然后除了可以这两种之外,如果你只用GCC的话,还可以用这个IDdeep 这个program 嘛
Dialogue: 0,1:09:00.67,1:09:03.66,Default,,0,0,0,,然后它是什么的简称呢?
Dialogue: 0,1:09:03.66,1:09:08.74,Default,,0,0,0,,ignore victor dependency 就是忽略矢量依赖关系
Dialogue: 0,1:09:09.30,1:09:19.74,Default,,0,0,0,,就是说他可以嗯就是忽略这个B可能会依赖于A的情况,然后这个是GCC特有的
Dialogue: 0,1:09:19.74,1:09:25.75,Default,,0,0,0,,不过其他编译器这个pro 挂码可能就不一样,你可以自己去查
Dialogue: 0,1:09:25.75,1:09:35.11,Default,,0,0,0,,通过官网IVDEP在MSVC是怎么样自己去查,但这个就是比较跨平台,比较通用的一个
Dialogue: 0,1:09:35.11,1:09:38.89,Default,,0,0,0,,但是他得开一个选项,没办法
Dialogue: 0,1:09:41.21,1:09:45.87,Default,,0,0,0,,i mean deep 就是呼市extra dependent
Dialogue: 0,1:09:46.96,1:09:56.68,Default,,0,0,0,,然后还有一个循环的优化,就是这种因为史料的话,他是没办法弄一个F语句的
Dialogue: 0,1:09:56.68,1:10:06.85,Default,,0,0,0,,一旦有F语句矢量化就会很困难,所以说运气会把这F语句挪到外面,具体是怎样呢?
Dialogue: 0,1:10:06.85,1:10:22.25,Default,,0,0,0,,可以看到这个EAX,EAX是不是这个这个参数的寄存器,然后他会先判断就是这个是不是为那个为空啊,就是是不是为帧啊
Dialogue: 0,1:10:22.25,1:10:25.74,Default,,0,0,0,,如果为真的话,就跳到了12
Dialogue: 0,1:10:26.67,1:10:42.99,Default,,0,0,0,,可以看到这里面是一个呃哎呀搞反了,我这里瞧一下他人傻了,应该这样
Dialogue: 0,1:10:52.75,1:10:57.88,Default,,0,0,0,,总之哎呀,哎呀算了,你看得懂就行了
Dialogue: 0,1:10:57.88,1:11:13.12,Default,,0,0,0,,总之呢这上面一个可以看到生成A的PS的一个循环,然后下面生成一个MPS,也就是乘法,它会去提前判断,也就是它相当于进行了这么一个优化
Dialogue: 0,1:11:13.12,1:11:25.44,Default,,0,0,0,,也就是把F提到外面,然后false 循环也是分成两份放循环,这样就可以里面就没有F语句,从而可以使用化,明白吧?
Dialogue: 0,1:11:28.64,1:11:42.63,Default,,0,0,0,,但是这样我们写起来是更麻烦,所以有时候也可以这样写,只要编译器知道as more 是一个循环之中不会改变的量,它就能够这样优化
Dialogue: 0,1:11:42.63,1:11:49.73,Default,,0,0,0,,就是你们知道有没有同学用太极啊,就是这个东西有人用吗?
Dialogue: 0,1:11:49.73,1:12:01.71,Default,,0,0,0,,如果你用这个东西的话,你也可以把F提到外面,然后外面这里弄一个F点TI点statement ,这样也能够变快,知道吧?
Dialogue: 0,1:12:04.03,1:12:09.41,Default,,0,0,0,,这样就可以用CM级优化了,因为没有F指令
Dialogue: 0,1:12:09.99,1:12:17.16,Default,,0,0,0,,然后除了这种可以挪到外面来的F还有可以挪到外面来的表达式
Dialogue: 0,1:12:17.49,1:12:29.57,Default,,0,0,0,,比如这里优化之前是长这样的,优化之后他发现哎这个DT3TD这DT不是随着循环不会改变的吗?
Dialogue: 0,1:12:30.43,1:12:41.36,Default,,0,0,0,,那那如果我这样,就是先把DT上DT计算好之后,就直接乘就可以了,就不需要再重新计算1024遍呀
Dialogue: 0,1:12:41.36,1:12:47.83,Default,,0,0,0,,所以我们可以看到这里,他先是进行了一个DT乘DT的运算
Dialogue: 0,1:12:48.16,1:13:00.22,Default,,0,0,0,,然后到这里他就直接应用这个这个乘号的叉MM0了,就可以节省一次1024次乘法运算
Dialogue: 0,1:13:00.22,1:13:08.26,Default,,0,0,0,,这种思想就叫什么,会把热的那个代码尽量移到冷的代码里去
Dialogue: 0,1:13:08.84,1:13:16.97,Default,,0,0,0,,也就是说在不是这看起来好像是一个城从一个地方移到另外一个地方
Dialogue: 0,1:13:16.97,1:13:26.44,Default,,0,0,0,,其实它是把1024之城移到一个只会城一次的地方,从而它就能够更快,明白吧?
Dialogue: 0,1:13:33.76,1:13:38.37,Default,,0,0,0,,然后就是你这样其实是有需要加一个括号的
Dialogue: 0,1:13:38.37,1:13:41.53,Default,,0,0,0,,如果你把括号去掉,它会怎么办?
Dialogue: 0,1:13:41.53,1:13:51.00,Default,,0,0,0,,它会因为乘法不是左结合性嘛,所以它会认为是先BI乘以DT,然后乘出来结果再去乘DT
Dialogue: 0,1:13:51.00,1:14:00.47,Default,,0,0,0,,从而他发现不了DT乘以D是一个不变量,然后他就硬生生成了两遍,所以编译器是有点蠢的
Dialogue: 0,1:14:00.47,1:14:04.60,Default,,0,0,0,,他明明加一个括号是一样的,他发现不了
Dialogue: 0,1:14:05.23,1:14:13.27,Default,,0,0,0,,所以说我们就最好帮编译器像刚才那样打上一个括号,告诉他这里面全是不变量
Dialogue: 0,1:14:13.27,1:14:18.56,Default,,0,0,0,,而这样的话,他因为乘法结合率的原因就不会识别出来
Dialogue: 0,1:14:19.49,1:14:25.88,Default,,0,0,0,,然后还有一个会导致CMD优化失败的例子,就是调用外部函数
Dialogue: 0,1:14:25.88,1:14:30.15,Default,,0,0,0,,刚刚也说外部函数是优化失败的罪魁祸首
Dialogue: 0,1:14:30.15,1:14:36.78,Default,,0,0,0,,比如这里我弄一个for 循环,本来这只有这个的话是可以优化成功的
Dialogue: 0,1:14:36.78,1:15:02.75,Default,,0,0,0,,而你调用了一个声明的其他文件的函数,那他没办法矢量化矢量化,他得调用视频,又不知道这个二的是不是会改变A他不知道呀,所以说编译器看不到二的,你干了啥,他只好求他只好求我说,哎呀,那我不优化了,我就一个个去调用你吧,知道吧?
Dialogue: 0,1:15:02.75,1:15:20.43,Default,,0,0,0,,所以说尽可能的把二的放在同一个文件里定义,这样编译器就能够看到啊,二的你啥也没算,我直接把二的调去掉,然后再剩下的一个reduction 的代码,它就能够优化了,对吧?
Dialogue: 0,1:15:22.43,1:15:24.38,Default,,0,0,0,,看全部都是PS
Dialogue: 0,1:15:24.38,1:15:30.53,Default,,0,0,0,,我刚刚说过,全是SS说明他优化失败了,都是标量的
Dialogue: 0,1:15:30.53,1:15:34.72,Default,,0,0,0,,如果全是PS的话,就说明优化成功
Dialogue: 0,1:15:34.72,1:15:45.71,Default,,0,0,0,,他用了那个patent ,也就是矢量的代码,对编译器看得到A的就可以内灵了
Dialogue: 0,1:15:45.71,1:15:49.17,Default,,0,0,0,,不需要声明一个音量哦,不需要
Dialogue: 0,1:15:50.84,1:15:56.57,Default,,0,0,0,,所以说在热的for 循环里,尽量不要调用外部函数
Dialogue: 0,1:15:56.57,1:16:05.72,Default,,0,0,0,,我说这呢冷的话没关系,像这种钓鱼1024变就属于热的哈,热的循环
Dialogue: 0,1:16:06.40,1:16:10.87,Default,,0,0,0,,有问题吗?
Dialogue: 0,1:16:16.09,1:16:22.87,Default,,0,0,0,,情绪啊,然后刚才说到这种调用函数会影响CMD
Dialogue: 0,1:16:22.87,1:16:30.34,Default,,0,0,0,,然后像这种随机访问也会影响CMD,就是为什么会失败呢?
Dialogue: 0,1:16:30.34,1:16:42.93,Default,,0,0,0,,因为BI每次的值是不确定的,就是他可能是跳跃的在访问,这时候这个编译器就没有相应的指令能够生成这个
Dialogue: 0,1:16:43.48,1:16:50.54,Default,,0,0,0,,因为他是跳跃的房子,还有一种比较恶劣的情况,就是这种I乘2
Dialogue: 0,1:16:50.54,1:17:01.76,Default,,0,0,0,,也就是说这是先访问0 ,再访问2 ,再访问是我们刚刚说的这种跳跃的访问是不利于CMD的
Dialogue: 0,1:17:01.76,1:17:11.53,Default,,0,0,0,,但是编译器三号它智能的很,它用了一大堆shuffle 指令,还是成功的对A进行了那个一定的优化
Dialogue: 0,1:17:11.88,1:17:16.67,Default,,0,0,0,,所以他这是一个部分成功了,但是比较艰难
Dialogue: 0,1:17:16.67,1:17:19.59,Default,,0,0,0,,所以最好的情况是什么呢?
Dialogue: 0,1:17:19.59,1:17:30.77,Default,,0,0,0,,最好情况就直接AI也就是顺序访问这种情况下,编译器能够只用一个指令就进行一个读写就行了
Dialogue: 0,1:17:30.77,1:17:37.16,Default,,0,0,0,,不需要他再弄那么多,需要否指令来那个读取跳跃的数据
Dialogue: 0,1:17:37.16,1:17:46.72,Default,,0,0,0,,所以说对编译器和CPU来说顺序,而且连续的访问才是最理想的,明白吧?
Dialogue: 0,1:17:47.68,1:17:49.44,Default,,0,0,0,,喝口水哦
Dialogue: 0,1:17:59.57,1:18:11.91,Default,,0,0,0,,所以说顺序和连续,首先顺序就是不能弄一个像一个B下标或者是函数返回值这种这种就影响它的优化
Dialogue: 0,1:18:16.28,1:18:21.61,Default,,0,0,0,,然后还有一个优化方式就是循环展开
Dialogue: 0,1:18:21.61,1:18:30.68,Default,,0,0,0,,像这种我们的循环体非常的简单,就是从I到1024这样出现一个什么问题
Dialogue: 0,1:18:30.68,1:18:49.91,Default,,0,0,0,,你看生成的代码一行是move UPS,然后还有一行是A的Q然后是compare q 就是说我实际执行计算的指令只有一个,而进行比较和这个I的加的指令却用了两个,还有一个跳转指令
Dialogue: 0,1:18:49.91,1:19:00.83,Default,,0,0,0,,这样的话就导致一部分的时间,大部分的时间都花在这个跳转和加上,而不是用在这个实际的计算上
Dialogue: 0,1:19:02.37,1:19:05.85,Default,,0,0,0,,这时候我们就可以用啊肉展开
Dialogue: 0,1:19:05.85,1:19:20.04,Default,,0,0,0,,比如这里就是把四个循环体放在一个循环里,然后这里改成I加等于4 ,这样的话它就有四个目录指令和3个不是目录的指令
Dialogue: 0,1:19:20.04,1:19:31.30,Default,,0,0,0,,这样的话就是4.3米船,我实际执行计算的时间就更多了,从而就可以避免这个反复比较的这个overhead
Dialogue: 0,1:19:37.27,1:19:40.23,Default,,0,0,0,,对的,射手最好是连续的
Dialogue: 0,1:19:45.25,1:19:50.73,Default,,0,0,0,,所以吸收矩阵也用四组成就,可以吗?
Dialogue: 0,1:19:51.87,1:19:57.29,Default,,0,0,0,,然后我们这里有一个GCC指令可以实现循环展开
Dialogue: 0,1:19:57.29,1:20:06.41,Default,,0,0,0,,当然这个是GCC测定的,而且我目前也没找到什么通用的替代,你得自己去用葡萄瓜嘛
Dialogue: 0,1:20:06.41,1:20:09.86,Default,,0,0,0,,GCCL4后面这个数字可以改
Dialogue: 0,1:20:09.86,1:20:13.56,Default,,0,0,0,,4 ,就代表把四个循环体变成了一个
Dialogue: 0,1:20:13.56,1:20:29.09,Default,,0,0,0,,这里看到有四个MOB用PS,你可以根据实际的情况决定要用多少就是小的循环器,一般都会去unknown 一下,但太大的循环体就不要去unknown
Dialogue: 0,1:20:29.09,1:20:39.79,Default,,0,0,0,,因为它会导致生成代码变大,这样对指令的解码就会造成一定的压力,从而可能可能反而就会变慢
Dialogue: 0,1:20:39.79,1:20:56.85,Default,,0,0,0,,说不对呀,这个你得自己去测,就是你测,比如你调到四变快,调到8变快了,然后调到16又变慢,那你就知道八是最好的情况,知道吗?
Dialogue: 0,1:20:58.76,1:21:05.77,Default,,0,0,0,,就是不建议你手动去把它写四遍,建议多用普罗旺的语句去进行操作
Dialogue: 0,1:21:05.77,1:21:16.91,Default,,0,0,0,,而且用普通话呢还有一个好处,就是如果你这是1023的,它也能够自动它也能够自动生成边界特判的代码
Dialogue: 0,1:21:19.51,1:21:28.18,Default,,0,0,0,,然后就是这张的重点结构体,这是很多同学都会错误的这样去写
Dialogue: 0,1:21:28.18,1:21:39.37,Default,,0,0,0,,就是比如我们这里有一个my bank ,它里面有XY2个,然后我们这里做了什么事呢?
Dialogue: 0,1:21:39.37,1:21:50.12,Default,,0,0,0,,就是AX乘等于Y,也就是它会使用到X和Y这两个成语,然后可以看到生成非常复杂的代码
Dialogue: 0,1:21:50.12,1:21:56.74,Default,,0,0,0,,然后里面有shuffle 身份的,但他最终还是用到了MULPS
Dialogue: 0,1:21:56.74,1:22:00.33,Default,,0,0,0,,也就是说他的确的确是成功了
Dialogue: 0,1:22:01.91,1:22:16.05,Default,,0,0,0,,但是如果我们突然就是想把这个改成三维的了,你就凭空加了一个C然后我们的fan 甚至没有任何改变,他这里就全部变成SS
Dialogue: 0,1:22:16.05,1:22:20.25,Default,,0,0,0,,也就是说时间化失败了,这是啥原因呢?
Dialogue: 0,1:22:25.51,1:22:38.61,Default,,0,0,0,,这是因为我们刚才之所以能够使量化,因为他把两个my wake 给读出来,然后读到一个SIMD的128位寄存器里了
Dialogue: 0,1:22:38.61,1:22:50.67,Default,,0,0,0,,而如果你这样为三个的话,他如果要读到一个CMD里,他这里就是XYZX,然后剩余一个Y是没办法读出来的
Dialogue: 0,1:22:50.67,1:23:00.13,Default,,0,0,0,,所以说这时候编辑就尴尬了,他没办法生成分成一个CMD的图,所以说要怎么办呢?
Dialogue: 0,1:23:00.13,1:23:09.52,Default,,0,0,0,,就是我们添加一个尾单,就是你看这里我根本没用Z我只是加了一个Z它就一下子变低效
Dialogue: 0,1:23:09.52,1:23:21.70,Default,,0,0,0,,所以如果我们再要让它再变回高效,甚至可以这样直接加一个恰padding 4 ,这是一个没有任何用处的那个空的变量
Dialogue: 0,1:23:21.70,1:23:36.23,Default,,0,0,0,,然后这时他又能够优化成功了,就是可以发现,就是如果你的strict 是二的整数幂大小,这时候是有利于CMD优化的
Dialogue: 0,1:23:37.19,1:23:47.02,Default,,0,0,0,,而如果你像刚才那样是三个一组,这时候他就没有办法对,起床就三五级是优化失败
Dialogue: 0,1:23:47.88,1:23:55.77,Default,,0,0,0,,就是说你也知道计算机是一个二进制的东西,它最喜欢的就是二的整数幂
Dialogue: 0,1:23:55.77,1:24:04.91,Default,,0,0,0,,如果你的速度大小都是二的整数,幂结构体大小也是二的整数幂,这时候往往是比较高效
Dialogue: 0,1:24:05.26,1:24:12.67,Default,,0,0,0,,可以看到这里是生成这个PS的代码,从而使运行的矢量化
Dialogue: 0,1:24:12.67,1:24:20.97,Default,,0,0,0,,然后除了刚才这种很困扰的这种直接加一个从来用不到的变量
Dialogue: 0,1:24:20.97,1:24:38.68,Default,,0,0,0,, C++ 11还新增了,然后就哦那就是也就是对齐到16字节,这样my wake 的起始地址,它会对齐到16 ,而且它的大小也会变成16的整数倍
Dialogue: 0,1:24:38.68,1:24:47.01,Default,,0,0,0,,这时候生成的代码其实是一样的,但是又不需要手动加一个叉padding
Dialogue: 0,1:24:47.01,1:24:50.51,Default,,0,0,0,,明白了吧?
Dialogue: 0,1:24:51.73,1:25:04.91,Default,,0,0,0,,那是不是说就是哎呀小鹏老师我学会了,我这就回去把我的程序每一个时间很多加上alleged ,是不是我程序就能变快呢?
Dialogue: 0,1:25:04.91,1:25:08.41,Default,,0,0,0,,不一定哦,甚至是有可能会变慢
Dialogue: 0,1:25:08.89,1:25:18.59,Default,,0,0,0,,因为的确对齐到16之间也能够帮助CMDU款,但它只是其中一个点,又不是全部的点
Dialogue: 0,1:25:18.59,1:25:31.72,Default,,0,0,0,,这个结构体变大,还有其他的缺点,就比如会霸占着你的那个缓存,然后就导致它更容易变成memory bound ,从而有可能还会变慢
Dialogue: 0,1:25:31.72,1:25:39.85,Default,,0,0,0,,所以说你不能就是把这个当成万能的万精油它不行的,你得考虑实际情况
Dialogue: 0,1:25:39.85,1:25:45.63,Default,,0,0,0,,实际测了以后,如果加了的确变化了,那才去加嘛,对吧?
Dialogue: 0,1:25:46.72,1:26:09.97,Default,,0,0,0,,然后就是其实这个案例最好的办法应该是用这个用来说刚才说到的这个嗯就是这个结构是不是XYZpadding ,其实就是这样的,就是先XYZ,然后这里空一个pending ,然后再XYZ再空一个pending
Dialogue: 0,1:26:09.97,1:26:18.78,Default,,0,0,0,,这种结构类型叫every office johnny ,它是指的是XYZ单个对象的属性紧紧凑在一起
Dialogue: 0,1:26:18.78,1:26:20.60,Default,,0,0,0,,什么叫单个对象?
Dialogue: 0,1:26:20.60,1:26:41.86,Default,,0,0,0,,也就是我一个my wake 的XYZ是紧凑的,而与之相对的就是SOSstrict of every 它会把一个对象拆成三个数组,就是一个对象的X在一个数字里,一个对象的Y又在另一个数字里直接用在另一个数字里
Dialogue: 0,1:26:41.86,1:26:47.56,Default,,0,0,0,,这样的好处就是他不需要去考虑padding 的,这是肯定的
Dialogue: 0,1:26:47.56,1:26:57.88,Default,,0,0,0,,然后他而且因为这样,如果你只访问了x no z 或者根本没有用到,那这时候就相当于只用到了XY
Dialogue: 0,1:26:57.88,1:27:04.66,Default,,0,0,0,,而如果你用AOS的话,Z和penny 都相当于没有用的那个那个属性
Dialogue: 0,1:27:04.66,1:27:13.76,Default,,0,0,0,,所以如果你刚才只用XY那CPU看到你就是在不停的跳着两个图传,效率是更低的
Dialogue: 0,1:27:13.76,1:27:25.65,Default,,0,0,0,,而如果你用AOS,你只访问XY没用Z那CPU看到啊你是在访问两个数组啊,从而它它能够用SIMD优化
Dialogue: 0,1:27:26.60,1:27:36.27,Default,,0,0,0,,所以说SOA虽然它好像不符合我们面向对象的思想,但它其实是对CPU更友好的
Dialogue: 0,1:27:37.23,1:27:39.33,Default,,0,0,0,,就来看看怎么做吧
Dialogue: 0,1:27:39.33,1:27:50.59,Default,,0,0,0,,首先是刚才说的比较烂的AOS,我们用float XYZ这样生成的就是一个不能SIMD的代码
Dialogue: 0,1:27:50.96,1:27:55.74,Default,,0,0,0,,然后要怎么变成SOA呢?
Dialogue: 0,1:27:56.07,1:28:01.86,Default,,0,0,0,,就是哎你看这种是不是很符合面向对象的想法
Dialogue: 0,1:28:01.86,1:28:10.36,Default,,0,0,0,,就my bank 是一个对象,然后my bank 包含XYZ,我有1024个mybank 其实这样不太好
Dialogue: 0,1:28:10.36,1:28:14.24,Default,,0,0,0,,就是对性能来说,我们会把它变成这样
Dialogue: 0,1:28:14.24,1:28:25.17,Default,,0,0,0,,就刚才是A括号1024 ,我现在A1024不要了,把它移到my wake 里,就是X有1024个Y有1024个
Dialogue: 0,1:28:25.17,1:28:30.99,Default,,0,0,0,,然后访问的时候本来是第二个A的X现在变成A的X的
Dialogue: 0,1:28:30.99,1:28:34.63,Default,,0,0,0,,第二个就是这个括号换了一下位置
Dialogue: 0,1:28:34.63,1:28:40.22,Default,,0,0,0,,本来是A有1024的,现在每个属性都有1024的
Dialogue: 0,1:28:40.22,1:28:47.26,Default,,0,0,0,,他们分离开来的存储可以看到这里就用到了PS传矢量化是成功的
Dialogue: 0,1:28:48.78,1:29:13.46,Default,,0,0,0,,对的对的,这样虽然就是说哎这个不符合面向对象的思想呀,但是他其实更高效,所以就分到分出2派人,一种是组装抽象的面向对象编程,还有一种是面向数据编程,就是data oriented programming
Dialogue: 0,1:29:13.46,1:29:17.05,Default,,0,0,0,,这种在高性能计算中比较常见
Dialogue: 0,1:29:17.05,1:29:28.11,Default,,0,0,0,,而面向对象呢他们的工作就是java 的工作,就是每天打开一个数据库,然后写入学生信息,然后关闭
Dialogue: 0,1:29:28.11,1:29:37.24,Default,,0,0,0,,而如果我们是DOP的话,就会把一个学生的每一个属性存储到很多个数字里
Dialogue: 0,1:29:37.24,1:29:53.92,Default,,0,0,0,,这样的话如果我只用学生的姓名信息,我只要访问这个姓名数字就可以了,就不会再去读一遍他的身份证号啊、学号啊什么都读一遍,这样不是浪费了内存嘛,对吧?
Dialogue: 0,1:29:54.25,1:29:58.00,Default,,0,0,0,,是不是这样,其实是对性能更好的呀
Dialogue: 0,1:29:59.49,1:30:06.20,Default,,0,0,0,,然后就是有时候面向对象的人又说,哎呀,你这样太不利于优化了
Dialogue: 0,1:30:06.20,1:30:11.92,Default,,0,0,0,,如果我们想要把mac 存在一个map 里,那你这样就不行了呀
Dialogue: 0,1:30:12.68,1:30:26.20,Default,,0,0,0,,所以说推出了一种中间解决方案,就四个对象打包乘以SOA,然后再用N除4的大小的速度打包成一个AOS
Dialogue: 0,1:30:26.20,1:30:31.19,Default,,0,0,0,,这样既有AOS的直观,又有SOA的高效
Dialogue: 0,1:30:31.19,1:30:37.42,Default,,0,0,0,,这里其实就相当于把四个变成一个M128了
Dialogue: 0,1:30:37.42,1:30:43.48,Default,,0,0,0,,但是这种其实也不常用,最常用的还是这个SOA
Dialogue: 0,1:30:43.81,1:30:47.48,Default,,0,0,0,,然后这个东西也能够优化成功
Dialogue: 0,1:30:47.48,1:30:51.99,Default,,0,0,0,,然后他还是我们王新伟同学的最爱哦
Dialogue: 0,1:30:51.99,1:31:02.91,Default,,0,0,0,,所以说嗯但是他的缺点就是刚才只需要一个for 循环,都是只需要一个的,这里却需要两个
Dialogue: 0,1:31:02.91,1:31:12.27,Default,,0,0,0,,因为它这里面还有两个下标去访问很麻烦,而且这对随机访问也非常不由
Dialogue: 0,1:31:13.40,1:31:21.82,Default,,0,0,0,,所以我们测试一下,就是刚才这个就比如这个程序吧,它有XYZ3个属性
Dialogue: 0,1:31:21.82,1:31:39.36,Default,,0,0,0,,然后我们一个计算的过程是把它的XYZ加起来写入到X然后首先第一步优化就是这个是AOS,我们把它优化成SOA,也就是把N移上去
Dialogue: 0,1:31:39.36,1:31:48.19,Default,,0,0,0,,然后这样然后再再加上我们每个编译器特有的on law 语句,就是GCC是GCC
Dialogue: 0,1:31:48.19,1:31:50.33,Default,,0,0,0,,ROC呢也是这个
Dialogue: 0,1:31:50.33,1:32:00.23,Default,,0,0,0,,然后MSC它就是微软的那个它比较霸气,它直接占据根的那个,它就不需要再进行一个
Dialogue: 0,1:32:00.23,1:32:18.66,Default,,0,0,0,,所以说我们对每个编译器,然后把它进行一个unknown ,然后我们这次测试结果,首先这是未经优化之前需要811620 ,那好有吗?
Dialogue: 0,1:32:19.25,1:32:22.12,Default,,0,0,0,,对,mesh 是有这种优化的
Dialogue: 0,1:32:22.12,1:32:28.88,Default,,0,0,0,,我们ZNO里的mesh 也是这样优化,这是原来的那个效率
Dialogue: 0,1:32:28.88,1:32:34.98,Default,,0,0,0,,然后这个是用了16字节对齐以后的项目,它反而变慢了
Dialogue: 0,1:32:34.98,1:32:44.38,Default,,0,0,0,,然后这是如果我把原来的那个代码一点不变,只是加上一个parallel phone ,它能够变快到这样
Dialogue: 0,1:32:44.38,1:32:49.21,Default,,0,0,0,,而如果我们用SOA优化的话,能够变成这样
Dialogue: 0,1:32:49.21,1:32:57.59,Default,,0,0,0,,然后SOA再加上这个program ,OMPCMD让他忽略那个指针别名能变成这样
Dialogue: 0,1:32:57.59,1:33:03.69,Default,,0,0,0,,然后这是我们用了size 7作为它的下标以后又变快了一点
Dialogue: 0,1:33:03.69,1:33:15.46,Default,,0,0,0,,然后最快的就是我们用的是二十7之后再去on ,though , 可以看到它甚至比我们原来直接加一个的量还要快
Dialogue: 0,1:33:15.46,1:33:17.29,Default,,0,0,0,,这说明了什么呢?
Dialogue: 0,1:33:17.29,1:33:21.23,Default,,0,0,0,,就是说明panel for 是给无脑的人用的
Dialogue: 0,1:33:21.23,1:33:28.84,Default,,0,0,0,,他用了以后一寸能变快,但是他的加速比永远不会超过CPU的个数
Dialogue: 0,1:33:28.84,1:33:39.34,Default,,0,0,0,,而如果我们认真的去优化一下它的内存布局,从而它可以CMD优化成功,使它的缓存更加高效
Dialogue: 0,1:33:39.34,1:33:43.94,Default,,0,0,0,,它甚至能够吊打你的病情,放对吧,对吧?
Dialogue: 0,1:33:43.94,1:33:53.66,Default,,0,0,0,,然后更好是就是如果我这优化以后再上个变形,这样就比原来还要快,比谁都要快了
Dialogue: 0,1:33:53.66,1:34:03.02,Default,,0,0,0,,然后最傻的是AOSOA它虽然快一点,但它其实还不如SON呢,对吧?
Dialogue: 0,1:34:03.86,1:34:07.93,Default,,0,0,0,,唉,生存计划是那个游戏吗?
Dialogue: 0,1:34:09.67,1:34:12.31,Default,,0,0,0,,他是 C++ 的游戏吗?
Dialogue: 0,1:34:12.31,1:34:20.89,Default,,0,0,0,,里面这样优化,然后这是我们的那个可以看到这一条绿线是无脑并行
Dialogue: 0,1:34:20.89,1:34:32.65,Default,,0,0,0,,然后这条粉线是我们深度优化,可以看到深度优化之后,它在低数据量的时候甚至超过了这个无脑变形
Dialogue: 0,1:34:32.65,1:34:37.85,Default,,0,0,0,,因为变形它需要创建很多线程,这也需要时间
Dialogue: 0,1:34:37.85,1:34:41.68,Default,,0,0,0,,所以对于小数据我们甚至超过了
Dialogue: 0,1:34:43.11,1:34:46.94,Default,,0,0,0,,所以这个按你最高效的是SOA格式
Dialogue: 0,1:34:47.42,1:34:53.66,Default,,0,0,0,,当然不一定所有的案例都可以用SOA就能优化,你得看情况
Dialogue: 0,1:34:54.03,1:35:04.36,Default,,0,0,0,,然后就是就是我们刚才说的都是基于C语言的指针,这个太老了,我们 C++ 不是喜欢II思想吗?
Dialogue: 0,1:35:04.36,1:35:09.66,Default,,0,0,0,,那就得用STO的victor 容器,然后出现了什么问题
Dialogue: 0,1:35:09.66,1:35:21.51,Default,,0,0,0,,就首先那个经典案例就是第一章说到指针边民问题,它不能够保证C和B是不是同一个变成,从而它得生成两遍
Dialogue: 0,1:35:21.51,1:35:25.30,Default,,0,0,0,,然后第一个语句就不能够被优化掉
Dialogue: 0,1:35:25.71,1:35:34.24,Default,,0,0,0,,那么我想哎那我把这个引用声明为呢,是最后的行不行?
Dialogue: 0,1:35:34.24,1:35:52.42,Default,,0,0,0,,没有任何效果,你看没有任何效果,就是你这个victor 里的指针,它是没有上restrict ,你只是把vector 本身做了一个restrict b 其实不知道C和A它是不是同一个地址的
Dialogue: 0,1:35:52.42,1:36:08.03,Default,,0,0,0,,所以说结论就是嗯要么就是用OMPCM里去让他忽视这个那个可能存在的那个指针别名,或者用GCCIV电话也可以
Dialogue: 0,1:36:08.03,1:36:23.01,Default,,0,0,0,,所以 C++ 的缺点就体现在这里,它的自由度太高,它甚至不能指正A和B里面的data 是不是指向同一个对象,a rest 它就能够从语法层面定制
Dialogue: 0,1:36:23.01,1:36:28.69,Default,,0,0,0,,就如果你同时对一个对象取了可变引用,它就会报错
Dialogue: 0,1:36:28.69,1:36:34.89,Default,,0,0,0,,所以说在rust 编写这种代码是第一个肯定是会被优化掉的
Dialogue: 0,1:36:34.89,1:36:36.95,Default,,0,0,0,,但 C++ 就不可以
Dialogue: 0,1:36:36.95,1:36:41.34,Default,,0,0,0,,那为什么 C++ 标准会就不禁止一下呢?
Dialogue: 0,1:36:41.34,1:36:52.31,Default,,0,0,0,,因为它一旦禁止就会导致不兼容,而导致不兼容以后就导致他没有所有的历史遗产,那就和rest 一样了
Dialogue: 0,1:36:52.31,1:36:54.85,Default,,0,0,0,,那十几家家就没有任何优势
Dialogue: 0,1:36:55.18,1:36:59.47,Default,,0,0,0,,所以说 C++ 还是得抱着他的遗产
Dialogue: 0,1:36:59.47,1:37:06.64,Default,,0,0,0,,然后我们在到时候加上一些提示来帮助编译器优化,知道吧?
Dialogue: 0,1:37:07.86,1:37:14.73,Default,,0,0,0,,送啊,对这两个结合
Dialogue: 0,1:37:17.15,1:37:25.72,Default,,0,0,0,,总之然后就是刚才不是说SOA比较好嘛,但是如果我用了victor 怎么办呢?
Dialogue: 0,1:37:25.72,1:37:39.10,Default,,0,0,0,,也可以你可以像这样把victor 移上,不过你得保证XYZ当中一个被push 的时候,三个都被push ,否则的话这里就会出错
Dialogue: 0,1:37:39.10,1:37:45.18,Default,,0,0,0,,然后这里访问A的大小的时候,也会变成A的X的3
Dialogue: 0,1:37:45.68,1:37:57.40,Default,,0,0,0,,所以说就是所以这个是要维护三个内存,也是有一点开胶的,明白吗?
Dialogue: 0,1:37:58.27,1:38:06.15,Default,,0,0,0,,他是unity 啊,那应该是C项配啊,总会涉及到 C++ 油化
Dialogue: 0,1:38:06.48,1:38:24.99,Default,,0,0,0,,然后就是第八章数学运算,就是看这个例子他做了什么,我们这里是接受于A参数,然后变成A除2 ,返回A除2 ,结果它就生成了一个乘,呃,这个是什么呢?
Dialogue: 0,1:38:24.99,1:38:33.75,Default,,0,0,0,,这个其实是浮点数的0.5的意思,也就是说他把一个除法优化成了更快的乘法
Dialogue: 0,1:38:33.75,1:38:34.26,Default,,0,0,0,,为啥?
Dialogue: 0,1:38:34.26,1:38:36.84,Default,,0,0,0,,因为乘法比除法更快呀?
Dialogue: 0,1:38:37.48,1:38:45.93,Default,,0,0,0,,然后还有一个虽然除法更快,但这里他却没有把除法提取成一个倒数出来
Dialogue: 0,1:38:45.93,1:38:48.31,Default,,0,0,0,,为什么他不能优化呢?
Dialogue: 0,1:38:48.31,1:38:57.02,Default,,0,0,0,,因为他害怕B40的情况,B40的话这里得变成INF或者说他会报一个错
Dialogue: 0,1:38:57.02,1:39:06.80,Default,,0,0,0,,这时候编译器就不敢优化,所以我们可以手动进行这个优化,就把衣橱必先预先计算出来
Dialogue: 0,1:39:06.80,1:39:22.65,Default,,0,0,0,,然后在这里用乘法就乘以刚才这个倒数和直接除是一样的,但它能够把嗯能够把1024次除法变成1024次乘法加一次除法
Dialogue: 0,1:39:22.65,1:39:27.37,Default,,0,0,0,,这样其实是更快的,就是你要对这个有概念
Dialogue: 0,1:39:28.49,1:39:40.98,Default,,0,0,0,,1024的乘法乘,比如乘法所需的时间是一,然后再加一个除法,所需的时间是2 ,这时候花了1026秒
Dialogue: 0,1:39:40.98,1:39:47.92,Default,,0,0,0,,然后如果我用1024个除法的话,就会花掉2048秒
Dialogue: 0,1:39:47.92,1:39:54.35,Default,,0,0,0,,所以说这个优化虽然总的指令数量增加了,但其实乘法比较快
Dialogue: 0,1:39:54.35,1:39:58.32,Default,,0,0,0,,所以我这个其实是更快的解法,明白吧?
Dialogue: 0,1:40:01.09,1:40:04.53,Default,,0,0,0,,GPU不都是SOA的吗?
Dialogue: 0,1:40:06.40,1:40:26.26,Default,,0,0,0,,我记得GPU它是有一个可以指定的戴森球,是计算组设计,不懂不懂,没有玩过这个游戏,乘法比除法快
Dialogue: 0,1:40:26.26,1:40:42.24,Default,,0,0,0,,然后就是我实在是觉得奇怪,为什么他要放弃优化,所以说这里就有一个解决方案,就是加GCCfast mass 这个这个那个选项
Dialogue: 0,1:40:42.24,1:40:52.52,Default,,0,0,0,,加了fast mass 以后,编译器就能够告诉他啊,对这个数学更加的那个啊更加的大胆
Dialogue: 0,1:40:52.52,1:40:57.56,Default,,0,0,0,,也就是说他会把一个除法优化成很多个乘法
Dialogue: 0,1:40:57.56,1:41:05.52,Default,,0,0,0,,但是这样有一个问题,就是如果你的B为零,它就不能保证是不是还正确
Dialogue: 0,1:41:05.85,1:41:10.36,Default,,0,0,0,,所以这就是这个选项不默认开启的原因
Dialogue: 0,1:41:10.36,1:41:18.87,Default,,0,0,0,,因为开启了以后对NAN和无穷大的处理会和IEEE腐朽的标准不一致
Dialogue: 0,1:41:19.60,1:41:24.75,Default,,0,0,0,,但是他又是一个求稳,然后放弃优化的案例啊
Dialogue: 0,1:41:24.75,1:41:32.85,Default,,0,0,0,,所以说如果你能够保证的话,再加上这个flag 就可以,明白吧?
Dialogue: 0,1:41:34.87,1:41:43.37,Default,,0,0,0,,这个到时候再说,到时候不是有个GPU专题嘛,到时候再讲讲GPU怎么赐
Dialogue: 0,1:41:45.97,1:41:59.81,Default,,0,0,0,,然后还有一个严重的问题,就是数学函数,就是你可能就以为SQRT函数就可以了,但是它是接受double 的
Dialogue: 0,1:41:59.81,1:42:07.14,Default,,0,0,0,,就如果你用float 去调用SQRT的话,它也会返回一个double
Dialogue: 0,1:42:07.52,1:42:11.31,Default,,0,0,0,,而且他计算的精度也是基于double 的
Dialogue: 0,1:42:11.31,1:42:13.59,Default,,0,0,0,,长安是比弗洛长更慢
Dialogue: 0,1:42:14.96,1:42:21.89,Default,,0,0,0,,而如如果你想要调用float 的开根号,其实应该用SQRD
Dialogue: 0,1:42:24.55,1:42:26.72,Default,,0,0,0,,等一下我卡住了
Dialogue: 0,1:42:36.40,1:42:51.02,Default,,0,0,0,,所以说最好的解决方案应该是 C++ 的这个SQRT函数,它承载了两个版本,就能够自动根据我拿什么去调用它就自动返回什么类型
Dialogue: 0,1:42:51.02,1:42:54.10,Default,,0,0,0,,所以推荐不要再用C语言的
Dialogue: 0,1:42:54.10,1:42:59.24,Default,,0,0,0,,这个全局的是靠他了,多用用STD你的这个吧
Dialogue: 0,1:43:00.16,1:43:03.82,Default,,0,0,0,,当然最坑的一个应该说是ABS
Dialogue: 0,1:43:03.82,1:43:10.90,Default,,0,0,0,,如果你用全局命名空间的ABS,你会发现这样一个有趣的现象
Dialogue: 0,1:43:10.90,1:43:14.31,Default,,0,0,0,,ABS1.4等于一,为什么呢?
Dialogue: 0,1:43:14.31,1:43:26.91,Default,,0,0,0,,因为一点4被隐私的转换成了int 因为ABS它是一个针对int 的函数,你要用FABS才能调用double 的那个
Dialogue: 0,1:43:26.91,1:43:34.33,Default,,0,0,0,,而如果你想用float 那个得用FABSF坑人吧,坑人吧
Dialogue: 0,1:43:34.33,1:43:38.97,Default,,0,0,0,,这所以这些函数就是C语言的遗产了
Dialogue: 0,1:43:38.97,1:43:57.30,Default,,0,0,0,,永远不要再用全局变量空间的这几个了,始终用STD前缀,用STDABS它存在introduct ,都能够知道,这个就不会出现这种奇怪的那个现象了
Dialogue: 0,1:43:57.30,1:44:18.64,Default,,0,0,0,,当然power 和善都是power 有一点区别,然后就是还有一个fast mass 的一个优点,就是比如我这里是对A的每一个数求根号,为啥它没有矢量化呢?
Dialogue: 0,1:44:18.64,1:44:22.98,Default,,0,0,0,,因为他生怕A某一个数是负数
Dialogue: 0,1:44:22.98,1:44:30.85,Default,,0,0,0,,那么这个时候就比如A一是负数,那么计算到A一就会出错,然后停止
Dialogue: 0,1:44:30.85,1:44:42.79,Default,,0,0,0,,但是如果你矢量化的话,那就变成A出错了以后,但是A零已经写入了,从而他就不能保证这个写入的顺序
Dialogue: 0,1:44:42.79,1:44:46.87,Default,,0,0,0,,所以他就不敢不敢那个优化,知道吧?
Dialogue: 0,1:44:47.21,1:45:10.42,Default,,0,0,0,,所以说我们开启fast class 以后,你就告诉他啊,不要怕,我能够保证A绝对不会出错,它就能够优化成通过这个嗯,然后还有这些我也不知道是什么的东西,总之他就是可能是用了一个额外的牛顿迭代,让他进入更高
Dialogue: 0,1:45:11.92,1:45:19.80,Default,,0,0,0,,我看不懂,但大受震撼,小鹏老师也看不懂,大家也不需要去看懂
Dialogue: 0,1:45:19.80,1:45:24.05,Default,,0,0,0,,总之他这个发的时候应该是会更快的
Dialogue: 0,1:45:25.65,1:45:35.04,Default,,0,0,0,,然后还有一个问题,就是今天我们的作业就是关于这个有一个嵌套循环,这个很常见吧
Dialogue: 0,1:45:35.04,1:45:40.52,Default,,0,0,0,,在计算剪辑或者是那个矩阵乘法都会用到这个吧
Dialogue: 0,1:45:40.52,1:45:51.50,Default,,0,0,0,,但如果你直接这样显示有问题的,可以看到这里有有可能A和C是重合的,甚至B和C是重合的
Dialogue: 0,1:45:51.50,1:45:55.43,Default,,0,0,0,,随兵器,生怕指针出现别人不敢优化
Dialogue: 0,1:45:55.43,1:46:04.61,Default,,0,0,0,,所以刚才不是还有人说,哎呀,嗯这个指证别人不用怕,我就他不是会生成两个版本吗?
Dialogue: 0,1:46:04.61,1:46:11.16,Default,,0,0,0,,结果他这你看只生成了一个版本,没有生成CMD版本,为啥?
Dialogue: 0,1:46:11.16,1:46:20.34,Default,,0,0,0,,还是因为我说的就是编译器当碰到问题复杂化的时候,他直接睡大觉,他放弃优化了
Dialogue: 0,1:46:20.34,1:46:35.02,Default,,0,0,0,,就是他担心AC和A会重合,又担心B和A会重合,他要连续判断三个之间的排列组合是不是重合,他觉得太复杂,他索性放弃了矢量化
Dialogue: 0,1:46:35.02,1:46:50.88,Default,,0,0,0,,所以说这时候要么我们就给ABC都加上面strong ,要么就可以用这种方式先把C读出来,读到temp ,然后对temp 进行追加,然后读完之后再写回到CI
Dialogue: 0,1:46:50.88,1:46:55.39,Default,,0,0,0,,这样的话呢,也是对那个寄存器更加友好
Dialogue: 0,1:46:55.39,1:47:04.16,Default,,0,0,0,,因为这时候time 相当于在寄存器里就不会重复写入这个全局的那个那个地址吧
Dialogue: 0,1:47:04.16,1:47:14.93,Default,,0,0,0,,这样的话编译器就认为不管有没有人别名,我都是提前读取,就不管C怎么选入A和B都不会变
Dialogue: 0,1:47:14.93,1:47:17.69,Default,,0,0,0,,所以它从而能够优化成功
Dialogue: 0,1:47:21.23,1:47:26.77,Default,,0,0,0,,当然一种更好的解决方法就是把time 初始化为零
Dialogue: 0,1:47:26.77,1:47:38.41,Default,,0,0,0,,看啊这里是先读出了CI,我这里直接初始化为零,然后再把这个temp 加等于到CN,这样不是没区别吗?
Dialogue: 0,1:47:38.41,1:47:45.47,Default,,0,0,0,,有区别,因为刚开始这种加法是在CI已经有值的情况下去加
Dialogue: 0,1:47:45.47,1:47:53.73,Default,,0,0,0,,这样如果CI是一个,比如一千,而A是个0.01的值,这是他们两个的那个指数
Dialogue: 0,1:47:53.73,1:48:04.91,Default,,0,0,0,,就是你知道浮点数的构造,它有一个指数,有一个底数,而指数相差太大的话,它们两个之间相加的精度就会受损
Dialogue: 0,1:48:04.91,1:48:13.17,Default,,0,0,0,,就比如1000加0.000001 ,可能还是一千,就导致加出来的结果可能会不对
Dialogue: 0,1:48:13.17,1:48:22.92,Default,,0,0,0,,所以我们最好是把初始化为零,然后这样再去加这手加出来的话就会更准确
Dialogue: 0,1:48:23.65,1:48:37.19,Default,,0,0,0,,然后再加完之后再一次性加到C这样的话time 就足够大,从而不会出现那个那个指数位太远,差太远,导致精度误差的问题
Dialogue: 0,1:48:37.75,1:48:43.53,Default,,0,0,0,,它是精度上的提升,不是性能上的提升
Dialogue: 0,1:48:43.53,1:48:50.12,Default,,0,0,0,,但我们最好也要做一做,嗯,然后这里是全部这节课的总结
Dialogue: 0,1:48:50.12,1:48:54.87,Default,,0,0,0,,首先函数尽量写在同一个文件里,不需要音量
Dialogue: 0,1:48:54.87,1:49:02.62,Default,,0,0,0,,你写同一个文件就行了,然后使避免放循环里调用,不在同一个文件的函数
Dialogue: 0,1:49:04.19,1:49:10.90,Default,,0,0,0,,然后是只要非cos 的指针尽量去加上restrict ,表明不会出现指针别名
Dialogue: 0,1:49:10.90,1:49:14.88,Default,,0,0,0,,然后SOA往往是比AOS更高效的
Dialogue: 0,1:49:14.88,1:49:24.09,Default,,0,0,0,,然后如果你实在要AOS,可以试试看对齐到16或者64字节,16 10的CMD宽度
Dialogue: 0,1:49:24.09,1:49:34.54,Default,,0,0,0,,64是缓存行宽度,然后把你的代码写的简单点,让别人去看懂,你搞复杂化,他看不懂,他就没法优化
Dialogue: 0,1:49:34.54,1:49:52.45,Default,,0,0,0,,然后就是有时候我们会出现指针别名,这时候可以用OMPCMD强制他用CMD优化,然后循环中的不变量编译器有时候能够优化成功,包括F的判断,有时候能优化成功
Dialogue: 0,1:49:52.45,1:49:57.43,Default,,0,0,0,,如果你不确定,最好还是手动把它挪到外面来吧
Dialogue: 0,1:49:58.45,1:50:05.19,Default,,0,0,0,,然后如果你的循环体很小,可以用unknown 展开,看看有没有性能提升
Dialogue: 0,1:50:05.19,1:50:14.09,Default,,0,0,0,,然后对于GCC编译器可以加上这两个,让它快两倍甚至4倍都有,可明白吗?
Dialogue: 0,1:50:15.49,1:50:35.99,Default,,0,0,0,,为啥加个括号没看懂这个为啥加括号不管了,总之就是这几个手法,这次作业你也会用
Dialogue: 0,1:50:35.99,1:50:40.04,Default,,0,0,0,,然后就是在c mac 里怎么开这些开关
Dialogue: 0,1:50:40.04,1:50:52.48,Default,,0,0,0,,首先是杠O3 ,这个默认的c develop type 是develop ,它会生成一些调试,但是它的优化就会被关掉,然后也可以开启O3
Dialogue: 0,1:50:52.48,1:51:05.13,Default,,0,0,0,,就是因为例子模式,它就相当于O3了,而且在MSVC上也会替换为相应的那个链子,还有FONMP
Dialogue: 0,1:51:05.13,1:51:19.12,Default,,0,0,0,,这因为这个FONMP是只针对GCC的开启方式,微软的可能不一样,所以可以使用这种通用的方式来开启它
Dialogue: 0,1:51:19.12,1:51:29.62,Default,,0,0,0,,它是做成了一个裤子,然后fast mass 和MRGnative ,这个好像是没办法,就是变成一个跨平台的
Dialogue: 0,1:51:29.62,1:51:35.84,Default,,0,0,0,,你只能就针对GCC开这个嗯,感谢你的观看
Dialogue: 0,1:51:35.84,1:51:45.45,Default,,0,0,0,,这个作业还在做啊,我还没提交上去,就是等会儿我会把安卓上好像是出错了
Dialogue: 0,1:51:46.42,1:51:56.82,Default,,0,0,0,,我怎么以为是O3 ,就是我上次试过在安卓上搞open mp,结果出错了,我先停一下