質問

いくつかのドキュメントと質問/回答を閲覧していて、それが言及されているのを見つけました。簡単な説明を読んだところ、基本的にはポインタが他の場所を指すために使用されないというプログラマからの約束になると述べられていました。

これを実際に使用する価値があるいくつかの現実的なケースを誰かが提供できますか?

役に立ちましたか?

解決

restrictは、ポインタが基礎となるオブジェクトにアクセスする唯一のものであると述べています。これは、コンパイラによって最適化を可能にする、ポインタエイリアスの可能性を排除します。

たとえば、私はメモリに数値のベクトルを掛けることができ、特殊な命令でマシンを持っている、と私は次のコードがあるとします:

void MultiplyArrays(int* dest, int* src1, int* src2, int n)
{
    for(int i = 0; i < n; i++)
    {
        dest[i] = src1[i]*src2[i];
    }
}

コンパイラが適切に処理するために必要がある場合destsrc1、およびsrc2重なり、それは最初から最後まで、一度に1回の乗算を行う必要がありますを意味します。 restrictを有することによって、コンパイラは、ベクトル命令を使用してこのコードを最適化することが自由である。

ウィキペディアは、別の例で、restrict上のエントリを持っているここを。

他のヒント

ウィキペディアの例とても 照らす。

それは明らかにその方法を示しています 組み立て命令を 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は本当にそれをやりますか?

GCC 4.8 Linux x86-64:

gcc -g -std=c99 -O0 -c main.c
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 つの命令。

配列

これまでのところ、単一命令の節約ができていますが、ポインタがループされる配列を表す場合(一般的な使用例)、前述したように、大量の命令を節約できる可能性があります。 スーパーキャット.

たとえば、次のことを考えてみましょう。

void f(char *restrict p1, char *restrict p2) {
    for (int i = 0; i < 50; i++) {
        p1[i] = 4;
        p2[i] = 9;
    }
}

のため restrict, 、賢いコンパイラ (または人間) は、それを次のように最適化できます。

memset(p1, 4, 50);
memset(p2, 9, 50);

これは、適切な libc 実装 (glibc など) でアセンブリが最適化されている可能性があるため、はるかに効率的になる可能性があります。 パフォーマンスの観点から、 std::memcpy() と std::copy() のどちらを使用する方が良いでしょうか?

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
    

    memset 予想通りに電話がかかります。

  • 制限なし:stdlib 呼び出しはなく、反復幅は 16 回だけです ループ展開 ここで再現するつもりはありません:-)

ベンチマークを行う忍耐力はありませんが、制限付きバージョンの方が高速であると信じています。

C99

完全を期すための基準を見てみましょう。

restrict 2 つのポインタが重複するメモリ領域を指すことはできないと述べています。最も一般的な使用法は関数の引数です。

これにより、関数の呼び出し方法が制限されますが、コンパイル時の最適化がさらに可能になります。

発信者が指示に従わない場合、 restrict 契約、未定義の動作。

C99 N1256 ドラフト 6.7.3/7「型修飾子」には次のように書かれています。

制限修飾子 (レジスタ ストレージ クラスなど) の使用目的は、最適化を促進することであり、適合プログラムを構成するすべての前処理翻訳単位から修飾子のすべてのインスタンスを削除しても、その意味 (つまり、観察可能な動作) は変わりません。

6.7.3.1「制限の正式な定義」に悲惨な詳細が記載されています。

厳密なエイリアス規則

restrict キーワードは、互換性のある型のポインタにのみ影響します (例:二 int*) 厳密なエイリアス規則では、互換性のない型のエイリアスはデフォルトでは未定義の動作であると規定されているため、コンパイラはそれが起こらないと想定して最適化を行うことができます。

見る: 厳密なエイリアス規則とは何ですか?

こちらも参照

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top