--* 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), 다중 실행 경로를 추적할 수 있는 디버깅 환경. 등등..
현재 서버를 활용하는 방법은 하나의 하드웨어(Server Computer)에 하나의 운영체계(OS)를 설치하고 그 위에서
응용 프로그램(Application)을 수행하는 방법을 사용한다. 예를 들면 어떤 서버는 Web Server를 수행하고, 어떤
서버는 Database를, 또 다른 서버는 E-Commerce 서버를 수행하는 것을 생각할 수 있다. 이들
Application은 같은 OS에서 수행될 수도 있고, 다른 OS를 요구할 수도 있다.
문제는 이들 서버의 평균 활용률이 5~10%정도밖에 안된다는 것이다. 나머지 90~95%의 성능은 낭비되고 있다.
2. 가상화 기술의 장점.
가상화 기술은 하나의 하드웨어에서 여러 종류의 OS를 수행하거나 혹은 한 OS를 여러 개 수행하는 것을 가능하게 한다. 각
OS들은 완전히 독립적으로 수행한다. 따라서, 기존에는 여러 대의 Server 하드웨어를 필요로 했던 작업들을 하나의
Server 머신에서 수행할 수 있다.
따라서 보다 적은 수의 하드웨어로 서비스를 제공하는 것이 가능하다. 이는 곧 솔루션을 제공하는데 필요한 비용이 현저히
줄어든다는 것을 의미한다. 예를 들어, 기존의 하드웨어 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년 이내에 쓸만한 프로토 타입이 나올 것으로 예상된다.