# 26章 C99的限制
這個例子說明了為什么某些情況下FORTRAN的速度比C/C++要快
```
#!cpp
void f1 (int* x, int* y, int* sum, int* product, int* sum_product, int* update_me, size_t s)
{
for (int i=0; i<s; i++)
{
sum[i]=x[i]+y[i];
product[i]=x[i]*y[i];
update_me[i]=i*123; // some dummy value
sum_product[i]=sum[i]+product[i];
};
};
```
這是一個十分簡單的例子,但是有一點需要注意:指向update_me數組的指針也可以指向sum數組,甚至是sum_product數組。但是這不是嚴重的錯誤,對嗎? 編譯器很清楚這一點,所以他在循環體中產生了四個階段: 1.計算下一個sum[i] 2.計算下一個product[i] 3.計算下一個unpdate_me[i] 4.計算下一個sum_product[i],在這個階段,我們需要從已經計算過sum[i]和product[i]的內存中載入數據
最后一個階段可以優化嗎?既然已經計算過的sum[i]和product[i]是不需要再次從內存裝載的(因為我們已經計算過他們了)。但是編譯器不能保證在第三個階段沒有東西被覆蓋掉!這就叫“指針別名”,在這種情況下編譯器無法確定指針指向區域的內存是否已經被改變。
C99標準中的限制給解決這一問題帶來了一線曙光。由設計器傳送給編譯器的函數單元在標記這種關鍵字(restrict)后,它會指向不同的內存區域,并且不 會被混用。 如果要更加準確地描述這種情況,restrict表明了只有指針是可以訪問對象的。這樣的話我們可以通過特定的指針進行工作,并且不會用到其他指針。也就是說一個對象如果被標記為restrict,那么它只能通過一個指針訪問。 我們把每個指向變量的指針標記為restrict關鍵字:
```
#!cpp
void f2 (int* restrict x, int* restrict y, int* restrict sum, int* restrict product, int*
restrict sum_product,
int* restrict update_me, size_t s)
{
for (int i=0; i<s; i++)
{
sum[i]=x[i]+y[i];
product[i]=x[i]*y[i];
update_me[i]=i*123; // some dummy value
sum_product[i]=sum[i]+product[i];
};
};
```
來看下結果:
清單26.1: GCC x64: f1()
```
#!bash
f1:
push r15 r14 r13 r12 rbp rdi rsi rbx
mov r13, QWORD PTR 120[rsp]
mov rbp, QWORD PTR 104[rsp]
mov r12, QWORD PTR 112[rsp]
test r13, r13
je .L1
add r13, 1
xor ebx, ebx
mov edi, 1
xor r11d, r11d
jmp .L4
.L6:
mov r11, rdi
mov rdi, rax
.L4:
lea rax, 0[0+r11*4]
lea r10, [rcx+rax]
lea r14, [rdx+rax]
lea rsi, [r8+rax]
add rax, r9
mov r15d, DWORD PTR [r10]
add r15d, DWORD PTR [r14]
mov DWORD PTR [rsi], r15d ; store to sum[]
mov r10d, DWORD PTR [r10]
imul r10d, DWORD PTR [r14]
mov DWORD PTR [rax], r10d ; store to product[]
mov DWORD PTR [r12+r11*4], ebx ; store to update_me[]
add ebx, 123
mov r10d, DWORD PTR [rsi] ; reload sum[i]
add r10d, DWORD PTR [rax] ; reload product[i]
lea rax, 1[rdi]
cmp rax, r13
mov DWORD PTR 0[rbp+r11*4], r10d ; store to sum_product[]
jne .L6
.L1:
pop rbx rsi rdi rbp r12 r13 r14 r15
ret
```
清單26.2: GCC x64: f2()
```
#!bash
f2:
push r13 r12 rbp rdi rsi rbx
mov r13, QWORD PTR 104[rsp]
mov rbp, QWORD PTR 88[rsp]
mov r12, QWORD PTR 96[rsp]
test r13, r13
je .L7
add r13, 1
xor r10d, r10d
mov edi, 1
xor eax, eax
jmp .L10
.L11:
mov rax, rdi
mov rdi, r11
.L10:
mov esi, DWORD PTR [rcx+rax*4]
mov r11d, DWORD PTR [rdx+rax*4]
mov DWORD PTR [r12+rax*4], r10d ; store to update_me[]
add r10d, 123
lea ebx, [rsi+r11]
imul r11d, esi
mov DWORD PTR [r8+rax*4], ebx ; store to sum[]
mov DWORD PTR [r9+rax*4], r11d ; store to product[]
add r11d, ebx
mov DWORD PTR 0[rbp+rax*4], r11d ; store to sum_product[]
lea r11, 1[rdi]
cmp r11, r13
jne .L11
.L7:
pop rbx rsi rdi rbp r12 r13
ret
```
被編譯過的f1()和f2()的不同點是:在f1()中,sum[i]和product[i]在循環中途被裝入,但是在f2()中沒有這樣的特性。已經計算過的變量將被使用,既然我們已經向編譯器“保證”在循環執行期間,sum[i]和product[i]不會發生改變,所以編譯器“確信”變量的值不用從內存被再裝入。很明顯,第二個例子的程序更快。 但是如果函數變量中的指針發生混淆的情況又能如何呢?這與一個程序員的認知有關,并且結果是不正確的。 回到FORTRAN。FORTRAN語言編譯器按照指針的本身含義對待他,所以當FORTRAN程序在這種情況下不可能使用restrict的時候,它可以生成生成執行更快的代碼。
這有什么實用價值?當函數處理內存中很多大“塊”的時候,比如說用超級計算機解決線性代數問題。或許這就是為什么FORTRAN語言還在這個領域被使用。 但是當迭代步驟不是很多的時候,速度的增加并不是顯著的。
- 第一章 CPU簡介
- 第二章 Hello,world!
- 第三章? 函數開始和結束
- 第四章 棧
- Chapter 5 printf() 與參數處理
- Chapter 6 scanf()
- CHAPER7 訪問傳遞參數
- Chapter 8 一個或者多個字的返回值
- Chapter 9 指針
- Chapter 10 條件跳轉
- 第11章 選擇結構switch()/case/default
- 第12章 循環結構
- 第13章 strlen()
- Chapter 14 Division by 9
- chapter 15 用FPU工作
- Chapter 16 數組
- Chapter 17 位域
- 第18章 結構體
- 19章 聯合體
- 第二十章 函數指針
- 第21章 在32位環境中的64位值
- 第二十二章 SIMD
- 23章 64位化
- 24章 使用x64下的SIMD來處理浮點數
- 25章 溫度轉換
- 26章 C99的限制
- 27章 內聯函數
- 第28章 得到不正確反匯編結果
- 第29章 花指令
- 第30章 16位Windows
- 第31章 類
- 三十二 ostream