[펌] 아키텍처 체크 리스트

1. 아키텍처의 개요와 설명을 포함해서 프로그램의 구조가 명확한가?
2. 기능과 다른 모듈과의 인터페이스를 고려해서 모듈이 잘 정의되었나?
3. 모든 기능들이 너무 많거나 적지않은 모듈에 의해 적절히 수행되는가?
4. 설계된 아키텍처가 변화에 적절히 대응할 수 있는가?
5. 구매와 개발에 대한 비교와 결정을 포함하는가?
6. 아키텍처가 다른 목적을 위해 만들어진 코드를 재사용할지를 기술하는가?
7. 중요 데이터 구조가 기술되고 확인되었는가?
8. 중요 데이터 구조가 루틴 액서스 뒤에 은폐되었는가?
9. 데이터베이스 구조와 내용은 설명되었나?
10. 중요 알고리즘이 기술되고 확인됐는가?
11. 중요 대상들이 기술된 것을 확인했는가?
12. 사용자 입력을 처리하는 방법이 기술되었나?
13. 사용자 인터페이스의 중요관점이 정의 되었나?
14. 변경시 프로그램의 다른부분에 영향을 끼치지 않게 사용자 인터페이스가 모듈화되었는가?
15. 메모리관리에 대한 메모리 사용량의 예측과 방법이 기술되고 확인됐는가?
16. 아키텍처는 각 모듈의 용량과 속도를 설정했는가?
17. 문자열 처리에 대해 기술했으며 문자-문자열-저장량에 대해 평가했는가?
18. 에러처리 방법에 대해 기술하는가?
19. 에러메시지는 명확한 사용자 인터페이스를 나타내기 위한 부분으로 처리되는가?
20. 견고성의 정도를 설명하는가?
21. 정도에 지나치거나 부족하게 설계되었나? 이 부분에 대해 명확하게 설명하는가?
22. 시스템의 중요목표가 명확하게 기술되었나?
23. 전체 아키텍처가 개념적으로 일치되었나?
24. 기본 설계가 앞으로 구현될 기종과 언어에 독립적으로 쓰여졌는가?
25. 모든 중요 결정에 대한 동기가 명시되었는가?
26. 시스템을 구현할 프로그래머로써 당신은 아키텍처에 만족하는가?

출처: Steve McConnell의 Code Complete(MS Press 1995)

이 체크 리스트를 읽은 후, 실제 Code Complete 책을 읽고 내가 느낀 아키텍쳐를 세가지로 나타낸다면 다음과 같다.

  1. 아키텍쳐는 산출물이기 이전에 실무 개발자가 코딩하는 매 순간 순간 마다 참고하고, 참고 해야만 하는 유연한(때로 아키텍쳐도 수정되어야 한다) 성서이여야 한다.
  2. 아키텍쳐는 DBMS와 Web Server 그리고 방화벽 등의 시스템의 구성도와 같은 A2 용지 크기의 한장짜리 문서를 포함해서, 시스템을 개발하기 위해 고려해야할 사항을 세부적으로 기술하거나 묘술하는 것이다.
  3. 아키텍쳐에서 어떤 선택 사항에 대한 이유가 반드시 있어야 한다. “예전에 그랬으므로..” 또는 “다른 시스템도 그랬으므로..” 라는 것은 이유가 될 수 없다.

ExampleApplication의 개념

ExampleApplication 클래스는 Ogre에서 기본적으로 제공하는 Framework 클래스이다. 이 클래스가 Framework가 될 수 있는 이유는 이 클래스를 상속 받아 가상함수를 구현하면 하나의 3D 어플리케이션이 만들어지기 때문이다. ExampleApplication 클래스의 구조와 흐름을 파악한다면, Ogre의 전체적인 흐름까지 쉽게 파악할 수 있을 것이므로 Ogre의 첫번째 분석에 ExampleApplication에 대해서 집중적으로 살펴보았다. 아래는 ExampleApplication의 클래스 관계도이다. 매우 심플한 구조인것을 알 수 있다. 심플하다는 것은 확장성이 뛰어나다는 것을 의미하며 또 그만큼 견고하다는 의미라고 생각된다. (너무 Ogre를 좋게만 보는거 아닌가 싶다)

