C에서 restrict 키워드를 사용하는 규칙?
언제, 언제를 사용하지 않는지를 이해하려고 합니다.restrict
어떤 상황에서 가시적인 이점을 제공하는지에 대해 설명합니다.
「Demystifying The Restrict Keyword」(사용법에 관한 몇 가지 규칙이 기재되어 있습니다)를 읽은 후, 함수가 포인터를 건네받았을 때, 그 함수에 건네지는 다른 인수와 데이터가 중복(에일리어스)될 가능성을 고려해야 한다는 인상을 받습니다.지정된 기능:
foo(int *a, int *b, int *c, int n) {
for (int i = 0; i<n; ++i) {
b[i] = b[i] + c[i];
a[i] = a[i] + b[i] * c[i];
}
}
컴파일러는 새로고침해야 합니다.c
두 번째 표현으로 표현하면b
그리고.c
같은 위치를 가리킵니다.또, 이 시스템은,b
(장전하기 전에)a
같은 이유로.그 후, 그것은 대기해야 한다.a
저장하여 다시 로드해야 합니다.b
그리고.c
다음 루프의 선두에 있습니다.함수를 다음과 같이 호출하면:
int a[N];
foo(a, a, a, N);
컴파일러가 이 작업을 수행해야 하는 이유를 알 수 있습니다.사용.restrict
컴파일러에게 이 작업을 절대로 하지 않을 것을 효과적으로 전달하여 용장 부하를 떨어뜨릴 수 있도록 합니다.c
로딩a
전에b
저장됩니다.
다른 SO 게시물인 Nils Pipenbrinck에서는 성능 이점을 보여주는 이 시나리오의 실제 예를 제공합니다.
지금까지 제가 알아낸 바로는 이 제품을 사용하는 것이 좋습니다.restrict
포인터에 삽입되지 않는 함수로 넘어갑니다.코드가 삽입되어 있으면 컴파일러는 포인터가 겹치지 않는 것을 알 수 있다고 합니다.
여기서부터 상황이 애매해지기 시작한다.
Ulrich Drepper의 논문 "모든 프로그래머가 메모리에 대해 알아야 할 것"에서 그는 "제한이 사용되지 않는 한 모든 포인터 액세스는 에일리어싱의 잠재적 소스"라고 진술하고 그가 사용하는 서브매트릭스 행렬 곱셈의 구체적인 코드 예를 제시합니다.restrict
.
하지만 내가 그의 예제 코드를 컴파일할 때restrict
두 경우 모두 동일한 바이너리를 얻습니다.사용하고 있다gcc version 4.2.4 (Ubuntu 4.2.4-1ubuntu4)
다음 코드에서 알 수 없는 것은 보다 광범위하게 사용하기 위해 다시 작성해야 하는지 여부입니다.restrict
또는 GCC의 에일리어스 분석이 너무 좋아서 인수가 서로 에일리어스되지 않았는지 확인할 수 있습니다.순수하게 교육적인 목적으로 이 코드의 내용을 사용 또는 사용하지 않도록 하려면 어떻게 해야 합니까? 그 이유는 무엇입니까?
위해서restrict
컴파일 대상:
gcc -DCLS=$(getconf LEVEL1_DCACHE_LINESIZE) -DUSE_RESTRICT -Wextra -std=c99 -O3 matrixMul.c -o matrixMul
제거만 하면 됩니다.-DUSE_RESTRICT
쓰지 않다restrict
.
#include <stdlib.h>
#include <stdio.h>
#include <emmintrin.h>
#ifdef USE_RESTRICT
#else
#define restrict
#endif
#define N 1000
double _res[N][N] __attribute__ ((aligned (64)));
double _mul1[N][N] __attribute__ ((aligned (64)))
= { [0 ... (N-1)]
= { [0 ... (N-1)] = 1.1f }};
double _mul2[N][N] __attribute__ ((aligned (64)))
= { [0 ... (N-1)]
= { [0 ... (N-1)] = 2.2f }};
#define SM (CLS / sizeof (double))
void mm(double (* restrict res)[N], double (* restrict mul1)[N],
double (* restrict mul2)[N]) __attribute__ ((noinline));
void mm(double (* restrict res)[N], double (* restrict mul1)[N],
double (* restrict mul2)[N])
{
int i, i2, j, j2, k, k2;
double *restrict rres;
double *restrict rmul1;
double *restrict rmul2;
for (i = 0; i < N; i += SM)
for (j = 0; j < N; j += SM)
for (k = 0; k < N; k += SM)
for (i2 = 0, rres = &res[i][j],
rmul1 = &mul1[i][k]; i2 < SM;
++i2, rres += N, rmul1 += N)
for (k2 = 0, rmul2 = &mul2[k][j];
k2 < SM; ++k2, rmul2 += N)
for (j2 = 0; j2 < SM; ++j2)
rres[j2] += rmul1[k2] * rmul2[j2];
}
int main (void)
{
mm(_res, _mul1, _mul2);
return 0;
}
이것은 코드 옵티마이저에 대한 힌트입니다.restrict를 사용하면 포인터 변수를 CPU 레지스터에 저장할 수 있으므로 에일리어스도 갱신하기 위해 포인터 값의 업데이트를 메모리에 플래시할 필요가 없습니다.
그것을 활용할지는 옵티마이저와 CPU의 구현 세부 사항에 크게 좌우됩니다.코드 옵티마이저는 이미 비에일리어싱 검출에 많은 투자를 하고 있습니다.이는 매우 중요한 최적화이기 때문입니다.당신의 코드로 그것을 검출하는 것은 문제 없을 것입니다.
또한 GCC 4.0.0-4.4에는 restrict 키워드가 무시되는 회귀 버그가 있습니다.이 버그는 4.5에서 수정되었다고 보고되었습니다(단, 버그 번호를 잃어버렸습니다).
(이 키워드를 사용하는 것이, 실제로 큰 메리트를 얻을 수 있을지는 모르겠습니다.프로그래머가 이 수식자를 잘못 사용하는 것은 매우 쉬우므로 프로그래머가 "거짓말"을 하지 않는다는 것을 옵티마이저가 확신할 수 없습니다.)
포인터 A가 메모리의 일부 영역에 대한 유일한 포인터라는 것을 알고 있는 경우, 즉 에일리어스가 없는 경우(즉, 다른 포인터 B는 반드시 A, B!= A와 동일하지 않음) A의 유형을 "squal" 키워드로 한정함으로써 이 사실을 옵티마이저에 알릴 수 있습니다.
나는 여기에 이것에 대해 썼다: http://mathdev.org/node/23 그리고 몇몇 제한된 포인터가 실제로 "선형"이라는 것을 보여주려고 했다(그 게시물에 언급된 바와 같이).
주목할 필요가 있는 것은 최신 버전은clang
는 에일리어싱 런타임체크를 사용하여 코드를 생성할 수 있으며, 2개의 코드 패스(에일리어싱 가능성이 있는 경우 및 명백한 경우)를 생성할 수 있습니다.
이는 위의 예시와 같이 컴파일러가 주목할 만한 데이터의 범위에 따라 달라집니다.
저는 STL을 많이 사용하는 프로그램이 가장 정당하다고 생각합니다 - 특히<algorithm>
도입이 어렵거나 불가능하거나 둘 중 하나입니다.__restrict
한정자
물론 이 모든 것은 코드 사이즈를 희생하지만 포인터가 다음과 같이 선언될 수 있는 불명확한 버그에 대한 많은 가능성을 제거합니다.__restrict
개발자가 생각했던 것만큼 잘 섞이지 않습니다.
만약 GCC도 이 최적화를 하지 않았다면 나는 놀랐을 것이다.
여기서 행해지는 최적화는 포인터가 에일리어스가 되지 않는 것에 의존하지 않는가?res2를 쓰기 전에 여러 mul2 요소를 미리 로드하지 않는 한 에일리어스 문제는 없습니다.
첫 번째 코드 조각에서는 어떤 종류의 에일리어스 문제가 발생할 수 있는지 알 수 있습니다.여기서 그것은 명확하지 않다.
Dreppers 기사를 다시 읽으면서, 그는 특별히 제한이 어떤 것도 해결할 수 있다고 말하지 않았다.심지어 다음과 같은 문구도 있습니다.
{이론적으로는 1999년 개정판에서 C언어에 도입된 restrict 키워드로 문제를 해결할 수 있습니다.하지만 컴파일러는 아직 따라잡지 못했습니다.그 이유는 컴파일러가 잘못된 오브젝트 코드를 생성하게 되는 잘못된 코드가 너무 많이 존재하기 때문입니다.}
이 코드에서는, 메모리 액세스의 최적화가 알고리즘내에서 이미 행해지고 있습니다.잔여 최적화는 부록에 제시된 벡터화된 코드로 수행되는 것으로 보인다.따라서 여기에 제시된 코드의 경우 제한에 의존하는 최적화는 이루어지지 않기 때문에 차이가 없다고 생각합니다.모든 포인터 액세스는 에일리어싱의 소스이지만 모든 최적화가 에일리어싱에 의존하는 것은 아닙니다.
섣부른 최적화가 모든 악의 근원이기 때문에 restrict 키워드의 사용은 사용 가능한 장소에서가 아니라 적극적으로 연구하고 최적화하는 경우에 한정해야 합니다.
조금이라도 차이가 있다면, 이사하는 것mm
(gcc가 발신자 코드의 모든 것을 알 수 없게 된 경우 등)는, 그것을 나타내는 방법이 됩니다.
32비트 Ubuntu 또는 64비트 Ubuntu로 실행 중입니까?32비트의 경우, 다음 중 하나를 추가해야 합니다.-march=core2 -mfpmath=sse
(또는 사용하고 있는 프로세서 아키텍처가 무엇이든), 그 이외의 경우 SSE를 사용하지 않습니다.다음으로 GCC 4.2를 사용하여 벡터화를 활성화하려면-ftree-vectorize
option (4.3 또는 4.4에서는 디폴트로 포함되어 있습니다.-O3
)를 추가해야 할 수도 있습니다.-ffast-math
컴파일러가 부동소수점 연산을 재정렬할 수 있도록 하기 위해 (또는 완화된 부동소수점 의미론을 제공하는 다른 옵션).
또,-ftree-vectorizer-verbose=1
루프를 벡터화할 수 있는지 여부를 확인하는 옵션입니다.이것은 restrict 키워드를 추가했을 때의 효과를 확인하는 간단한 방법입니다.
이 샘플 코드의 문제는 컴파일러가 콜을 인라인으로 하는 것만으로, 이 샘플에서는 에일리어싱이 불가능하다는 것입니다.main() 함수를 삭제하고 -c를 사용하여 컴파일할 것을 권장합니다.
언급URL : https://stackoverflow.com/questions/2005473/rules-for-using-the-restrict-keyword-in-c
'programing' 카테고리의 다른 글
모든 데이터를 모든 슬롯에 할당 (0) | 2022.08.12 |
---|---|
하위 구성 요소에 전달된 프로포트가 생성된 메서드에 정의되지 않았습니다. (0) | 2022.08.12 |
NET::VueJS 프로젝트 실행 시 ERR_CERT_INVALID 오류 (0) | 2022.08.12 |
Linux의 최대 PID (0) | 2022.08.12 |
Laravel / Vue: v-model 사용 시 검증 중 (0) | 2022.08.12 |