Programming/Linux(Unix)

Restrict 한정어

부풍 2009. 7. 29. 08:50

하드웨어적으로 병렬 처리를 지원하는 환경(예를 들면 벡터 프로세서)에서 제공하는 병렬화 기능을 제대로 활용하기 위해서는 기본적으로 특정 연산이 반복해서 적용되는 두 배열 대상체가 서로 무관해야 한다. 즉 그와 같은 환경에서 제공하는 성능 좋은 최적화 기능을 십분 활용하기 위해서는 특정 연산을 수행하는 함수에 매개변수를 통해 주어지는 두 배열의 모든 요소들이 서로 에일리어징되어서는 안 된다는 의미이다. C 언어를 처음 표준화하던 시기에 컴파일러에게 두 대상체가 서로 에일리어징되지 않았음을 확신시키기 위한 방법으로 noalias라는 형 한정어(type qualifier)를 도입하려 했다. 하지만 이 형 한정어는 상당히 엄격하고 복잡한 의미를 가지고 있어 제대로 기술하기도 쉽지 않았고, 이를 도입할 경우 언어에 심각한 오점을 만들 가능성도 있기에 심한 반대에 부딪혀 결국 표준에 입성하지 못했다(http://www.lysator.liu.se/c/dmr-on-noalias.html 참고). 다만 C90의 끝자락에 서로 겹쳐진 두 배열 대상체를 함수에 전달하는 것은 병렬 환경에서의 최적화를 고려해 별로 바람직하지 않다는 일종의 충고만이 자리잡고 있을 뿐이었다.

그렇게 강력한 반대 속에 사라진 noalias의 의도는 훨씬 유연한 의미를 갖는 제한된 포인터(restricted pointer)라는 이름으로 C99에서 부활하게 된다. 물론 이미 noalias를 통해 시행착오를 한번 겪었기에 noalias와는 많이 다른 방법을 통해 접근하게 된다. 제한된 포인터란 포인터에만 적용되어 유효한 의미를 갖는 형 한정어 restrict를 갖는 포인터를 말한다. 이렇게 선언된 포인터의 정확한 의미는 표준에서조차 상당히 수학적으로 기술되어 있기에 이곳에서 모두 다루기에는 무리가 있다. 다만 대략적인 의미를 다음과 같이 설명할 수 있다.

 

void func(double *restrict d, cont double *restrict s, size_t n);

 

이 선언은 함수 func() 안에서 d가 가리킬 수 있는 대상체와 s가 가리킬 수 있는 대상체가 서로 무관함을 의미한다. 이러한 보장을 통해 병렬 연산을 통한 최적화가 지원되는 환경의 컴파일러는 d s가 각각 가리키는 대상체에 적용되는 연산을 병렬화하여 최적화할 수 있다. 물론 제한된 포인터를 매개변수로 갖는 함수에 겹쳐진 배열 대상체를 전달하는 행위는 이제 불법이 되며 앞서 살펴본 에일리어징과 관련된 예처럼 예상치 못한 결과를 얻을 수 있는 원인이 될 수도 있다. 참고로 restrict는 레지스터 처럼 컴파일러에게 최적화를 위해 프로그래머가 제공해 주는 일종의 힌트일 뿐이다. 따라서 restrict를 통해 이룰 수 있는 최적화와 무관한 환경(혹은 그러한 최적화가 존재하지만 컴파일러 제작자가 무능력하거나 게으른 경우)에서는 컴파일러가 간단히 restrict를 무시해 버릴 수도 있다.

제한된 포인터가 함수 매개변수에서 사용되면 결국 해당 포인터가 가리키는 대상체가 서로 겹치지 않았음을 의미하기 때문에 서로 겹쳐진 대상체를 인자로 주어서는 안 되는 기존의 표준 라이브러리 함수를 기술하는 방법도 훨씬 수월해졌다. 예를 들어 표준은 메모리의 블럭 단위 복사에 대해 특별히 효율적인 연산을 제공하는 환경을 고려해 메모리 복사 함수를 memcpy() memmove()로 나눠 제공하고 있다. 겹쳐진 메모리 공간에서도 올바른 복사가 이루어지도록 하기 위해 두 메모리 공간이 겹쳐 있음을 확인하는 과정 자체가 무시 못할 오버헤드가 되기 때문에 프로그래머는 메모리가 겹쳐 있지 않음을 확신하는 경우 memcpy()를 사용해 잠재적으로 좋은 성능을 기대할 수 있다. 겹쳐 있을 가능성이 있는 경우 약간의 오버헤드를 감수하고 안전하게 memmove()를 사용할 수 있는 것이다. 따라서 C90에서는 memcpy()의 원형을 다음과 같이 선언한다.

 

memcpy(void *, const void *, size_t);

 

말로써 겹쳐진 메모리 영역 사이의 복사를 금지했지만, 제한된 포인터의 도입으로 이제 C99에서는 memcpy() memmove()가 서로 다른 형태의 원형을 갖고 있음을 확인할 수 있다.

memcpy(void *restrict, const void *restrict, size_t);

memmove(void *, const void *, size_t);

출처:  http://kjs1981.tistory.com/tag/Restrict%20%ED%95%9C%EC%A0%95%EC%96%B4