boost::intrusive_ptr<T>는 boost 패키지에서 제공하는 또 다른 형태의 Smart Pointer입니다.
이전 글에서 설명한 boost::shared_ptr<T>는 smart pointer가 스스로 reference count를 관리했지만,
intrusive_ptr<T>는 reference count 관리를 타겟 메모리 객체에 맡깁니다. 즉, Reference count는
메모리 관리의 대상이 되는 객체에서 직접하고, intrusive_ptr<T>는 그 객체가 제공하는 Reference
count 기능을 빌려쓰는 겁니다. 그러면 메모리 객체와 intrusive_ptr<T> 사이를 연결해 주는
매개체가 있어야 겠죠. intrusive_ptr<T>는 다음의 두 함수를 통해 메모리 객체를 관리합니다.

void    intrusive_ptr_add_ref(Object *a_pObject) 
void    intrusive_ptr_release(Object *a_pObject)

예를 하나 들어보죠.
아래 클래스는 관리의 대상이 되는 Object를 정의합니다. 
보시면 Reference Count 인터페이스가 정의되어 있습니다.

class Object 
{
public:
// Constructor.
Object(void) : m_iRefCount(0) {};
virtual ~Object(void) {};

// Ref count manager.
void    inc_ref(void) { m_iRefCount++; };
void    dec_ref(void) { m_iRefCount--; };
void    get_ref(void) { return m_iRefCount; };

private:
int      m_iRefCount;
}

boost::intrusive_ptr<T>는 이전에 설명한 shared_ptr<T>와 동일한 방식으로 사용합니다.

{
boost::intrusive_ptr<Object>       sp1 = boost_intrusive_ptr<Object>(new Object);
boost::intrusive_ptr<Object>       sp2;

sp2 = sp1;
...
}

그리고 intrusive_ptr<T>와 Object를 연결하는 두 함수는 다음과 같이 제공되어야 합니다.

void     intrusive_ptr_add_ref(Object *a_pObject)
{
a_pObject->inc_ref();
}

void     intrusive_ptr_release(Object *a_pObject)
{
a_pObject->dec_ref();
if( a_pObject->get_ref() <= 0 )
delete a_pObject;
}

boost::intrusive_ptr<T>는 타겟 객체에 Reference count를 증가시킬 때, intrusive_ptr_add_ref()
함수를 호출하고, Reference count를 감소시킬 때 intrusive_ptr_release()를 호출합니다. 따라서
이 두 함수를 사용자가 제공해 주지 않으면 링크 에러가 발생하게 됩니다.

intrusive_ptr<T>는 어떤 경우에 유용할까요?

1. 기존 코드에 smart pointer를 입히는 경우입니다. 이미 예전 코드에 Reference count
   메커니즘이 적용되어 있는 경우, intrusive_ptr<T>을 이용하여 쉽게 코드를 refactoring
   할 수 있습니다.

2. Object가 자기 자신(this)에 대한 smart pointer를 리턴해야 하는 경우입니다.
    shared_ptr<T>는 이 상황을 처리하지 못합니다. (이전 글 참조)

3. ...

boost 패키지 문서에서는 가급적이면 shared_ptr<T>를 사용하라고 권고하고 있습니다.
하지만, 위에서 나열한 경우라면 intrusive_ptr<T>를 사용하는 것이 좀 더 나을 수 있습니다.
다음과 같은 코드에서 Smart Pointer를 사용할 수 있을까?

class  Object
{
public:
      boost::shared_ptr<Object>  methodA(int value)
      {
           if(value == 0 )
               return this;  <-- 문제 코드.
           else
               return member;
       }

private:
       boost::shared_ptr<Object>    member;
}

위 코드에서 methodA(value)는 입력 매개 변수값에 따라 자기 자신을 리턴하거나, 
아니면 자기가 가지고 있는 member의 값을 리턴한다. member도 smart pointer로 
정의되어 있죠.

자, 이 경우 member를 리턴하는 것은 별 문제가 안됩니다. 
문제가 되는 경우는 자기 자신을 리턴해야 하는 경우입니다.

{
       boost::shared_ptr<Object>   pObj( new Object ), pObj2;
       
       pObj2 = pObj->methodA(0);
}