위의 클래스 관계도에서 파악할 수 있는 몇가지를 살펴보면, ExampleApplication 클래스는 RenderWindow, Root, SceneManager, FrameListener, Camera를 속성맴버로 갖고 있다. (SceneNode는 아니다) 생성과 소멸에 관한 Composition 관계는 ExampleApplication은 Root와 FrameListener를 생성하며 Root가 RenderWindow와 SceneManager를 생성한다. 그리고 Camera는 SceneManager가 생성하여 관리한다. 일단 여기서 가장 중요한 FrameListener와 SceneManager에 대해 간단히 설명하면, FrameListener는 사용자의 Interaction에 대해 렌더링될 각 Frame을 컨트롤하는 역활을 한다. 그리고 SceneManager는 화면상에 렌더링될 SceneNode 들을 관리해준다.

SceneNode는 직접적으로 ExampleApplication과는 관계가 없지만 Ogre에서 매우 중요한 개념(대부분의 3D엔진에서 중요한 용어이며 개념이다)이다. Ogre에서 SceneNode를 말하려면 Entity도 함께 언급을 해야한다. Entity는 3차원 Geometry 좌표로 구성한 Mesh라고 할 수 있는데, 이 Entity가 SceneNode에 붙게된다. SceneNode는 위치와 방향 값을 갖고 있으므로 Entity와 SceneNode가 붙게되면 특정 위치와 방향으로 놓이게 되는 물체가 정의되는 것이다. 여기서 중요한 것은 하나의 SceneNode에는 여러개의 Entity가 붙을 수 있다는 점이다. 참고로 빛(Light)도 SceneNode에 붙일 수 있다.

관계도에서 하나 더 알 수 있는 것은 ExampleApplication의 모든 맴버 함수는 추상함수라는 점이다. 즉, 앞서 언급한것처럼 ExampleApplication을 상속받아 추상함수를 구현하게 되면 하나의 어플리케이션이 만들어진다는 것이다.

이제 ExampleApplication의 추상함수들의 용도를 살펴보면, 유일한 공개(public) 함수인 go()와 보호(protected) 접근자 함수인 그외의 함수들로 구성되며 그 목적은 다음과 같다.

  • go – WinMain이나 main, 또는 Rendering Thread에서 직접 호출되며 SceneNode 등과 같은 것을 화면상에 렌더링 한다. 내부적으로 setup 함수를 호출하고 조건무한루프 함수인 Root의 startRendering 함수를 실행한 뒤, destroyScene 함수를 호출한뒤, 일반적으로 어플리케이션이 종료된다.
  • setup – 가장 많은 일을 하는 함수로, 리소스 파일을 읽어들이고 Root 클래스를 초기화하며 Root 클래스를 통해 SceneManager를 생성시킨다. 그외에 카메라, 뷰포트, SceneNode를 생성을 담당하는 함수등을 차례로 호출한다.
  • destroyScene – 어플리케이션이 종료되기 직전에 더 이상 필요하지 않을 SceneNode를 메모리에서 제거한다.
  • setupResources – 리소스(이미지, Mesh, 스크립트 등)의 경로를 외부 cfg로부터 읽어들여 추후에 읽어들일 리소스의 경로를 파악한다.
  • configure – 사용자에게 3D API로 OpenGL이나 Direct3D를 쓸것인지와 해상도, 전체화면여부등을 결정할 수 있도록 하고, 결정된 사항을 기반으로 3D 환경을 설정한다.
  • chooseSceneManager – sceneManager를 생성한다.
  • createCamera – 카메라 생성
  • createViewport – Viewport 생성. Viewport는 실제 렌더링될 창(Window)의 배경색 및 픽셀 너비와 높이 값등을 가지고 있다.
  • createResourceListener – 리소스를 실제로 Loading할때 사용자에게 현재 얼마만큼의 리소스가 Loading되었는지를 파악할 수 있는 Listener 클래스를 생성한다.
  • loadResources – 실제 리소스를 Loading 한다.
  • createScene – 다른 추상 함수와는 달리 순수 추상함수로써 화면상에 렌더링할 SceneNode를 생성한다.
  • createFrameListener – 사용자의 Interaction을 받거나 그외의 다른 이유를 통해 변경된 사항을 매 Frame에 반영할 수 있도록하는 Listener를 생성한다. Ogre를 분석때 처음에 ExampleApplication을 분석하는 것처럼 다음에는 ExampleFrameListener를 분석해 볼것이다. 일단 여기서는 간단이 앞서 언급한 내용만 알아도 충분할 것같다.

일단 ExampleApplication에 대한 개념적인 것을 정리했다. 다음에는 실제로 ExampleApplication을 상속받아 화면상에 하나의 모델을 나타내는 예제를 만들어보는 것으로 해서 ExampleApplication에 대한 정리를 마루리할 것이다.

