Các ví dụ Wikipedia là rất sáng.
Nó cho thấy rõ làm thế nào nó cho phép lưu một hướng dẫn lắp ráp .
Không giới hạn:
void f(int *a, int *b, int *x) {
*a += *x;
*b += *x;
}
Lắp ráp giả:
load R1 ← *x ; Load the value of x pointer
load R2 ← *a ; Load the value of a pointer
add R2 += R1 ; Perform Addition
set R2 → *a ; Update the value of a pointer
; Similarly for b, note that x is loaded twice,
; because x may point to a (a aliased by x) thus
; the value of x will change when the value of a
; changes.
load R1 ← *x
load R2 ← *b
add R2 += R1
set R2 → *b
Với hạn chế:
void fr(int *restrict a, int *restrict b, int *restrict x);
Lắp ráp giả:
load R1 ← *x
load R2 ← *a
add R2 += R1
set R2 → *a
; Note that x is not reloaded,
; because the compiler knows it is unchanged
; "load R1 ← *x" is no longer needed.
load R2 ← *b
add R2 += R1
set R2 → *b
GCC có thực sự làm điều đó?
GCC 4.8 Linux x86-64:
gcc -g -std=c99 -O0 -c main.c
objdump -S main.o
Với -O0
, họ là như nhau.
Với -O3
:
void f(int *a, int *b, int *x) {
*a += *x;
0: 8b 02 mov (%rdx),%eax
2: 01 07 add %eax,(%rdi)
*b += *x;
4: 8b 02 mov (%rdx),%eax
6: 01 06 add %eax,(%rsi)
void fr(int *restrict a, int *restrict b, int *restrict x) {
*a += *x;
10: 8b 02 mov (%rdx),%eax
12: 01 07 add %eax,(%rdi)
*b += *x;
14: 01 06 add %eax,(%rsi)
Đối với người không quen biết, quy ước gọi là:
rdi
= tham số đầu tiên
rsi
= tham số thứ hai
rdx
= tham số thứ ba
Đầu ra GCC thậm chí còn rõ ràng hơn bài viết wiki: 4 hướng dẫn so với 3 hướng dẫn.
Mảng
Cho đến nay chúng ta có các khoản tiết kiệm lệnh đơn, nhưng nếu con trỏ biểu thị các mảng được lặp lại, một trường hợp sử dụng phổ biến, thì một loạt các hướng dẫn có thể được lưu, như được đề cập bởi supercat .
Xem xét ví dụ:
void f(char *restrict p1, char *restrict p2) {
for (int i = 0; i < 50; i++) {
p1[i] = 4;
p2[i] = 9;
}
}
Bởi vì restrict
, một trình biên dịch thông minh (hoặc con người), có thể tối ưu hóa điều đó thành:
memset(p1, 4, 50);
memset(p2, 9, 50);
có khả năng hiệu quả hơn nhiều vì nó có thể được tối ưu hóa khi triển khai libc (như glibc): Sử dụng std :: memcpy () hoặc std :: copy () để thực hiện tốt hơn?
GCC có thực sự làm điều đó?
GCC 5.2.1.Linux x86-64 Ubuntu 15.10:
gcc -g -std=c99 -O0 -c main.c
objdump -dr main.o
Với -O0
, cả hai đều giống nhau.
Với -O3
:
với hạn chế:
3f0: 48 85 d2 test %rdx,%rdx
3f3: 74 33 je 428 <fr+0x38>
3f5: 55 push %rbp
3f6: 53 push %rbx
3f7: 48 89 f5 mov %rsi,%rbp
3fa: be 04 00 00 00 mov $0x4,%esi
3ff: 48 89 d3 mov %rdx,%rbx
402: 48 83 ec 08 sub $0x8,%rsp
406: e8 00 00 00 00 callq 40b <fr+0x1b>
407: R_X86_64_PC32 memset-0x4
40b: 48 83 c4 08 add $0x8,%rsp
40f: 48 89 da mov %rbx,%rdx
412: 48 89 ef mov %rbp,%rdi
415: 5b pop %rbx
416: 5d pop %rbp
417: be 09 00 00 00 mov $0x9,%esi
41c: e9 00 00 00 00 jmpq 421 <fr+0x31>
41d: R_X86_64_PC32 memset-0x4
421: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
428: f3 c3 repz retq
Hai memset
cuộc gọi như mong đợi.
không hạn chế: không có cuộc gọi stdlib, chỉ một vòng lặp rộng 16 vòng lặp không kiểm soát mà tôi không có ý định sao chép ở đây :-)
Tôi không đủ kiên nhẫn để đánh giá chúng, nhưng tôi tin rằng phiên bản giới hạn sẽ nhanh hơn.
C99
Chúng ta hãy nhìn vào tiêu chuẩn cho sự hoàn thiện vì lợi ích.
restrict
nói rằng hai con trỏ không thể trỏ đến các vùng bộ nhớ chồng chéo. Việc sử dụng phổ biến nhất là cho các đối số chức năng.
Điều này hạn chế cách hàm có thể được gọi, nhưng cho phép tối ưu hóa thời gian biên dịch nhiều hơn.
Nếu người gọi không tuân theo restrict
hợp đồng, hành vi không xác định.
Bản dự thảo C99 N1256 6.7.3 / 7 "Loại vòng loại" cho biết:
Mục đích sử dụng của vòng loại hạn chế (như lớp lưu trữ đăng ký) là để thúc đẩy tối ưu hóa và xóa tất cả các phiên bản của vòng loại khỏi tất cả các đơn vị dịch tiền xử lý soạn thảo chương trình tuân thủ không thay đổi ý nghĩa của nó (nghĩa là hành vi có thể quan sát được).
và 6.7.3.1 "Định nghĩa chính thức về hạn chế" đưa ra các chi tiết chính.
Quy tắc răng cưa nghiêm ngặt
Các restrict
từ khóa chỉ ảnh hưởng đến con trỏ của các loại tương thích (ví dụ như hai int*
) bởi vì các quy tắc nghiêm ngặt răng cưa nói rằng răng cưa loại không tương thích là hành vi không xác định theo mặc định, và do đó trình biên dịch có thể giả định nó không xảy ra và tối ưu hóa đi.
Xem: quy tắc răng cưa nghiêm ngặt là gì?
Xem thêm
memcpy
vsmemmove
là một ví dụ điển hình.