case 1. methodA()에서 컴파일 에러를 막기 위해서 boost::shared_ptr<Object>(this) 값을 
           리턴하는 경우를 생각해봅시다. 이렇게 되면, 위 App code에서 pObj, pObj2가 
           같은 오브젝트를 가리키는 서로 다른 2개의 smart pointer가 됩니다. 따라서
           메모리 객체가 2번 지워지는 에러가 발생합니다.

case 2. 그렇다고 "this"를 그대로 리턴할 수도 없습니다. 컴파일 에러가 나니까요.

case 3. Object가 스스로를 가리키는 smart pointer "self"를 가지게 하고, 이 값을 methodA()를
           통해서 리턴할 수도 있습니다. 하지만 이렇게 되면 해당 오브젝트는 자기 순환 참조를 하게
           되어 메모리 릭으로 남습니다. (use_count가 0이 될 수가 없습니다.)

이 문제를 해결하기 위해서 이리저리 자료를 찾아보고, 고민을 해봤는데, 
결론은 "shared_ptr<T>은 사용할 수 없다" 입니다.

대신, boost::intrusive_ptr<T>을 사용하면 이 문제를 해결할 수 있습니다.
intrusive_ptr<T>는 기본적으로 shared_ptr<T>와 사용법은 비슷하나 Reference Count를
메모리 객체가 직접 관리해야 하는 점이 다릅니다. shared_ptr<T>의 경우, 포인터를 감싸는
별도의 구조체를 자동으로 생성하고 거기에서 reference count를 관리하는데, intrusive_ptr<T>은
Object가 제공하는 Reference Counter 인터페이스를 이용합니다. 
-> intrusive_ptr<T>에 대한 설명은 따로 해야할 듯 합니다. 양이 만만치 않을듯..
모바일 환경에서 프로그램을 작성하면서 가장 신경 쓰이는 문제가 바로 메모리 관리입니다.
언제 할당하고, 언제 해제해야 할 지를 깊이 생각해서 프로그램을 작성하지 않으면 
메모리 누수가 생기거나, 잘못된 주소를 참조했다고 에러가 발생하죠.
게다가 모바일 환경에서는 메모리 할당 실패가 발생할 확률이 매우 높습니다.
(PC환경에서는 거의 발생하지 않기 때문에 PC에서 잘 수행되는 프로그램이 모바일 환경에서
 죽는 경우가 종종 발생합니다.)

boost 라이브러리는 프로그래머가 메모리 관리를 편하게 할 수 있도록 여러가지 pointer wrapper
클래스들을 제공합니다. 그 중에서 특히 shared_ptr<T>이 유용하기 때문에 이놈을 여기에서 
설명합니다.

boost::shared_ptr<T> 클래스는 일반 C++ 포인터를 사용하는 것과 동일하게 사용합니다.
예를 들면, 

#include <boost/smart_ptr.hpp>

boost::shared_ptr<Object>     p(new Object());

p->methodA();   // methodA는 Object의 메소드.
(*p).methodA(); // 위 코드와 동일.

위 예제를 보면, Object 타입의 객체가 하나 생성되고, shared_ptr 오브젝트인 p가 그 오브젝트를
가리킵니다. 사용법은 일반 포인터와 같다는 것을 알 수 있습니다.

boost::shared_ptr<T>는 Reference Counting 메커니즘을 이용해서 메모리 객체를 관리합니다.
즉, 메모리 객체 하나에 대해서 몇 개의 shared_ptr들이 그 객체를 가리키는지 관리하는 겁니다.
shared_ptr 내부 구현을 살펴보면, 객체를 가리키는 포인터 변수와 그 포인터 변수를 감싸는
내부 데이터 구조를 볼 수 있습니다. 이 내부 데이터 구조에는 use_count 변수가 있어서 
몇 개의 shared_ptr이 특정 메모리 객체를 가리키는지 관리합니다.
예를 들어보죠.

