13

restrict私は多くの行列演算を行っており、C99 のポインター修飾子を利用したいと考えています。

次のように、マトリックスをポインターへのポインターとして設定して、添字を簡単に付けられるようにしたいと思います。

int **A = malloc (ncols * sizeof(int *));
A[0] = malloc (nrows * ncols * sizof(int));
for (int i=1; i < ncols; i++) {
    A[i] = A[0] + i*nrows;
}

さて、行列乗算関数

void mmultiply ( int nrows, int ncols, int **Out, int **A, int **B);

引数の両方のポインターを制限付きとして修飾する必要がありますか? int *restrict *restrictこれは有効な構文ですが、 と異なる動作をするかどうかを判断するのに苦労していint **restrictます。

次に、ポインターが適切に制限されている場合、A[0][col*nrows + row]未定義を介して要素にアクセスしていますか? (つまり、コンパイラは、そのようなの値に対してのみ行列にアクセスすると想定しますか?) それとも、単に一貫性を保つ必要がありますか?A[col][row]rowrow < nrow

4

3 に答える 3

4

最初の質問「はい」については、両方のrestrict修飾子を使用すると意味が異なります。具体的には、ポインターもエイリアスされないということです。違いが生じるかどうかについては、理論的にはそうですが、実際にはオプティマイザに依存します。

2 番目の質問「はい」については、行ポインターを介してアクセスされるものはすべて、行ポインターを介してのみアクセスされると想定されます。

あなたもそこに投げ込むことができconstます。

最後に、これが -O2、-O3、または -Os の gcc である場合、コンパイラは型に基づいてエイリアス分析を既に実行しています。他のコンパイラもこれを行うと確信しています。これは、ポインターと int の制限が既に理解されており、相互に格納できる可能性のある配列のみが残っていることを意味します。

つまり、オプティマイザーは、ポインターが int として格納されていないと想定し、ループ中にポインターの書き込みを行っていないことを認識します。

そのため、制限が 1 つだけの同じコードが得られる可能性があります。

于 2009-09-20T00:23:03.057 に答える
2

外側 (2 番目) の restrict は、ポインターの配列 (A、B、および out) のエイリアスがないことをコンパイラーに伝えます。内側 (最初の) の restrict は、int の配列 (ポインターの配列の要素によってポイントされる) のいずれも別名ではないことをコンパイラーに伝えます。

A[0][col*nrows + row] と A[col][row] の両方にアクセスすると、内部制限に違反しているため、問題が発生する可能性があります。

于 2009-09-20T00:09:01.803 に答える
2

int **restrictOut、A、および B によってアドレス指定されたメモリが重複しないことのみをアサートします (ただし、関数がそれらのいずれも変更しないと仮定すると、A と B は重複する可能性があります)。これはポインタの配列を意味します。Out、A、および B が指すメモリの内容については何も主張しません。n1124 の脚注 117 には次のように記載されています。

識別子 p が型 (int **restrict) を持つ場合、ポインター式 p および p+1 は、p によって指定された制限付きポインター オブジェクトに基づいていますが、ポインター式 *p および p[1] はそうではありません。

と同様にconst、two で修飾するとrestrict、配列内のいずれの値もオーバーラップ メモリを指していないという、希望どおりの結果が得られるのではないかと思います。しかし、標準を読んでも、それが実際に有効であることを証明することはできません。「D を、オブジェクト P を型 T への制限修飾ポインターとして指定する手段を提供する通常の識別子の宣言とする」は、実際には for int *restrict *restrict A、A[0] および A[1] がオブジェクトであることを意味すると思いますint への制限修飾ポインターとして指定されます。しかし、それはかなり重い法律用語です。

あなたのコンパイラが実際にその知識で何かをするかどうかはわかりません。明らかに可能です。実装されているかどうかの問題です。

rows * cols * sizeof(int)したがって、 を割り当て、 でインデックス付けするだけの従来の C 2 次元配列に比べて、何が得られたのかはよくわかりませんA[cols*row + col]。次に、restrict を 1 回だけ使用する必要があることは明らかです。これを使用して何かをrestrict行うコンパイラは、Out への書き込み全体で A および B からの読み取りを並べ替えることができます。もちろん、なしrestrictではできません。したがって、自分がしていることを行うことで、コンパイラの慈悲に身を置くことになります。二重制限に対応できず、単一の制限ケースのみに対応できない場合、二重間接化により最適化が犠牲になります。

最初の推測では、とにかく、乗算は追加のポインター間接化よりも高速である可能性があります。明らかにパフォーマンスを気にするか、restrict をまったく使用しないため、この変更を行う前に (関心のあるすべてのコンパイラで) かなり慎重にパフォーマンスをテストします。アクセスするたびに配列に列があります。

A[0][col*nrows + row] undefined を介して要素にアクセスしていますか?

はい、要素がアクセスの 1 つによって変更された場合、これにより A[0] が A[col] を介してアクセスされるメモリのエイリアスになるためです。A と B のみが制限修飾されたポインターであれば問題ありませんが、A[0] と A[col] がそうである場合はそうではありません。

この関数では A を変更しないと想定しているので、実際にはそのエイリアスで問題ありません。ただし、Out で同じことを行った場合、動作は未定義になります。

于 2009-09-20T00:30:43.167 に答える