C++のrestrictキーワードはどういう意味ですか?
関数に指定された2つ以上のポインターがオーバーラップしないということですか?他に何を意味するのでしょうか?
Christer Ericson氏の論文 Memory Optimization では、restrict
はまだC++標準の一部ではありませんが、多くのコンパイラでサポートされており、利用可能な場合は使用することを推奨しています。
キーワードを制限する
! 1999 ANSI/ISO C規格の新機能
!まだC++標準ではありませんが、多くのC++コンパイラでサポートされています
!ヒントのみであるため、何もせずに適合している可能性があります
制限付きのポインター(または参照)...
! ...基本的には、ポインターのスコープに対して、ポインターのターゲットはそのポインター(およびそこからコピーされたポインター)を介してのみアクセスされるというコンパイラーへの約束です。
それをサポートするC++コンパイラでは、おそらくCと同じように動作するはずです。
詳細については、このSO投稿を参照してください: C99の「制限」キーワードの現実的な使用法
エリクソンの論文をざっと読むのに30分かかります。おもしろくて価値があります。
編集
また、IBMの AIX C/C++コンパイラは__restrict__
キーワードをサポートしています であることもわかりました。
次のプログラムがg ++で正常にコンパイルされるため、g ++もこれをサポートしているようです。
#include <stdio.h>
int foo(int * __restrict__ a, int * __restrict__ b) {
return *a + *b;
}
int main(void) {
int a = 1, b = 1, c;
c = foo(&a, &b);
printf("c == %d\n", c);
return 0;
}
restrict
の使用に関する素晴らしい記事も見つけました。
Edit2
私は、C++プログラムでの制限の使用について具体的に説明している記事に出くわしました。
Load-hit-storesおよび__restrictキーワード
また、Microsoft Visual C++ __restrict
キーワードもサポート 。
他の人が言ったように、ifはC++ 14の時点ではを意味しないなので、C99 restrict
と同じことを行う__restrict__
GCC拡張を考えてみましょう。
C99
restrict
は、2つのポインターが重複するメモリー領域を指すことができないことを示します。最も一般的な使用法は、関数の引数です。
これにより、関数の呼び出し方法が制限されますが、より多くのコンパイル最適化が可能になります。
呼び出し元がrestrict
コントラクトに従わない場合、未定義の動作。
C99 N1256 draft 6.7.3/7 "Type qualifiers"は次のように述べています。
制限修飾子(レジスタストレージクラスなど)の使用目的は、最適化を促進することであり、適合プログラムを構成するすべての前処理変換ユニットから修飾子のすべてのインスタンスを削除しても、その意味は変わりません(つまり、観察可能な動作)。
6.7.3.1「制限の正式な定義」には、詳細な説明があります。
可能な最適化
Wikipediaの例 はvery照明です。
1つのアセンブリ命令を保存できるようにする方法を明確に示しています。
制限なし:
void f(int *a, int *b, int *x) {
*a += *x;
*b += *x;
}
擬似アセンブリ:
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
制限付き:
void fr(int *__restrict__ a, int *__restrict__ b, int *__restrict__ x);
擬似アセンブリ:
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は本当にそれをしますか?
g++
4.8 Linux x86-64:
g++ -g -std=gnu++98 -O0 -c main.cpp
objdump -S main.o
-O0
の場合、それらは同じです。
-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)
初心者の場合、 呼び出し規約 は次のとおりです。
rdi
=最初のパラメーターrsi
= 2番目のパラメーターrdx
= 3番目のパラメーターGCCの出力は、Wikiの記事よりも明確でした:4命令対3命令。
配列
これまでのところ、単一の命令の節約がありますが、ポインターがループオーバーされる配列を表す場合、一般的なユースケースでは、 supercat および マイケル 。
例について考えてみましょう:
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;
}
}
restrict
のおかげで、スマートコンパイラ(または人間)は、それを次のように最適化できます。
memset(p1, 4, size);
memset(p2, 9, size);
適切なlibc実装(glibcなど)でアセンブリが最適化される可能性があるため、潜在的にはるかに効率的です パフォーマンスの観点からstd :: memcpy()またはstd :: copy()を使用する方が良いですか? 、場合によっては SIMD命令 を使用します。
制限なしでは、この最適化はできません。考慮してください:
char p1[4];
char *p2 = &p1[1];
f(p1, p2, 3);
次に、for
バージョンは以下を作成します。
p1 == {4, 4, 4, 9}
memset
バージョンでは次のようになります。
p1 == {4, 9, 9, 9}
GCCは本当にそれをしますか?
GCC 5.2.1.Linux x86-64 Ubuntu 15.10:
gcc -g -std=c99 -O0 -c main.c
objdump -dr main.o
-O0
では、両方とも同じです。
-O3
を使用:
制限付き:
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
期待どおりの2つのmemset
呼び出し。
制限なし:stdlib呼び出しはなく、16反復幅 ループ展開 のみで、ここでは再現しません:-)
私はそれらをベンチマークする忍耐を持っていませんでしたが、私は制限バージョンがより速くなると信じています。
厳密なエイリアスルール
restrict
キーワードは、互換性のある型(2つのint*
など)のポインターにのみ影響します。厳密なエイリアスルールでは、互換性のない型のエイリアスはデフォルトでは未定義の動作であるため、コンパイラーはそれが発生しないと想定して最適化を解除できるためです。
参照に対して機能しますか?
GCCのドキュメントによると: https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Restricted-Pointers.html と構文:
int &__restrict__ rref
メンバー関数のthis
のバージョンもあります。
void T::fn () __restrict__
なし。 C99標準に追加されました。
C++にはそのようなキーワードはありません。 C++キーワードのリストは、C++言語標準のセクション2.11/1にあります。 restrict
は、C++ではなく、C99バージョンのC言語のキーワードです。
一部のCライブラリのヘッダーファイルはキーワードを使用するため、C++言語は少なくともキーワードを無視する必要があります。したがって、キーワードを抑制するためにキーワードを空のマクロに#defineする必要はありません。 。