#include <boost/smart_ptr.hpp>
{
boost::shared_ptr<Object>     p(new Object());
boost::shared_ptr<Object>     q;
boost::shared_ptr<Object>     r;

// 1. 최초 상태에서 p는 Object 객체를 가리키며, use_count = 1 입니다.
//    q, r은 null 객체를 가리키며 use_count = 0 입니다.

q = p;
// p가 가리키는 Object를 q도 가리키게 됩니다. use_count = 2 입니다.
// r은 여전히 null을 가리키고, use_count = 0 입니다.

r = q;
// 이제 r도 p, q와 같은 오브젝트를 가리키게 됩니다. use_count = 3입니다.

// 이제 하나하나 지워봅시다.
r.reset();
// 이제 r은 더 이상 p, q와 같은 오브젝트를 가리키지 않습니다. use_count = 2로 감소합니다.
// 하지만 p, q가 여전히 오브젝트를 가리키고 있기 때문에 오브젝트가 지워지지는 않습니다.

q.reset();
// q도 더 이상 메모리 객체를 가리키지 않습니다. use_count = 1이 됩니다.

r.reset();
// 메모리 객체에 대한 참조가 모두 사라지면서, use_count = 0 이 됩니다.
// 이 시점에서 실제 메모리 객체가 삭제됩니다.
}

물론 위 예제와 같이 reset() 코드를 불러줄 필요는 없습니다. (사실 이게 중요한 점이죠)
왜냐하면 shared_ptr들이 삭제될 때, 파괴자가 불리게 되는데 이 때 Reference Count가 감소하게
됩니다. 위 예제에서는 마지막 "}"를 벗어나는 순간 p, q, r이 모두 삭제되고, 생성된 메모리 객체에
대한 참조가 모두 사라지기 때문에 자동으로 메모리 객체가 삭제됩니다. 즉, 프로그래머가 메모리
관리에 대해 신경을 좀 덜 써도 된다는 뜻이죠.

물론 boost::shared_ptr<T> 클래스가 모든 문제를 해결해주는 것은 아닙니다.
Reference Counting 방식은 Object간 순환 참조가 발생하면 무용지물이 됩니다.
(순환 참조를 없애기 위해서는 weak_ptr을 사용하거나, 코드에 Trace Garbage Collector를
  적용해야 합니다. 이 부분은 설명하기에 너무 길기 때문에 나중에 따로 설명하도록 하죠.)

여튼 순환 참조시 문제가 왜 발생하는지 봅시다.
다음과 같이 ObjectA -> ObjectB -> ObjectA 순서로 참조가 일어난다고 
가정하고 코드를 작성해 봅시다. 내부 참조는 모두 shared_ptr을 이용합니다.
#include <boost/smart_ptr.hpp>
{
class Object 
{
public:
boost::shared_ptr<Object>    member;
};

boost::shared_ptr<Object>     pObjectA(new Object());    // A 메모리 객체에 대한 참조
boost::shared_ptr<Object>     pObjectB(new Object());    // B 메모리 객체에 대한 참조

// 1. 이 시점에서 pObjectA, pObjectB는 각각 use_count = 1입니다.
//
pObjectA->member = pObjectB;

// 2. 이 시점에서 pObjectB의 use_count = 2가 됩니다. (B가 2번 참조됨)
pObjectB->member = pObjectA;

// 3. 이 시점에서 pObjectA의 use_count = 2가 됩니다. (A가 2번 참조됨)
}

이제 코드 수행이 끝나고, 종료되는 과정을 생각해봅시다.
마지막 "}"를 만나면 pObjectA, pObjectB 객체는 삭제됩니다 (로컬 변수이기 때문이죠.)
pObjectA가 먼저 삭제된다고 해봅시다. 이 때, use_count는 2에서 1로 감소합니다. 하지만 
여전히 use_count = 1이기 때문에 할당된 메모리 객체(A)는 삭제되지 않습니다. 
그 다음 pObjectB가 삭제됩니다. 이 경우에도 use_count가 2에서 1로 감소하기 때문에 
메모리 객체 (B)는 삭제되지 않습니다. 메모리 객체 A가 삭제되려면, 객체 B가 먼저
삭제되어야 합니다. 그런데 B가 삭제되려면 A가 먼저 삭제되어야 합니다.
닭이 먼저냐 달걀이 먼저냐의 문제인 거죠.
결과적으로, A, B 객체는 메모리 누수로 남게 됩니다.