여기서 ExampleApplication의 이름을 ExampleApplication이라고 정의했는지 생각해 볼 필요가 있을 것 같다. ExampleApplication은 단지 Ogre에서 최소한 실행되는 3D 프로그램이 갖추어야할 것들만을 가지고 있는 예제에 불과한것같다. 즉, 처음 Ogre에 접근하는 개발자들에게 ExampleApplication을 상속받아, 얼마나 쉽게 3D 프로그램을 개발할 수 있는지를 보여주기 위한 Example에 불과한것이 아닌가 싶다. 물론 예상일뿐이지만 말이다.

시작하며…

OGRE는 Object-Oriented Graphics Rendering Engine의 약자로써, 이미 아시는 분들은 저보다 훨씬 잘아시리라 믿습니다. 3D를 주 테마로 잡고 있는 개인이 운영하는 사이트를 보면 OGRE를 강한 열정을 가지고 분석하고 있는 분들이 많더군요. 하지만 저는 단지 수개월전에 OGRE를 공개소스로써 3D 그래픽 엔진 중 인기순위 1위를 차지하는 라이브러리 정도로만 알고 있었고, 그 당시 한번 컴파일이나 해보자는 마음으로 소스코드를 다운로드 받아 컴파일을 시도했지만, 실패하고 말았었습니다. 왜 실패했는지 그 이유는 모르겠습니다. 아마도 제 마음속에 별.. 그다지 OGRE에 대한 관심이 별로 없었지 않았나 싶습니다. 하지만 다행이 이미 컴파일된 데모들을 실행해 볼 수는 있었고, 단지 “좋군..”이 전부였습니다.


하지만 최근 우연이 Direct3D를 전문으로 다루는 어느 개인 블로그에 방문했데, 그 곳에 OGRE를 다루더군요. 또 다시 호기심이 발동한 저는 다시한번 컴파일이나 해볼 요량으로  OGRE의 공식 사이트인 www.ogre3d.org에 방문해 차근 차근 시도를 해보았습니다. 그런데 이번엔 왠일인지 쉽게 컴파일이 되더군요. 이상하게도 컴파일이 되니 제 마음속에서 이 놈을 깊이 있게 분석해보리라는 강한 욕심이 생겼습니다. 정말입니다. 컴파일 성공이라는 첫걸음이 성공하는 순간, 저도 전혀 예상하지 못했던 흥분과 욕심이 생기는겁니다!

제가 OGRE에서 얻고자 하는 바는 다음과 같습니다.

  • 많은 3D 그래픽 개발자로부터 찬사를 받고 있는 OGRE를 직접 사용해보고…
  • 최고 수준의 Object-Oriented를 지향한다는 Ogre의 개발자의 이 엔진으로부터 설계방법을  배우며…
  • OpenGL이나 DirectX보다 상위 수준의 API 통해 최신 그래픽 기술을 직접 코딩해 느껴보고, 점차 하위 수준으로 접근해 나간다.

위의 3가지 목적중에 2번째인 설계방법이 가장 큰 목표입니다. OGRE의 첫자인 “O”가 Object-Oriented인 만큼, 또한 OS 독립적이며 OpenGL과 Direct3D를 실행중에 유연하게 선택할 수 있는 설계방식이 무척 궁금합니다. 물론 제 나름대로의 이러저러하게 하면 되지 않을까 하는 다소 어설픈 아이디어는 있지만, 다른 개발자의 아이디어는 어떨까, 배우고 싶습니다.
위는 Ogre의 핵심 클래스의 관계도입니다. 간단해 보이기는 하지만 다른 부수적인 것들이 붙으면 매우 복잡하겠지요. Ogre를 분석하는 방법은 Ogre 사이트에서 제공하는 Tutorial과 Demo 소스를 분석하는 방식으로 하려 합니다. 제 스스로에게 건투를 빕니다.

Memory Leak 검출해 주는 오픈소스

예전에 VC6.0을 사용할때 코딩하고 Debug로 컴파일하면 어느 지점에서 메모리 누수(Leak)가 발생하는지 IDE의 출력창에서 알려주었다. 원래 VC6.0이 그랬는지, 아니면 나도 모르게 뭔가를 설치해서 그랬는지는 기억나지 않지만 말이다. 그런데 VS2003 이후로는 메모리 누수에 대한 보고를 해주지 않는 것이다. 난 한동안 아.. 메모리 누수가 발생하지 않게 잘했나보다 했는데.. 그게 아니란것을 알고 난후, 메모리 누수를 검출해주는 툴을 찾다가 괜찬은 녀석 둘을 만났다.

