2008.10.17 17:07 책 정리/Exceptional C++
Pimpl 의 주요 단점은 항당 new 를 이용하여, 생성하고 delete를 이용하여 해제하는 과정을 겪어야 한다는 점이다. 보다 안전하고 보다 빠르게 사용 할수 있을까? ... 아래 3가지 시도를 우선 보도록 하자.

 역시 이 시도1로 하면 Pimpl의 비용을 전혀 내지 않지만, Pimpl 은 쓰지 않으니 .. 한번 사용해 보도록 하자.


 시도 1의 구성을 Pimpl을 이용하여 바꾸었다. 하지만 new/delete에 따른 과부하가 성능을 감소시킨다고 들어 났다. 과감한 프로그래머는 "완전한" 해결책을 내 놓게 된다. 허허허


허허허, 이렇게 사용을 한다.


자 이제 질문이 들어 갈 시간이다.
  1. Pimpl 이디엄의 공간 과부하가 무엇인가?
  2. Pimpl 이디엄의 성능 과부하가 무엇인가?
  3. 시도 #3에 대해 논의하고, 과부하를 극복하는 더 좋은 방법이 있는가?

분석

모든 Pimpl 이디엄은 Pimpl 개체를 가리키기 위한  최소한 1개의 포인터를 요구하며, 나아가 "후진 포인터"를 사용하는 Pimpl 이라면, 또 포인터 1개가 요구 되어 진다. 이러한 포인터는 플랫폼마다 4Byte 또는 8Byte를 더하게 되고, 구조체 혹은 클래스의 바이트 정렬시 14바이트 혹은 그 이상이 추가 될 수도 있다.

이 공간 과부하를 없앨 순 없지만, 최소화 하는 방법으로 #pragma pack(N) 등으로 컴파일러에게 바이트 정렬시 N Byte 기준으로 정렬하라고 명령 내릴 수 있다.

하지만 이 방법은 공간 과부하는 줄일 수 있지만, 몇가지 주의 사항이 있으므로, 참고하기 구글에서 "바이트 정렬" 이라고 검색해 보길 바란다.

가이드 라인 : 프로파일러나 다른 도구들이 해도 된다고 알려주기 전까지는 최적화 하지 마라.(공간 과부하를 갖는 것이 더 편할 때가 종종 있다.)

 성능 상으로 과부하를 갖는데, 첫째, Pimpl 개체의 메모리 할당/해제를 반드시 해야 하고, 둘째, Pimpl 모체 클래스의 보이는 함수를 호출해야만 할때, "후진 포인터"를 사용해 호출 해야 하는 부분이다. (둘째는 그리 많은 과부하가 안 걸리겠지만 new 와 delete 는 많은 과부하가 걸릴 것이다.)

정확하게 말해서 이렇게 사용 하면 절대로 안된다. 비유하자면, 부서지기 쉬운 유리잔 같다. 그 이유를 들어 보면 new / malloc을 사용하여 동적으로 할당되는 메모리는 어떤 형식의 개체에 대해서도 적절히그 만큼의 양을 보장한다.[각주:1]

하지만 동적으로 할당 되지 않았던 char[sizeofx] 의 경우, 그런 보장이 없기 때문이다.

다음 코드를 보자.

 이 코드의 주석을 잘 보면, 위험할 수 있는 곳을 표시해 두었다. 그렇다 고정길이 배열을 스택에 올리고, 그곳에 pacement(위치지정) new를 사용했을 경우, 오류가 일어 날 수 있다는 것이다.


오류 이유는 더 작을 수 있기 때문이다. 그래서 몇가지 http://www.kldp.org[각주:2]질문[각주:3]을 올렸고, 답변을 얻을 수 있었다.  하지만, 왜 더 작을 수 있는지에 대한 이야기는 듣지 못해서 다시 질문[각주:4]을 올렸다.

= 답변을 얻었다.

추가 2008-10-19 : 17:19
KLDP http://kldp.org/node/98975 질문의 답변을 기록해 둔다.

BUS error 는 보통 align이 안 맞았을 때 발생합니다.

링크된 글의 링크된 글을 보면 나와 있습니다만, 결국 align 문제인듯 합니다.

일반적인 CPU 에서는 4byte 자료구조체 (int 등)는 주소값이 4byte로 align 되어 있어야 합니다. 8byte 자료 구조체는 8byte 주소로 align이 되어 있어야 하지요.

따라서 일반 변수 선언을 하거나 할 때엔 compiler 가 알아서 align된 주소로 할당해 줍니다. malloc 등과 같은 함수는 아예 8byte align된 주소를 돌려주도록 되어 있구요. (할당된 memory가 어떤 type으로 사용될 지 모르므로 무조건 최대 크기로 align된 주소를 돌려줍니다.)
그런데 위와 같이 char 형식으로 stack 변수를 선언하면, char type 은 1byte align만 맞으면 되기 때문에 align이 안 맞는 주소를 돌려받을 확률이 매우 높습니다.
그런 주소를 다른 type으로 형변환해서 사용하면 CPU에 따라 align이 안 맞는다고 BUS error 가 발생하는 것이지요.

CPU 에 따라 다르다는 것은. 우리가 많이 사용하는 X86 에서는 기본적으로 align이 안 맞아도 BUS error를 안 내기 때문입니다. (설정하면 bus error를 낼 수도 있습니다만, 그렇게하면 OS 부팅조차 안 될 것입니다. -ㅅ-) 하지만 그 외 대부분의 CPU 에서는 align이 안 맞으면 BUS error를 내게 됩니다.

=

즉, 8 Byte 정렬하여 힙에 올라가기 때문에, 힙에 올라간 데이터는 더 커질 수 있다. 더 커질 수 있기에, 포인터 접근에 대한 보장을 해 주지만, 스택에 올라간 1Byte 정렬의 배열일 경우, 정렬에 따른 포인터 접근을 보장해 주지 못한다.  이것 때문에 I/O Bus 에러를 벹어 낼 수 있다. 그러니 "사용하지 마라"로 요약 된다.[각주:5]


표준적인 방법으로는 "할당자"를 이용하여, 할당 한다면, "Fast Pimpl"을 최종 결과라고 할 수 있겠다.(할당자 부분을 이해 할수 없었기 때문에, 코드에 포함하지 않는다. 좋은 예제가 있는 분들은 .. 리플로 알려 주시면 정말 감사하겠습니다.

할당자에 대한 이야기는 .. 현재 나도 알아가고 있는 중이고, 관련 서적으로는 

C++ STANDARD LIBRARY
카테고리 컴퓨터/인터넷
지은이 니콜라이 M.조슈티스 (정보문화사, 2007년)
상세보기

이펙티브 STL(Effective STL) 상세보기

에 할당자 구현에 대해서 배우고 만들어 볼 법 하다.


총평

할당자를 알아 두어야 겠구나...

  1. 이것은 기본 operator new는 객체의 크기와 같거나 더 크게 잡아 준다. Effective C++ 2판 Memroy 부분 참조 [본문으로]
  2. http://www.devpia.com 보단 이곳에 더 질문을 올리는 편이다. 예전부터 써오던 곳이라... [본문으로]
  3. http://kldp.org/node/98951 [본문으로]
  4. http://kldp.org/node/98975 [본문으로]
  5. 콘솔기나 기타 프로세서에 돌아가다록 제작된 곳에서는 .. 큰일 날 것으로 보인다. 큰일 날 뻔했다. ㄷㄷㄷ [본문으로]
posted by 농사를 짓는 게임 프로그래머 최익필

댓글을 달아 주세요