간단히 다이어그램을 그려보면, 
종료 직전: pObjectA -> A <--> B <- pObjectB
종료 직후: A <--> B

트위터 테스트

2011/02/12 14:38
간단한 트위터 연동 테스트....

인재 관리

2007/02/01 17:16

1. 사랑하지 않으면 잃는다. Love them or lose them.


2. 항상 "무슨 곤란한 일은 없는가?"를 물어야 한다.


3. 정말 중요한 고충은 쉽게 말을 꺼내지 않는 경우가 많다.


4. 고충 상담 후 적절한 피드백이 이루어지지 않으면 아예 물어보지 않는 것보다 더 나쁜 결과를 야기할 수 있다.


5. 편애하지 않는다. 공정하게 평가한다.


6. 사람마다 성장/발전 속도가 다르다는 점을 인식한다.


7. "만일 내가 누군가를 좋아하지 않는다면 그것은 그를 더 잘 알아야만 한다는 점을 말해주는 것이다. " 링컨.


8. "돈, 사람, 아이디어는 필요한 곳으로 흘러가 소중히 다뤄지는 곳에 머문다", 조지 월터.


9. "조직의 핵심 인재를 화나게 하지 말라. 왜냐하면 그런 인재가 회사 성과의 대부분을 만들어 내고, 그들을 화나게 하면 조직을 떠나기 때문이다."


10. 돈 때문에 머문 인재라면, 같은 이유로 떠날 가능성도 크다.


11. 자신보다 더 뛰어난 인재를 채용하라.


--* Quoted from "UML 2.0 Tutorial" by Sparx Systems.


UML 2 defines 13 basic diagram types, divided into two general sets:


1. Structural Modeling Diagrams


 Structure diagrams define the static architecture of a model.  They are used to model the 'things' that make up a model - the classes, objects, interfaces and physical components.  In addition they are used to model the relationships and dependencies between elements.


- Package diagrams are used to divide the model into logical containers or 'packages' and describe the interactions between them at a high level
- Class or Structural diagrams  define the basic building blocks of a model: the types, classes and general materials that are used to construct a full model
- Object diagrams show how instances of structural elements are related and used at run-time.
- Composite Structure diagrams provide a means of layering an element's structure and focusing on inner detail, construction and relationships
- Component diagrams are used to model higher level or more complex structures, usually built up from one or more classes, and providing a well defined interface
- Deployment diagrams show the physical disposition of significant artefacts within a real-world setting.


2. Behavioral Modeling Diagrams

 

   Behavior diagrams capture the varieties of interaction and instantaneous state within a model as it 'executes' over time. 


- Use Case diagrams  are used to model user/system interactions. They define behavior, requirements and constraints in the form of scripts or scenarios
- Activity diagrams  have a wide number of uses, from defining basic program flow, to capturing the decision points and actions within any generalized process
- State Machine diagrams  are essential to understanding the instant to intant  condition or "run state" of a model when it executes
- Communication diagrams  show the network and sequence of messages or communications between objects at run-time during a collaboration instance
- Sequence diagrams  are closely related to Communication diagrams and show the sequence of messages passed between objects using a vertical timeline
- Timing diagrams fuse Sequence and State diagrams to provide a view of an object's state over time and messages which modify that state
- Interaction Overview diagrams fuse Activity and Sequence diagrams to provide allow interaction fragments to be easily combined with decision points and flows 

