동일한 기능을 하는 프로그램이 여러 CPU 아키텍처로 포팅된 실행 파일을 살펴보면...
우리에게 아주 친숙한 x86 아키텍처용 EXE는 크기가 가장 작다고 단정적으로 말할 수는 없지만 상당히 작은 편이다.
이보다 코드 사이즈가 더 작게 컴파일되는 아키텍처는 본인이 보기엔 찾기가 어렵다.
EXE 파일의 기계어 코드 부분을 헥사 에디터로 들여다봐도 코드 바이트가 좀 조밀조밀 있는 편이다. 그 반면 IA64나 MIPS 같은 아키텍처 EXE의 기계어 코드를 들여다보면, 4~8바이트 단위로 패턴이 느껴진다.
물론 인텔 아키텍처도 그나마 32비트와 64비트로 가면서 인스트럭션의 평균적인 크기가 길어져 오긴 했다. 그런데 초기의 16비트 명령 체계는 정말 아담했으며, 어셈블리 튜닝을 쓰면 오늘날의 컴퓨터 아키텍처로는 상상도 할 수 없는 작은 크기의 프로그램으로 온갖 큼직한 기능을 넣을 수 있었다. ^^;;
인텔 아키텍처는 하위 호환성을 최대한 존중하고 있으니, 옛날에 정한 1바이트짜리 코드 바이트가 다 선점되었으면 32비트나 64비트 때 추가된 명령은 탈출문자 접두사를 붙여서 5바이트~6바이트... 이런 식으로... 근본적으로 길어질 수밖에 없는 셈이다.
컴퓨터 아키텍처에 대해서 배운 전산 전공자라면, CISC 방식과 RISC 방식의 차이에 대해서는 익히 들어 봤을 것이며, 오늘날엔 이런 식의 단편적인 구분이 별 의미가 없어졌다는 것까지도 알고 있을 것이다.
옛날은 메모리가 아주 귀한 자원이던 시절이었다. 그래서 인텔 x86은 코드를 적재하는 데 필요한 기억장소의 크기를 최대한 아끼는 방향으로 설계되었다. 기계어 코드의 크기가 1바이트부터 시작해 아주 가변적이고, 각 명령 하나하나에 꽤 함축적으로 많은 뜻을 포함시킬 수 있다.
많은 뜻이라 함은, 명령을 내릴 때 상대 주소라든가 절대 주소라든가 실제 상수 같은 개념을 한 인스트럭션에다가 바로 표현할 수 있음을 의미한다. 다른 아키텍처라면 임시값을 또 레지스터에다 먼저 저장하고 전처리를 해서 여러 인스트럭션을 거쳐 표현해야 할 명령을, 한 인스트럭션만으로 표현할 수 있다는 뜻이다. 이것을 CISC 방식이라고 하며, 앞의 C는 complex를 뜻한다. 인텔 x86은 CISC 방식 아키텍처의 대표적인 예이다.
CISC 방식는 상술했듯이 코드 길이가 짧아진다는 장점이 있지만 이를 하드웨어적으로 해석하는 회로가 필연적으로 복잡해지고 만들기 어려워진다는 단점이 있다. 이는 전력 소모의 증가로까지 이어진다. CPU에서 실제로 명령을 실행하는 부분이 아니라 이게 무슨 명령인지 파악하는 부분부터가 오버헤드가 커진다는 뜻이다.
그래서 CISC 방식은 근본적으로 임베디드나 모바일 환경에는 적합하지 않다. 이런 이유로 인해, 오늘날 전세계적인 각광을 받고 있는 스마트폰에서는 ARM이라는 RISC (Reduced) 방식 아키텍처가 쓰이고 있다. ARM이 스마트폰 세계에서 거의 인텔 x86 같은 본좌 인지도를 차지한 것이다.
RISC는 설계 철학이 CISC와는 반대이다. 인스트럭션의 크기는 기계가 한 번에 인식하는 machine word 크기와 일대일 대응하거나 최소한 배수 단위이다. 인스트럭션을 fetch, decode하고 의미를 파악하는 절차가 아주 간편하다. 최소 의미 단위가 작은 대신에 임시 작업용으로 범용 레지스터를 CISC 방식 CPU보다 꽤 많이 준다.
이런 CPU는 심지어 구조체 멤버를 인식하는 단위에도 제약이 있다. 포인터의 오프셋이 machine word의 배수 단위로 딱 떨어지지 않고 사이에 걸쳐 있는데 그 주소를 참조시키면 CPU가 에러를 일으킨다. 성능과 효율을 위해, 이런 복잡한 상황은 과감하게 처리를 거부하는 방법을 택한 것이다.
인텔 x86은 그렇지 않다. 명령어의 실행부터가 워낙 지저분한 바이트 단위 fetching에 익숙한지라, 오프셋이 저런 경우에도 한 사이클만에 참조를 못 하더라도 여러 사이클로 나눠서 사용자가 원하는 데이터를 얻어 준다.
RISC 아키텍처에서 돌아가는 실행 파일을 들여다보면 기계어 코드가 진짜로 4바이트나 8바이트 패턴으로 쫘르륵 나열되어 있는 걸 볼 수 있다. 그리고 동일한 코드를 컴파일한 실행 파일 크기가 x86 같은 아키텍처의 것보다 더 크고, 실행 파일의 압축률도 더욱 높다(듬성듬성하다는 뜻). 다만 한 기능을 수행하는 데 드는 CPU 명령 수가 증가하고 그럴 수록 side effect라든가 복잡도도 더 증가하는 만큼, RISC 아키텍처 코드를 컴파일러가 최적화하기는 더욱 어려울 것이다.
이렇듯, 컴퓨터 아키텍처에서 CISC냐 RISC냐 하는 케케묵은 논쟁은 자동차로 치면 전륜 구동이냐 후륜 구동이냐, 철도 차량으로 치면 동력 분산식이냐 동력 집중식이냐 하는 차원의 재미있는 주제이다. 요즘은 x86 같은 CISC 방식 CPU도 내부적으로는 복잡하고 함축적인 명령을 다시 RISC 식 명령으로 나눠서 파이프라인을 수행함으로써 RISC의 장점을 취하려고 하고 있다.
그리고 문득 든 생각:
어느 기계에서나 이식 가능한 평범한 C/C++ 코드는 자연어로 치면 "나는 철수입니다" 같은 평이한 문장에다 대응시킬 수 있다.
그렇다면 도저히 포팅이 불가능한 인라인 어셈블리 코드는, 자연어로 치면 특정 언어의 음운 특성이나 특정 문화 배경에 대한 이해 없이는 도저히 이해할 수도, 문자 그대로 번역할 수도 없는 함축적인 유머나 언어 유희에다 비유할 수 있겠다.
Posted by 사무엘