http://www.codeproject.com/tools/visualleakdetector.asp
http://www.codeproject.com/tools/leakfinder.asp

간단히 소개를 하면, 첫번째는 라이브러리를 Link해주고 vld.h라는 헤더파일 하면 include 해주면 메모리 누수 검출의 준비가 끝난다. 누수의 결과는 IDE 창에서 해주게 되는데 아래의 코드를 작성하고 컴파일하면 IDE의 출력창에 누수의 지점과 내용을 출력해준다.

#include "vld.h"

int main()
{
    char *pChar = new char[100];

    return 0;
}

이것은 필자가 VS6.0에서 경험했던 바로 그것이였다. 그러나 단점은 프로젝트의 코드생성이 다중스레드DLL이냐, 그냥 다중스레드냐에 따라 다른 라이브러리를 링크해줘야 한다는 것과 CRT 메모리에 대한 누수만을 검출해주며 COM 메모리 누수는 검출해주지 못한다. 즉, COM군의 함수중에 메모리를 할당해주는 CoTaskMemAlloc 함수에 대한 메모리 누수는 검출해주지 못하는 것이다. 그러나 IDE와 연동되어 IDE의 출력창에서 누수에 대한 정보를 더블클릭하면 누수지점의 코드로 커서가 이동하는 기능은 상당히 매력적이다.

끝으로 두번째는. 하나의 헤더파일과 그에 대한 하나의 소스파일로 구성되는 간단한 구조인데, StackWalker.h 파일을 include 해주고 누수검출 시작과 끝을 지정하는 함수를 호출해줌으로써 메모리 누수 검출의 준비가 끝나게 되어 무척 사용하기가 쉽다. 게다가 CRT 메모리 뿐아니라 COM 메모리에 대한 누수검출까지 가능하다. 또한 메모리 누수에 대한 정보는 3가지로 구분되는데, 간단한 SImple 모드, 좀더 자세한 Advanced 모드, XML 모드이다. 모두 외부 파일을 통해 누수 정보를 개발자에게 Report한다. 하지만 첫번째 소개했던 누수툴과는 달리 IDE와 연동을 지원해주지 않는다는 단점이 있다.

플렛폼이 Windows라는 범위에만 초점을 맞춰볼적에 비록, 최근의 추세가 Garbage Collection에 의한 자동 메모리 정리 기능이 있어, 메모리 누수에 대한 염려가 많이 줄기는 하였지만, .NET에서 지원하는 C++/CLI는 CLR 메모리만이 아니라, CRT, CLR, Stack 메모리에 까지 접근이 가능하므로 메모리 누수에 대해 주의를 요한다는 점에서 아직까지 이러한 메모리 누수 검출 도구는 유용하다고 하겠다.

[참고] 아래 글의 “줘도 못먹는”에 해당하는 오프소스가 아닙니다.

놀고 있는 비디오 메모리 활용

비디오 카드의 메모리의 용량은 적게는 32M에서 256M 정도 된다. 흔히 128M 정도의 카드가 많이 사용된다. 이 많은 비디오 카드의 메모리는 렌더링의 속도를 향상 시키기 위해서 3차원 그래픽 프로그램에서 사용된다. 하지만 3차원 그래픽 프로그램이 구동되지 않을 경우 대부분의 비디오 카드 메모리는 사용되지 않고 낭비된다. 예를들어 필자의 경우 해상도 1280×1024에서 32비트 색상을 사용하고 비디오 카드의 메모리는 128M이므로 128M에서 약 5M를 뺀 약 120M 정도의 비디오 메모리가 놀고 있는 셈이다.

그렇다면 이렇게 낭비되는 비디오 카드의 메모리를 3D 그래픽의 사용 목적이 아닌 일반 메모리처럼 사용할 수는 없을까? 게다가 비디오 카드의 메모리는 일반 메모리에 비해서 그 속도가 훨씬더 빠르다는 장점이 있지 않은가?

비디오 메모리를 사용하기 위해서는 당연히 비디오 메모리에 접근할 수 있어야 한다. 비디오 메모리에 접근하기 위한 API가 필요한데, 접근을 위한 특별한 API 라이브러리를 사용하는게 아니라 OpenGL과 DirectX의 API를 사용하면 되겠다. 여기서 필자에게 익숙한 OpenGL API를 사용하도록 하겠다.

비디오 메모리와 관련된 OpenGL API는 다음과 같다.