Silicon Transistor 기술은 거의 한계에 다다르고 있다. Clock Speed를 높여서 성능의 향상을 얻는 것은 예전처럼 용이하지 않은 것으로 보인다. (INTEL은 4GHz의 벽을 넘지 못하고 있다.) 이미 주요 마이크로프로세서 업체들은 Multi-Core Processor로 접근 방향을 튼지 오래다. AMD는 Dual-Core Opteron을 발표하였으며, INTEL은 Notebook을 겨냥하여 Core-Duo를 발표하였다. 그리고 Multi-Core 추세는 계속 이어질 전망이다. Multi-Processing은 System의 전체적인 성능(Throughput)을 향상시키지만, Peak Performance를 향상시키지는 않는다. 따라서 Game과 같이 Peak Performance가 중요한 애플리케이션에서는 크게 이득이 되지 않을 것이다. 하지만, 동시에 여러개의 작업을 수행하는 Server Application의 경우는 Multi-Processing을 통해 성능의 향상을 얻을 수 있다. (Processor 수를 늘린다고, 그에 비례하여 성능이 향상되는 것은 아니다. 왜냐하면 Processor간 통신에 사용되는 Overhead, Cache의 사용율, 메모리 버스에서의 충돌 등이 성능을 오히려 저해할 수도 있기 때문이다.) 어쨌든, Multi-Processor는 대세가 되었다. 이는 2개의 Core에서 시작해서 4개, 8개, 16개 등으로 점차 늘어날 것이다.


Multi-Processor 구조는 같은 코어를 여러개 사용하는 Homogeneous Multi-processing과 다른 종류의 코어를 연결한  Heterogeneous Multi-processing으로 나누어 생각할 수 있다. 전자의 경우는 Opteron이나 Core-Duo같은 프로세서들이 해당하고, 후자의 예로는 IBM의 Cell 프로세서를 들 수 있다. Cell은 1개의 PowerPC Core와 8개의 DSP Core가 집적된 프로세서이다. 일반적으로 PowerPC와 같은 RISC 코어는 전체 시스템 제어에 주로 사용되며, DSP는 수치 연산에 특화되어 사용된다. Multi-Processor Architecture가 대중화 되면 이제 남은 문제는 이 아키텍쳐에 적합한 S/W를 작성하는 일이다. 기존의 S/W를 Multi-Processor에 맞게 변환하는 작업이나, 혹은 새로 Multi-Processor용 S/W를 작성하는 일이 모두 포함된다. 이것은 쉽게 해결하기 어려운 문제이다. Multi-Processor에서는 Multi-Thread 기반의 프로그래밍이 일반화 될텐데, 다들 알고 있듯이 Multi-Thread Application을 디버깅하는 것은 매우 어렵다. 왜냐하면 한번에 여러 개의 작업이 수행되고, 이들 사이에 연관성이 생기기 때문이다. 게다가, 모든 문제들이 Multi-Processor를 이용한다고 성능이 향상되는 것은 아니다. 문제가 비슷한 형태의 Sub-Problem으로 분할이 가능한 경우에는 Multi-Processor Architecture를 통해 성능의 향상을 얻을 수 있다. 하지만, 이것이 불가능한 경우에는 Processor를 여러 개 써도 성능에는 별 차이가 없게 된다.


따라서, Multi-Processor 환경에서 고민해야 하는 문제는 다음과 같이 정리할 수 있다.


1) 해결해야 하는 문제를 sub-problem으로 분할할 수 있는가? 아니면 Thread Group으로 구현하였을 때, 성능의 향상을 가져올 가능성이 있는가를 확인하는 일이다. (Amdahl's Law, Gustafson's Law 참조!) --> 프로그램은 Sequential 부분과 Parallel 부분으로 구분할 수 있다. Sequential 부분은 반드시 순차적으로 수행되어야 하므로, 프로세서 수에 상관없이 수행 시간이 동일하다. Parallel 부분은 병렬 수행이 가능하므로, 프로세서의 수가 많을 수록 이득을 얻을 수 있다. 예를 들어, 어떤 프로그램의 Sequential 부분이 50%라면, 프로세서 수가 아무리 늘어나도, 2배 이상의 성능향상은 불가능하다는 말이다. 즉, 10초 걸리는 작업을 줄이고 줄여도 5초까지 밖에 안된다. 기존의 S/W를 Multi-Processor Architecture로 재작성/변환하는 경우, Sequential 부분과 Parallel 부분의 확인이 먼저 선행되어야 한다. (이득을 얻을 수 있는가?)


