Như những người khác đã nói, nếu có nghĩa là không có gì trong C ++ 14 , vì vậy hãy xem xét __restrict__
tiện ích mở rộng GCC giống như C99 restrict
.
C99
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 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.
Tối ưu hóa có thể
Các ví dụ Wikipedia là rất sáng.
Nó cho thấy rõ làm thế nào khi 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 a may be equal to x.
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
load R2 ← *b
add R2 += R1
set R2 → *b
GCC có thực sự làm điều đó?
g++
4,8 Linux x86-64:
g++ -g -std=gnu++98 -O0 -c main.cpp
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 và michael .
Xem xét ví dụ:
void f(char *restrict p1, char *restrict p2, size_t size) {
for (size_t i = 0; i < size; 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, size);
memset(p2, 9, size);
Điều này có khả năng hiệu quả hơn nhiều vì nó có thể được lắp ráp được tối ưu hóa khi triển khai libc (như glibc) tốt hơn nên sử dụng std :: memcpy () hoặc std :: copy () để thực hiện? , có thể với hướng dẫn SIMD .
Nếu không, hạn chế, việc tối ưu hóa này không thể được thực hiện, ví dụ: xem xét:
char p1[4];
char *p2 = &p1[1];
f(p1, p2, 3);
Sau đó, for
phiên bản làm cho:
p1 == {4, 4, 4, 9}
trong khi memset
phiên bản tạo ra:
p1 == {4, 9, 9, 9}
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ỉ cần một vòng lặp rộng 16 vòng lặp 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.
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ì?
Nó hoạt động để tham khảo?
Theo các tài liệu GCC, nó thực hiện: https://gcc.gnu.org/onlinesocs/gcc-5.1.0/gcc/Restricted-Pointers.html với cú pháp:
int &__restrict__ rref
Thậm chí còn có một phiên bản cho this
các chức năng thành viên:
void T::fn () __restrict__
restrict
là một từ khóa c99. Vâng, Rpbert S. Barnes, tôi biết rằng hầu hết các trình biên dịch đều hỗ trợ__restrict__
. Bạn sẽ lưu ý rằng bất cứ điều gì có dấu gạch dưới kép, theo định nghĩa, việc triển khai cụ thể và do đó KHÔNG phải là C ++ , mà là một phiên bản cụ thể của trình biên dịch.