– glBindBuffer
– glBufferData
– glBufferSubData
– glDeleteBuffers
– glGenBuffers
– glMapBuffer
– glUnmapBuffer

위의 API는 ARB 군에 속해 있다가 OpenGL 1.5부터는 표준으로 안착되었다. 즉, OpenGL 1.5 이전에는 위의 함수명에다 접미사격으로 ARB를 붙여 사용해야 한다. 예를 들어서 glBindBuffer의 경우 glBindBufferARB 처럼 말이다. 물론 ARB의 경우는 Extension이므로 지원하는지의 여부를 먼저 확인해야 할 것이다. 확인을 위한 문자값은 GL_ARB_vertex_buffer_object이다.

각 함수의 내용에 대한 설명은 생략하고 사용하는 방법에 대해서만 살펴보겠다. 먼저 OpenGL을 초기화하고 현재 그래픽 카드가 지원하고 있는 OpenGL의 버전이 1.5 이상인지를 확인해야 한다. 여기서 한가지 문제점이 있는데, 그것은 바로 OpenGL의 초기화이다. OpenGL을 초기화하지 않으면 해당 API의 진입점을 찾지 못한다. 하지만 3D 기능을 사용하지도 않을 것인데, OpenGL을 초기화하해야한다는 것은 어쩌면 큰 부담이다. 특히 Console 프로그래밍과 같은 경우 렌더링 창에 대한 DC를 얻어와야 OpenGL을 초기화할 수 있는데, 이런 경우에 필자는 바탕화면의 DC를 사용하였다. 초기화를 위해 바탕화면의 DC를 사용하였을뿐, 실제로 무엇인가 렌더링하지는 않았기때문에 별 문제는 없으리라 본다.

어찌되었든 OpenGL을 초기화하고 OpenGL의 버전이 1.5 이상인 경우 위의 API의 진입점 포인트를 성공적으로 얻어왔다면 이제 사용하는 일만 남았다.

사용하는 방법은 정말 간단하다. 일반 메모리의 포인터를 얻어와 사용하는 방법과 유사하기 때문이다.

GLuint bufferID;

glGenBuffers(1, &bufferID);
glBindBuffer(GL_ARRAY_BUFFER, bufferID);
glBufferData(GL_ARRAY_BUFFER, sizeof(BYTE)*allocMemSize, NULL, GL_STATIC_DRAW);
BYTE *pBuffer = (BYTE *)glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE);

먼저 glGenBuffers를 사용하여 비디오 카드 메모리 버퍼의 ID를 생성하고 이 메모리 버퍼를 GL_ARRAY_BUFFER와 묶기 위해 glBindBuffer 함수를 사용한다. 그 후에 glBufferData를 사용하여 실제 비디오 메모리를 원하는 크기만큼 할당하는데 이 경우 가장 빠른 속도를 위해 GL_STATIC_DRAW를 사용하였다. 할당할 메모리의 크기는 자신이 사용하는 비디오 카드의 메모리의 크기를 잘 고려해서 지정하기 바란다. 여기까지 성공하였다면 할당 받은 비디오 카드의 메모리를 일반 메모리의 포인터처럼 사용하기 위해 메모리 주소값으로 Mapping 시키기 위해 glMapBuffer함수를 사용하여 포인터 주소를 얻어 온다. 바로 이 포인터 주소를 사용해서 일반 메모리와 똑같이 사용할 수 있는 것이다.

for(size_t i=0; i

위의 코드는 실제로 비디오 카드의 메모리를 마치 일반 포인터의 개념처럼 사용하는 예를 든 것이다.

이제는 이렇게 사용한 비디오 카드의 메모리를 해제시키는 방법은 다음 코드와 같다.

glUnmapBuffer(GL_ARRAY_BUFFER);
glDeleteBuffers(1, &bufferID);

필자가 시험삼아 10M의 메모리를 비디오 카드에 할당해 사용하는 방법과 일반 메모리에 new 연산자를 이용해 힙에 할당해서 사용하는 방법의 속도 차이를 비교해본 결과 약 3배정도의 속도 향상이 있음을 발견했다.

끝으로 이런 비디오 카드의 메모리의 속도의 장점을 잘활용한다면 빠른 속도를 요구하는 코드에 더욱 좋은 성능 향상을 꾀할 수 있을 것이다. 또한 비디오 메모리의 경우 일반 메모리에 비해 외부의 어플리케이션으로부터의 메모리 해킹이 어렵기 때문에 보안적인 면에서도 좋은 이점이 있을 것으로 생각된다.