web-dev-qa-db-ja.com

C ++のrestrictキーワードの意味は何ですか?

C++のrestrictキーワードはどういう意味ですか?

関数に指定された2つ以上のポインターがオーバーラップしないということですか?他に何を意味するのでしょうか?

177
user34537

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キーワードもサポート

136

他の人が言ったように、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標準に追加されました。

20
dirkgently

This は、このキーワードを追加する元の提案です。率直に指摘したように、これは C99 機能です。 C++とは関係ありません。

10
unwind

C++にはそのようなキーワードはありません。 C++キーワードのリストは、C++言語標準のセクション2.11/1にあります。 restrictは、C++ではなく、C99バージョンのC言語のキーワードです。

5
AnT

一部のCライブラリのヘッダーファイルはキーワードを使用するため、C++言語は少なくともキーワードを無視する必要があります。したがって、キーワードを抑制するためにキーワードを空のマクロに#defineする必要はありません。 。

4
Johan Boulé