2) Programmer가 일일이 작업을 분할하고, 작업이 수행될 Processor를 지정해 주는 것은 번거롭다. 프로그래머는 하나의 통일된 방법을 사용하여 S/W를 작성하고, 자동화 툴이 프로그램을 분석하여 프로그램을 적절히 분할하고, 병렬화 하며, 해당 부분을 적합한 프로세서에 매핑할 필요가 있다. (말은 쉽지만, 매우 어려운 작업이다.) 과거에 수행되었던 Parallel Compiler 기술들이 여기에서 빛을 발할 수 있다. 소스 코드 레벨에서 변환이 수행될 수도 있고, IR 레벨에서 변환이 수행될 수 있다. IBM의 Octopiler Project가 이런 목표를 지향하고 있다. Cell Processor에 좀 더 쉽게 프로그래밍을 할 수 있도록 하기 위함.


3) 프로그래머가 좀 더 쉽게 Multi-Threaded S/W를 개발할 수 있는 환경이 필요. Graphical Language, 4GL(Script like), 다중 실행 경로를 추적할 수 있는 디버깅 환경. 등등..

ww.programmableweb.com 에 가면 현재 공개되어 있는 Web API들에 대한 정보와, 그것들을 이용한 MashUp 애플리케이션 예제들을 살펴볼 수 있다.

현재 191개의 Web API가 공개되어 있으며, 561개의 MashUp 애플리케이션이 등록되어 있다.


MashUp의 경우, 여러가지 옵션으로 검색을 할 수 있는데,

      View as matrix는 어떤 API가 결합되어 MashUp을 구성하는지 알아보는데 편리하고,

      View popular는 가장 많이 애용되는 MashUP들을 나열한다.


관심이 가는 MashUp으로는 다음과 같은 것들이 있다.


1. LivePlasma : 어떤 주제에 대해 키워드를 입력하면 그것과 관련된 다른 아이템들을 연관된 강도에 따라 Visual하게 나타내어 준다. 이 기능은 news.cnet.com 에서도 차용하고 쓰고 있는 유용한 기능이다.


2. Where's Tim Hibbard ? : Tim Hibbard 라는 사람이 자신의 위치를 GPS를 이용하여 계속하여 업데이트하는 사이트이다. 이 사람의 행적을 날짜별로 추적할 수 있고, 메시지도 보낼 수 있다. 일종의 Life log라고 볼 수 있다.


3. Map Sex Offenders : 성범죄자들의 사진과 신상 정보를 지도상에 표시한 MashUp이다. 자신이 사는 동네 주위에 성범죄자가 살고 있는지 살펴보는데 유용하다. ..


이외에도 Bush History를 기술한 MashUp도 있고, 유명인들이 사는 위치를 지도상에 표시한 Celebrity Map도 있다.


MashUp에 대한 개념을 잡는데는 매우 유용한 사이트이다.

Zdnet에서 Virtualization에 대한 Whiteboard 강의를 듣고 정리한 것.

(강사 : Dan Chu, senior director of products, VMware )


0. 관련 링크 -

   http://news.zdnet.com/2036-2_22-6058678.html


1. 왜 가상화 기술인가?

 현재 서버를 활용하는 방법은 하나의 하드웨어(Server Computer)에 하나의 운영체계(OS)를 설치하고 그 위에서 응용 프로그램(Application)을 수행하는 방법을 사용한다. 예를 들면 어떤 서버는 Web Server를 수행하고, 어떤 서버는 Database를, 또 다른 서버는 E-Commerce 서버를 수행하는 것을 생각할 수 있다. 이들 Application은 같은 OS에서 수행될 수도 있고, 다른 OS를 요구할 수도 있다.


문제는 이들 서버의 평균 활용률이 5~10%정도밖에 안된다는 것이다. 나머지 90~95%의 성능은 낭비되고 있다.


2. 가상화 기술의 장점.

가상화 기술은 하나의 하드웨어에서 여러 종류의 OS를 수행하거나 혹은 한 OS를 여러 개 수행하는 것을 가능하게 한다. 각 OS들은 완전히 독립적으로 수행한다. 따라서, 기존에는 여러 대의 Server 하드웨어를 필요로 했던 작업들을 하나의 Server 머신에서 수행할 수 있다.


           |   eMail   | E-Commerce |  Web Server  |  Database |

           +---------+--------------+-------------+-----------+

           |    OS-1  |    OS-2          |    OS-3        |    OS-4     |

           +---------+--------------+-------------+-----------+

           |                           Virtualization Layer                     |

           +--------------------------------------------------+

           |                           Hardware                                  |

           +--------------------------------------------------+


따라서 보다 적은 수의 하드웨어로 서비스를 제공하는 것이 가능하다. 이는 곧 솔루션을 제공하는데 필요한 비용이 현저히 줄어든다는 것을 의미한다. 예를 들어, 기존의 하드웨어 5개가 필요했던 일이 1개만으로 수행할 수 있다면, 하드웨어의 수는 1/5로 줄어드는 것이다. 5천대의 Server를 가동했던 DataCenter라면 1000대의 Server로 기존의 서비스를 수행할 수 있다는 말이다. 게다가 서버 관리 및 유지에 필요한 유지 비용 (관리 인력 비용, 전기세, 건물 임대료 등등)을 고려하면 얻을 수 있는 이익은 더욱 크다고 할 수 있다.

컴파일러는 고급 프로그래밍 언어로 작성한 프로그램을 컴퓨터가 이해하는 기계어로 번역해주는 프로그램이다. 1950년대에 John Backus에 의해 Fortran 컴파일러가 개발된 이후, 수많은 프로그래밍 언어들이 개발되었다. Pascal, Simula, Modula, Ada, C, C++, Java, 등등..

이들 언어가 개발되기 이전에는 기계어 혹은 어셈블러를 이용해서 프로그램을 개발해야 했는데, 이는 너무나도 어려운 작업이어서 일정 복잡도 이상의 소프트웨어를 제대로 만드는 것은 불가능했다. 왜냐하면 어셈블러로 프로그램을 작성하는 경우, CPU가 명령들을, 어떤 순서로, 어떤 자원(레지스터, 혹은 메모리)을 사용하여 수행할 것인지를 프로그래머가 일일이 정해주어야 하기 때문이다. 예를 들어 5000줄 짜리 어셈블리 프로그램을 작성한다고 해보자, 명령어 하나가 2개의 레지스터를 사용한다고 가정하면, 프로그래머는 5000*2 = 1만개의 레지스터에 대해 일일이 판단을 내려주어야 한다는 말이 된다.

고급언어와 그를 위한 컴파일러가 개발됨으로써 이러한 문제들 중 많은 부분이 해결되었다. 프로그래머는 더 이상 어떤 레지스터를 쓸 것인지, 어떤 순서로 명령을 실행할 것인지를 고민할 필요가 없게 되었다. 컴파일러가 대신 처리해주기 때문이다. 뿐만 아니라. 고급 언어들은 프로그램을 구조적으로 구성할 수 있는 도구들을 제공하기 때문에, 프로그램을 작성하고 유지 보수하는 것이 훨씬 수월해졌다.

**사족 1: 프로그래밍 언어들은 소프트웨어 공학과 뗄수 없는 관계를 맺고 있다. 소프트웨어 공학의 이론이 언어에 반영되기 때문이다. 예를 들면, C언어가 대세였을 때, 소프트웨어 공학의 화두는 Modular Programming이었다. 그리고 이후, Object Oriented Programming[객체지향프로그래밍] 기법이 대세로 떠오르면서 C++, Java 같은 언어가 대세로 등장하게 된다.

우리가 사용하는 일반적인 컴파일러는 다음과 같은 구조를 가지고 있다.

사용자 삽입 이미지

사용자가 작성한 소스 프로그램은 먼저 Parser라고 불리는 해석기에 의해, IR(Intermediate Representation)으로 변환된다. 이 IR은 컴파일러가 프로그램을 이해하는 내부적인 표현 방법이다. IR을 생성한 이후에는 최적화 작업을 수행하는데, 이 단계에서는 최고의 성능을 낼 수 있도록 프로그램을 이리저리 뜯어고치는 작업을 하게 된다. 불필요한 부분을 삭제하고, 시간이 오래 걸리는 작업은 좀 더 효율적인 다른 작업으로 대치하는 등등... 최적화가 끝난 후, 코드 생성 단계에서는 IR을 실제 CPU가 제공하는 명령어들로 변환하고(Inst selection), 이어서 CPU의 자원들(레지스터, 메모리)를 할당하게 된다(Reg Allocation). 이 과정이 끝나면 어셈블리 코드가 생성되는데, 이 코드는 다시 어셈블러와 링커를 거쳐 실행 가능한 파일로 변환된다.

컴파일러마다 다소간의 차이는 있겠지만, 기본 골격은 위와 같다고 보면 된다. 소스 프로그램을 IR로 변환하는 단계를 일반적으로 Front-end(전반부)라고 하고, 그 이후 부분, 최적화에서 어셈블리 생성까지를 Back-end(후반부)라고 부른다.

컴파일러 개발로 인해 프로그래밍이 좀 해볼만해 진 것은 사실이지만, 아직 많은 문제들이 남아있다. 가장 큰 문제는 사용하는 프로세서(CPU)의 종류에 따라 컴파일러가 생성하는 최종 코드의 품질이 차이가 난다는 것이다. 이는 컴파일러가 해결해야 하는 일반적인 문제들이 현대 컴퓨터 구조로는 해결이 불가능한 NP 문제라는 데 기인한다. 각 명령에 레지스터를 할당하는 문제, 명령어의 순서를 어떻게 배열하는 것이 가장 효율적인지를 찾아내는 문제, IR의 각 부분들을 CPU의 어떤 명령으로 변환할지를 결정하는 문제 등등, 이 모든 문제들이 NP 문제이다. 따라서, 컴파일러는 Heuristic 혹은 Approximation(근사화) 방법을 통해 문제들을 해결할 수 밖에 없고, 사용한 알고리즘이 해당 프로세서와 얼마나 궁합이 잘 맞느냐에 따라 코드의 품질이 결정된다.

일반적으로 RISC(Reduced Instruction Set Computer) 프로세서의 경우에는 컴파일러가 비교적 좋은 결과를 낸다. 왜냐하면 RISC 구조가 컴파일러로 하여금 최적의 성능을 낼 수 있도록 하기 위해 설계되었기 때문이다. 반면에 DSP(Digital Signal Processor), NP(Network Processor)와 같이 CISC(Complex Instruction Set Computer) 구조로 설계된 프로세서에 대해서는 효율적인 컴파일러를 작성하는 것이 매우 어렵다.
** 실제로 RISC 프로세서는 컴파일러를 연구하던 사람들에 의해 제안된 구조이다.

또 하나의 문제는 컴파일러의 제작에 시간이 오래 걸린다는 것이다. 새로 설계한 프로세서를 사용하기 위해서는 컴파일러가 제작되어야 하는데, 이 시간이 짧게는 3개월에서 길게는 2~3년이 소모된다. Time-to-market이 중요한 지금의 시장 상황에서 이는 심각한 문제이다.

많은 컴파일러 연구자들이 위에서 제기된 두 가지 문제에 초점을 맞추고 있다. CISC 프로세서에 대해 효율적인 코드를 생성하기 위한 연구는 최근들어 내장형 시스템이 각광을 받으면서 더욱 활발해진 모습이다. 또한 컴파일러 작성 걸리는 시간을 절약하기 위한 방안으로 Retargetable Compiler라는 것이 연구되고 있다. 일종의 컴파일러 생성기라고 보면 되는데, 프로세서의 구조를 기술해주면, 컴파일러를 자동으로 생성해주는 소프트웨어이다. 아직까지 어떤 그룹도 만족할 만한 성능을 가진 Retargetable compiler를 개발하지는 못했다. 하지만 많은 그룹이 시간과 노력을 투자하고 있으므로, 2~3년 이내에 쓸만한 프로토 타입이 나올 것으로 예상된다.


BLOG main image
있는 그대로 보고 느껴라 by cyberyanne

공지사항

카테고리

분류 전체보기 (14)
Web Tech (4)
운영체제(OS) (1)
컴파일러 (1)
Technology (5)
Software Engineering (1)
General (1)

최근에 받은 트랙백

글 보관함

달력

«   2012/01   »
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 27 28
29 30 31        
Total : 1,552
Today : 3 Yesterday : 7