mir.pe (일반/밝은 화면)
최근 수정 시각 : 2024-12-17 10:40:00

어셈블리어


[[컴퓨터공학|컴퓨터 과학 & 공학
Computer Science & Engineering
]]
[ 펼치기 · 접기 ]
||<tablebgcolor=#fff,#1c1d1f><tablecolor=#373a3c,#ddd><colbgcolor=#0066DC><colcolor=white> 기반 학문 || 수학( 해석학 · 이산수학 · 수리논리학 · 선형대수학 · 미적분학 · 미분방정식 · 대수학( 환론 · 범주론) · 정수론) · 이론 컴퓨터 과학 · 암호학 · 전자공학 · 언어학( 형태론 · 통사론 · 의미론 · 화용론 · 음운론) · 인지과학 ||
하드웨어 구성 SoC · CPU · GPU( 그래픽 카드 · GPGPU) · ROM · RAM · SSD · HDD · 참조: 틀:컴퓨터 부품
기술 기계어 · 어셈블리어 · C/ C++ · C# · Java · Python · BIOS · 절차적 프로그래밍 · 객체 지향 프로그래밍 · 해킹 · ROT13 · 일회용 비밀번호 · 사물인터넷 · 와이파이 · GPS · 임베디드 · 인공신경망 · OpenGL · EXIF · 마이크로아키텍처 · ACPI · UEFI · NERF · gRPC · 리버스 엔지니어링 · HCI · UI · UX · 대역폭 · DBMS · NoSQL · 해시( SHA · 브루트 포스 · 레인보우 테이블 · salt · 암호화폐) · RSA 암호화 · 하드웨어 가속
연구

기타
논리 회로( 보수기 · 가산기 · 논리 연산 · 불 대수 · 플립플롭) · 정보이론 · 임베디드 시스템 · 운영 체제 · 데이터베이스 · 프로그래밍 언어{ 컴파일러( 어셈블러 · JIT) · 인터프리터 · 유형 이론 · 파싱 · 링커 · 난해한 프로그래밍 언어} · 메타데이터 · 기계학습 · 빅데이터 · 폰노이만 구조 · 양자컴퓨터 · 행위자 모델 · 인코딩( 유니코드 · MBCS) · 네트워크 · 컴퓨터 보안 · OCR · 슈퍼컴퓨터 · 튜링 머신 · FPGA · 딥러닝 · 컴퓨터 구조론 · 컴퓨터 비전 · 컴퓨터 그래픽스 · 인공지능 · 시간 복잡도( 최적화) · 소프트웨어 개발 방법론 · 디자인 패턴 · 정보처리이론 · 재귀 이론 · 자연어 처리( 기계 번역 · 음성인식) · 버전 ( 버전 관리 시스템 · Git · GitHub)

프로그래밍 사이트 선정 프로그래밍 언어 순위 목록
{{{#!wiki style="margin: 0 -10px -5px; word-break: keep-all"
{{{#!wiki style="display: inline-table; min-width: 25%; min-height: 2em;"
{{{#!folding [ IEEE Spectrum 2024 ]
{{{#!wiki style="margin: -5px 0"
<rowcolor=#fff> 스펙트럼 부문 상위 10개 프로그래밍 언어 직업 부문 상위 10개 프로그래밍 언어
1 Python 1 SQL
2 Java 2 Python
3 JavaScript 3 Java
4 C++ 4 TypeScript
5 TypeScript 5 SAS
6 SQL 6 JavaScript
7 C# 7 C#
8 Go 8 HTML
9 C 9 Shell
10 HTML 10 C++
}}}
}}}
}}}
[ Stack Overflow 2024 ]
[ TIOBE 2024 ]
||<tablewidth=100%><width=9999><-4><bgcolor=deepskyblue><tablebgcolor=#fff,#222> 2024년 8월 기준 검색어 점유율 상위 20개 프로그래밍 언어 ||
1 Python 11 MATLAB
2 C++ 12 Delphi / Object Pascal
3 C 13 PHP
4 Java 14 Rust
5 C# 15 Ruby
6 JavaScript 16 Swift
7 SQL 17 Assembly language
8 Visual Basic 18 Kotlin
9 Go 19 R
10 Fortran 20 Scratch
{{{#!wiki style="margin: 0 -10px -5px; min-height: calc(1.5em + 5px);"
{{{#!folding [ 21위 ~ 50위 펼치기 · 접기 ]
{{{#!wiki style="margin: -5px -1px -11px"
21 COBOL 36 Scala
22 Classic Visual Basic 37 Transact-SQL
23 LISP 38 PL/SQL
24 Prolog 39 ABAP
25 Perl 40 Solidity
26 (Visual) FoxPro 41 GAMS
27 SAS 42 PowerShell
28 Haskell 43 TypeScript
29 Dart 44 Logo
30 Ada 45 Wolfram
31 D 46 Awk
32 Julia 47 RPG
33 Objective-C 48 ML
34 VBScript 49 Bash
35 Lua 50 Elixir
}}}}}}}}} ||
[ PYPL 2024 ]

}}} ||
프로그래밍 언어 목록 · 분류 · 문법

1. 개요2. 장점3. 단점4. 생산성5. 디컴파일러6. 여담

1. 개요


Hello, world! 출력 코드 예시 (MS-DOS) 【 펼치기 · 접기 】
#!syntax cpp
adosseg
 .model small
 .stack 100h

 .data
hello_message db 'Hello, world!', 0dh, 0ah, '$'

 .code
main proc
    mov ax, @data
    mov ds, ax
    mov ah, 9
    mov dx, offset hello_message
    int 21h

    mov ax, 4C00h
    int 21h
main endp
end main
[1]

어셈블리어(Assembly Language)는 프로그래밍 언어의 하나로, 기계어에서 한 단계 위의 언어이며 기계어와 함께 단 둘뿐인 저급(Low Level) 언어에 속한다.[2][3] 주석에도 나와 있지만, 여기서 Low Level은 '수준이 낮다'라는 의미가 절대 아니라 하드웨어에 가까운 '근원적'이란 의미이다.

기계어라는 게 컴퓨터 관점에서 바로 읽을 수 있다는 것 빼고는 인간의 관점에서는 사용이 불편한 언어이기 때문에 이를 보완하기 위해 나온 것이 어셈블리어다. 따라서 어셈블리어의 특징은 기계어 1라인당 어셈블리 명령어가 대부분 1라인씩 대응되어 있고 이를 비교적 간단하게 짤 수 있는 어셈블러를 통해 기계어로 변환되도록 한 것이다. 이 때문에 어셈블리어는 고급 언어와 기계어 사이에 있다 하여 '중간 언어'라고도 불린다. 그리고 기계어는 CPU가 채택한 명령어셋( ISA)에 따라 다 다르기 때문에 어셈블리어의 명령어 역시 통일된 규격이 없다. 또한 문법 아키텍처에 따라서도 다르고 어셈블러의 종류에 따라서도 문법/매크로 등이 제각각이다.

위에서 말했듯이 어셈블리어는 통일된 규격이 없으므로, 모든 플랫폼에서 공통된 사항은 아니나 특별히 우리는 대부분 현재 x86- AMD64, ARM을 많이 사용하고 있으므로 부연 설명하자면, x86-AMD64 CPU 에서는 문법이 크게 인텔 방식과 AT&T 방식으로 나뉜다. 인텔 방식은 가독성이 뛰어나고, AT&T 방식은 가독성은 떨어지나, 인텔 방식보다 좀더 많은 정보를 포함하고 있다고 선호하는 사람들도 있다.[4] 사실 간단한 수준이라면 뭘 쓰건 큰 차이는 없다. 마이크로컨트롤러 임베디드 시스템으로 프로젝트를 할 경우 별도 명령어셋과 데이터 시트를 읽을 수 있어야 한다.

윈도우 플랫폼 위에서 코딩을 하게 될 경우, 보통 사용하는 IDE들이 인텔 문법 기반의 어셈블러(NASM, MASM 등)를 기본으로 하는 경우가 많으므로 이를 그대로 사용하지만, 리눅스를 위시한 오픈소스 진영에서는 스탠다드 컴파일러셋에 포함된 GNU 어셈블러(GAS)가 기본 옵션은 AT&T 방식을 사용하고 인텔 방식은 언제까지나 별도 옵션으로 지원하는 정도이기 때문에 GNU 컴파일러 툴과의 연동성을 위해서라면 부득이하게 AT&T 방식을 사용해야 한다.[5]

2015년 이후 어셈블리어의 점유율이 급상승하고 있다. TIOBE의 자료에 따르면 2017년 최고 8위까지 진입하여 세계 10위 이내로 들어올 정도로 급성장했으며, 2019년 기준 12위 (약 1.5%)로 절대로 낮다고 볼 수 없는 지경에 이르렀다. 사물인터넷에 쓰이는 연산력 자원이 적은 초소형 기기의 수요가 전세계적으로 크게 증가하고 있기 때문이다.[6] 어셈블리어는 C 등 다른 언어에 비해 높은 수준의 프로그램 최적화가 가능하며, 하드웨어 제어를 위해 특정한 기계어 명령을 수행하거나 레지스터, 메모리 번지에 직접 접근하기 쉽기 때문에 전력이나 메모리 사용량 등에서 민감하며 특정 기능을 위해 별도의 하드웨어 모듈을 이용하는 경우도 많은 초소형 임베디드 시스템에 유용하다. 단, 미국 시장에서 연봉은 Java보다 낮다.

어셈블리어 학습은 가급적 C언어 포인터를 이해하고 CPU 구조에 대해 이해한 다음 진행하는 게 유리하다. 반대로 어셈블리어를 먼저 하고 C언어의 포인터를 이해하는 방법도 있다. 따라서 컴퓨터공학과의 경우에도 1학년은 학습에 유리하지 않다.

캐슬린 부쓰(Kathleen Booth)[7] #가 개발했다.

2. 장점

"1번 레지스터(기억장치)에 들어있는 값에 10( 이진수 1010)을 더해서 0번 레지스터에 넣어라"라는 명령을 MIPS 아키텍처의 CPU에 내려보자.
단순히 바꾼 것뿐이지만 소리내서 읽을 수 있는 단어가 보이는 등 가독성이 좋아진다. 또한 프로그래머가 따로 주석을 달 수 있게 되어 한 개 이상의 명령 집합이 통틀어서 어떤 역할을 하는 루틴인지 나름대로 설명을 써 놓을 수 있으니 프로그래밍을 하기 훨씬 수월해졌다.

프로그래밍 언어가 다 그렇듯이 기계어에 가까울 수록 바이너리 파일크기가 작고 프로그램의 작동속도가 빠르다. C언어보다 훨씬 빠르므로 어떠한 프로그램을 만들어도 성능이 좋다. 다만 현재 운영체제 커널은 개발자의 생산성과 프로그램의 성능을 고려한 절충안으로 C언어나 C++ 등 상위의 언어를 선택하는 경우가 많다.

그리고 어셈블러는 컴파일러에 비해 만들기가 쉽다. CPU를 제조하는 제조업체가 해당 CPU 아키텍처에 맞는 C언어 컴파일러를 제공하지 않는 경우는 있을 수 있지만 어셈블러를 제공하지 않는 경우는 거의 없을 정도다. 어셈블러마저 배포하지 않는 칩은 제조업체가 대놓고 단종시키려고 하는 칩이거나 해당 칩 제조사가 거의 망해간다는 의미로 받아들이면 된다. 보통 칩을 팔아먹을 생각이 있는 제조회사라면 C언어 컴파일러까지는 제공한다.

또한 기계어에 대응되는 어셈블리 명령어 이외에도 메모리 위치나 정렬 등을 할 수 있는 지시어를 쓸 수 있고, 매크로 기능을 이용하여 매크로 호출을 쉽고 편리하게 할 수 있다. 이것마저 없으면 헬이다. 그로 인해 기계어로 직접 코딩할 때와 비교했을 때 보다 많은 소스코드가 포함된 프로젝트를 무리 없이 개발할 수 있게 됐다.

그리고 명령어 수가 적다. 어셈블리어는 CPU 매뉴얼의 부록 부분에 적혀있는 명령어 목록이 전부다.[9] 그 외에는 사용하는 어셈블러의 매크로 문법 정도인데 문법의 종류는 CPU의 종류보다 훨씬 적으므로 한 번 익혀두면 대체로 재사용이 가능하다. 그래서 특정 ISA에 대한 언어를 이해하는데 들어가는 시간이 매우 짧다.[10]

그리고 어셈블리어를 직접 사용하지 않더라도 시스템 의존적인 분야에서는 C/C++을 사용하더라도 어셈블리어 경험이 응용력에 차이를 준다. 따라서 커널 프로그래밍 및 임베디드 시스템 분야로 진로를 잡고 있는 사람은 어셈블리어 학습이 도움이 된다. 비주얼 스튜디오 Xcode는 Disassembly 기능을 통해 원본 소스 코드와 어셈블리 코드롤 비교해서 볼 수 있는 기능을 제공한다.

3. 단점

파이썬으로 Hello, world!를 출력할 경우
#!syntax cpp
print("Hello, world!")

어셈블리어로 Hello, world!를 출력할 경우[11]
#!syntax cpp
adosseg
 .model small
 .stack 100h

 .data
hello_message db 'Hello, world!', 0dh, 0ah, '$'

 .code
main proc
    mov ax, @data
    mov ds, ax
    mov ah, 9
    mov dx, offset hello_message
    int 21h

    mov ax, 4C00h
    int 21h
main endp
end main

어디까지나 기계어 대비 생산성이 높아졌을 뿐, 고급 언어에 비하면 생산성이 매우 떨어진다. 객체지향형 프로그래밍어인 Python으로 위의 어셈블리 언어와 똑같은 결과를 출력하려면 한 줄이면 충분하지만, 어셈블리어는 십수줄이나 필요하다. 또한 가독성이 매우 떨어진다.

고급 언어에 비하면 단어가 심하게 축약되어(addi는 add immediate[12]의 약자이다.)[13] 읽기에 좀 편한 수준이 된다. 그리고 CPU 아키텍처의 관점에서 소스 코드를 서술해야 하다 보니 크로스 플랫폼은 물 건너 간 지 오래고 정작 코드 작성자가 원하는 기능을 이해하기 쉽게 서술하기도 어렵다.

또한 어셈블리어는 언어를 이해하는데 들어가는 시간은 짧을지 몰라도 언어를 마스터하는데 들어가는 시간은 CPU 종류별로 천차만별인데, AVR, PIC 등의 단순한 CPU들은 CPU의 구조가 단순하여 사람이 코딩해도 그럭저럭 칩의 퍼포먼스를 전부 끌어낼 수 있으나 x86 등의 고성능 PC 및 워크스테이션용 CPU들은 파이프라인 기법이나 슈퍼스칼라 구조, 캐시 같은 온갖 속도향상 기법들이 도입되어 사람이 그 성능을 전부 끌어내기가 불가능에 가까워졌다. 현대의 i7 CPU도 옛날 386 시절의 x86 어셈블리 명령셋을 가지고 코딩 자체는 할 수 있는데, 그렇게 만든 프로그램의 퍼포먼스는 그야말로 펜티엄 4 수준으로 떨어진다. 그냥 80386 CPU의 코어 1개가 3GHz 속도로 동작하는 것과 다를 게 없는 환경이 조성되기 때문이다. 예를 들어 C언어의 성능을 올리려고 인라인 어셈블리 코드를 삽입했다고 하자. 하지만 옛날 286 머신과 동일한 방식으로 AH, AL레지스터만 사용할 경우 오히려 CPU의 주 연산기 성능이 AVX 연산 대비 1/256 이하로 떨어진다.

어째서일까? 예를 들어 CPU가 택배차이고 임무는 쌀배달이며 수행되는 기계어 코드가 배송 계획이라고 했을 때, 고급 언어로 배송 계획을 짠다면 택배차가 자전거인지 트럭인지에 따라 한 집씩 왕복하면서 배달할지 한 번 싣고 동네를 돌지를 컴파일러가 알아서 판단한다. 하지만 어셈블리어로 직접 코딩한다면 택배차가 자전거이든 트럭이든지와는 별개로 배송하는 방법도 프로그래머가 결정해야 한다. 왜냐하면, 고급 언어에서는 배송 계획을 짤 때 프로그래머가 '쌀을 쌀집에서 온 동네에 배송한다'라고 추상적으로 지시하지만 어셈블리어에서는 '쌀 하나를 싣는다. 100호를 간다. 100호에 도착해서 쌀을 내려놓는다. 쌀집으로 돌아온다. 쌀 하나를 싣는다. 101호를 간다...' 라고 구체적으로 지시해야 하기 때문이다.

자전거에는 쌀을 실어봐야 한두 가마니에 불과하니 택배차가 자전거( 80386)라면 이 지시는 합리적이다. 하지만 택배차가 트럭( i7)이라면 기름낭비, 시간낭비, 재능낭비일 뿐이다. 컴파일러라면 택배차가 쌀 100가마니를 실을 능력이 된다고 판단하면 '쌀 6가마니를 싣는다. 이동 거리가 최소화되는 순서대로 103, 104, 105, 102, 101, 100호 순으로 방문하면서 쌀을 하나씩 배송한다. 쌀집으로 돌아온다.'라는, 최적화된 배송 계획을 짠다.[14]

쌀집이 망해서 트럭이 도로 자전거가 돼도, 장사가 흥해서 트럭이 두 대가 돼도 컴파일러는 언제나 퍼포먼스를 최대한 발휘하는 배송 계획을 작성해 준다. 왜냐면, 프로그램이 지시하는 것이 '쌀을 온 동네에 배송한다'이기 때문이다. 하지만 어셈블리어는 자전거를 이용할 때는 어떻게 배송할지, 트럭을 이용할 때는 어떻게 할지, 효율 향상을 위해서는 어떻게 할지 이런 모든 최적화된 배송 계획을 프로그래머가 직접 작성해야 한다. 고급언어로는 무엇을 하라고 지시하는 게 가능하지만 어셈블리어는 어떻게 하라고만 지시할 수 있어서 이런 문제가 발생한다. 게다가 어셈블리어는 자전거의 종류 및 트럭 기종, 배달원의 신체 능력[15], 배달원의 출신지에 따라 명령을 달리 내려야 하는 단점도 안고 있다.

추상화가 덜 된 컴파일러한테 '배송하라'고만 시키면 그 의미를 이해할 수 없다며 컴파일에 실패할 것이다. 하지만 고급 언어의 추상화 레벨이 높아지면 추상적인 명령도 이해할 수 있게 된다. 여기서 더 추상화 단계가 올라가다 보면 '배송한다' 하나만 남고 다 지워질 수도 있다. 배송하는 게 쌀인지 고기인지도 중요치 않고(약타입, 타입 추론), 배송 범위가 동네인지 전국인지도 중요치 않고(메모리 계층 추상화), 쌀집이 어디 있는지도 중요치 않게 되어 버린다(하드웨어 추상화). 프로그래머는 그저 '배송하라'고만 시켰고, 컴파일러는 알아서 뭘 언제 얼마나 어떻게 배송할지 스스로 판단한다.

결국 어셈블리어는 이미 90년대 들어오면서부터 그 사용 빈도는 고급 언어들에게 밀려 거의 없다. 사실 과거엔 컴파일러가 그야말로 발적화에 가까웠기 때문에[16] 어셈블리어를 이용하여 컴파일된 코드를 고치거나 아예 어셈블리어로 프로그램을 짜기도 했지만 요즘은 PC 성능도 좋고 컴파일러도 좋아서 괜히 어셈블리어로 삽질할 필요는 거의 없다.

요즘은 오히려 컴파일러로 생성한 코드가 사람이 직접 어셈블리어로 작성한 코드보다 최적화가 잘 된 경우가 비일비재하다. 오래된 CPU와 달리 명령어 실행 시간이 조건에 따라 달라져서 사람이 그걸 다 따져가며 작업하기가 그만큼 힘들기 때문이다. 물론 컴파일러로는 절대로 할 수 없는 유형의 최적화도 있기는 한다. 예를 들어 하드 리얼 타임 시스템의 타이밍을 나노초 이하의 정밀도로 맞추는 최적화나 SIMD 프로그래밍과 같이, 어느 정도 수준까지는 가능하나 컴파일러가 그와 동급의 성능을 내는 코드를 생성하는 것은 불가능하다.[17] 즉, 컴파일러보다 잘할 자신이 없다면 어셈블리를 사용하는 의미가 없다. 그게 안 된다면 그냥 깔끔하게 C나 C++를 쓰자.[18] 물론 그게 가능한 소수의 능력자들이 있으며, 그들은 후술할 컴파일러, 운영체제, 임베디드 시스템(드라이버 등) 등의 개발에 필수이니 만큼 최고급 기술자로서 엄청난 몸값을 받는다.

4. 생산성

CPU 성능이 낮고, RAM이 작고, 컴파일러보다 인간이 최적화를 더 잘 해야 하는 경우 (time-critical한 경우 등) 어셈블리어를 사용하는 것이 유리하다.[19] 하지만 이런 환경이라 하더라도 CPU 성능이나 RAM이 조금만 확보되면 C, C++ 등의 고급 언어를 사용하는 것이 더 유리할 수 있다. 따라서 프로그래머는 둘 사이의 트레이드-오프를 적절하게 판단하여 프로젝트의 방향을 잡게 된다. 실제로 임베디드 시스템에서는 어셈블리어와 C/C++이 둘 다 쓰인다.

일반적으로 사용하는 컴퓨터에서야 64비트 CPU에 GB 단위 용량의 램이 대중화되어 있지만, 임베디드에서는 단가나 보드/칩 크기 문제로 8비트 CPU에 램도 MB도 아니고 고작 몇 KB 정도만 쓰는 경우도 많다.[20] C 컴파일러에서는 함수호출 프로시저 등에서 사용되지 않는 코드를 기본적으로 생성하는 경우가 있다. 이 경우 RAM이 극히 작다면 불필요하게 차지하는 램 용량도 성능저하를 일으킬 수 있기에 어셈블리어가 유리할 수 있다.

time-critical한 시스템에서는 타이밍을 맞추기 위해 어셈블리어의 사용이 더 많이 고려된다. 대표적으로 유도 미사일. 유도 미사일은 정해진 시간 안에 방향 계산을 완료하지 못하면 목표물을 격파시킬 수 없다. 어셈블리어로 하지 않으면 타이밍을 맞추기 힘든 경우도 많다.

컴퓨터의 성능 자체가 낮았던 8비트 시절까지는 어셈블리어가 필수로 여겨졌다. 1950~1980년대에는 CPU의 구조가 매우 단순했고 RAM도 작았다. 거기다 이런 시스템을 활용하는 컴파일러의 최적화 성능도 나빴다. 그리고 소스코드의 분량이 짧아 어셈블리어로 개발하더라도 개발자들이 감당할 수 있을 수준이었다. 특히 게임의 경우 대부분 어셈블리어로 제작됐다. 파스칼, C 같은 고급 언어로 최대한의 퍼포먼스가 필요한 액션이나 슈팅 게임을 만든다는 것은 상상하기 어려웠다. 16비트 IBM PC에서 굴러가던 MS와 볼랜드의 컴파일러는 8비트 컴퓨터용으로도 있었지만, 게임용으로는 별로 쓰이지 않았다.[21] 90년대까지도 저사양 PC에서 처리속도를 올릴 목적으로 어셈블리어 코딩이 이루어졌다. 콘솔이 저성능이던 때에 어떻게든 게임 퍼포먼스를 쥐어짜 보려고 갖은 수를 쓰던 시절, 컴파일한 바이너리나 실행코드의 메모리 주소를 포인터로 찍고 기계어로 출력한 후 이것을 역어셈블한 결과물을 베이스로 작업하는 식이었다.[22] 대표적으로 밀레니엄 시기였던 1999년· 2000년도에 휴대용 게임기 게임보이· 게임보이 컬러용으로 출시된 포켓몬스터 금·은 포켓몬스터 크리스탈 버전 역시 어셈블리어로 프로그래밍됐다. 90년대에 들어 차츰 게임의 스케일이 커지고 컴퓨터의 구조가 복잡해지며 컴파일러의 최적화 성능이 개선되고 난 뒤에야 C 등의 고급언어로 이행했다.

단순한 연산을 무척이나 많이 반복하는 분야 - 예를 들면 Computer Vision - 에도 생각보다 많은 어셈블리어가 쓰인다. SIMD가 대표적이다. GPGPU를 사용하기에는 환경적으로 어려운 경우 - 가령, 모바일이나 GPU 성능이 좋지 못한 하드웨어에 해당 알고리즘들을 포팅해야 할 때 - 에는 특효약이 될 수 있다. OpenMP라든가, 일반적으로 빌드 시 컴파일러 옵션에서 SIMD 관련된 것들이 있지만, 이는 매우 제한적으로 적용돼서 아직은 직접 사용자가 SIMD를 구현해야 한다. 다만, 어셈블리어를 직접 사용할 수도 있고, Intrinsics을 통해 좀 더 편한(?) C/C++ 스타일로 알고리즘을 구현할 수도 있다. 둘 간의 성능 차이도 생각보다 그렇게 크진 않다. Intrinsics을 사용하는 경우 컴파일러가 해 주는건 단순히 최적의 레지스터를 선택해 주는 역할만 할 뿐 명령어를 1:1로 생성하기 때문.

위와 같은 경우는 오늘날에는 특수한 경우가 됐다. 요즘 PC나 휴대폰에서 사용되는 일반적인 프로그램은 그냥 고급 언어를 사용한다. 전체적인 하드웨어의 성능이 대폭 상승하여 하나의 기기에 대한 최적화보다 다양한 기기에 대한 이식성, 생산성이 더 중요한 시대가 됐기 때문이다. 어셈블리어로 컴파일러를 이겨보겠다고 덤비는 그 시간과 인건비를 감수하는 것보다 그냥 한 스펙 더 높은 하드웨어를 사는 게 수백 수천 수억배(농담이 아니다) 싸게 먹힌다. 어셈블리를 대대적으로 동원해야 할 정도로 최적화가 절실할 경우에는 아예 FPGA, ASIC 등 전용 반도체를 제조해서 전용 머신을 만드는 게 싸다. 구글의 TPU가 이의 좋은 예이다. 소프트웨어로 아무리 빠르게 해 봐야 트랜지스터 게이트 레벨에서 최적화를 해 버린 주문형 반도체의 퍼포먼스를 따라잡을 수는 없다.

어셈블리어도 여러 가지가 존재하는데, 요즘 나오는 어셈블리어는 꽤 고수준 명령들을 지원하는 것들이 많고, 심지어 C에 가까운 프로그래밍이 가능한 것도 있다. 물론, 고수준 명령을 남발하면 퍼포먼스도 C만큼 떨어진다. 다만, 어셈블리어라는 타이틀을 위해서는 기계어와 1:1 대응이 돼야 하므로, 퍼포먼스를 원할 경우 의식적으로 그런 명령들을 배제하고 쓰면 상관이 없다. 이런 측면에서 보면 C보다 고수준 어셈블리어가 나을 수도 있지만, 어셈블리어는 결정적으로 명령어셋이 다른 플랫폼 간 호환성 문제가 절대 해결이 안 되는 치명적 단점이 있다.[23]

중요한 건, 어셈블리어를 사용한다고 꼭 퍼포먼스가 상승하는 것은 아니다. 크고 복잡한 프로그램들일수록 이런 경향이 있는데, 간단한 작업만 하기에도 복잡한 어셈블리어로 크고 복잡한 걸 만들어야 하는 경우 버그 없이 돌아가게 하는 것만 해도 이미 엄청난 일인데, 여기에 일반적인 C 컴파일러가 해주는 최적화를 능가하는 수준의 최적화까지 하기란 웬만한 편집증이 아니고서야 사실 거의 불가능하다.[24] 현대의 컴파일러는 최적화를 위해 생각보다 많은 일을 한다.[25] 실제로, 역사가 좀 된 프로그램 중에는 과거 어셈블리어로 짰다가 C 컴파일러들이 충분히 발달한 이후, C 언어로 다시 만들고보니 퍼포먼스가 오히려 크게 상승하는 기적이 일어났다는 경우도 종종 있는데 이는 C 컴파일러의 경우 최신 CPU 실행환경에 최적화된 바이너리 코드를 만드는 반면, 수제 어셈블리어 코드는 십수년 전 기준의 CPU 구조에 묶여 융통성 없는 바이너리 코드를 만들기 때문.

즉, 오늘날 임베디드 프로그래밍이 아닌 꽤 크고 복잡한 어플리케이션 프로그램에서 어셈블리어를 사용한다면, 보통 고수준 언어들로 만들어 놓은 다음 퍼포먼스 툴로 병목이 일어나는 부분을 찾아서 해당 코드만을 obj 파일 분석을 통해 어셈블리어로 튜닝해 주는 정도이다.[26] 실제로 보통 프로그램에서 95%의 코드는 런타임 시간의 5%만 차지한다는 격언이 있다. 런타임의 95%를 차지하는 5% 정도의 핵심 코드를 찾아서 최적화를 해주는 게 코드 최적화의 핵심이라는 것이다. 그렇기에 성능에 거의 혹은 전혀 영향을 안 미치는 부분까지도 포함해서 전부 어셈블리어로 바닥부터 만드는 건 일종의 바보짓이 된다. CPU 스펙이 향상됐을 때 고급언어는 컴파일 명령 한 줄 내리는 것 외에 다른 건 아무 것도 할 필요가 없다는 점도 명심하자. 어셈블리어는 아키텍처는 고사하고 리비전만 올라가도 소스 코드를 아주 많이 수정해야 한다.

5. 디컴파일러

어셈블리어를 고급 언어로 바꾸는 것은 매우 어려운 일이다. 그래서 어셈블리어로 만든 프로그램은 CPU 아키텍처가 바뀌면 이식이 잘 안 된다. 롤러코스터 타이쿤을 제작했던 크리스 소이어가 모바일 이식에 어려움을 겪고 있다고 말했다. 이유는 게임을 어셈블리어로만 만들었기 때문이다. 기사

물론 어느 분야가 그렇듯이 해당 CPU 어셈블리를 잘 알고, 특정 컴파일러들의 코드 생성을 잘 안다면 생각보다 별로 어렵지는 않다. 그 때부터는 변환 자체가 어려운 것이 아니라 단지 반복 작업이고, 코드량에 비례해서 많은 시간이 들어갈 뿐이다. 롤러코스터 타이쿤의 경우 테드 존(IntelOrca)이라는 영국 버크셔주 출신 프로그래머가 롤코타를 오픈소스로 배포하기 위해 리버스 엔지니어링을 한 뒤 C 언어로 디컴파일해서 만들었다. 소이어 역시 클래식 포팅 작업 당시 C로 옮겨갔다고 한다.

강력한 상용 분석 프로그램인 IDA에서 어셈블리어를 C 문법으로 바꿔주는 디컴파일 플러그인을 지원한다. IDA의 플러그인은 다른 프로그램보다 높은 정확도와 안정성을 가지고 있지만 보조용일 뿐, 오히러 사람이 직접 해석하는게 나을 경우가 있다.무엇보다도 기본판 가격만 무려 1000만원에 달한다.[27] 스택 포인터가 어긋나거나 난독화로 인해 기계어를 잘못 해석하고 있다면 이런 플러그인도 무용지물이다.

6. 여담



[1] 보편적으로 사용되는 어셈블리어 중 하나인 NASM x86 방식의 어셈블리어로 구현한 Hello, World! 출력 코드. [2] 여기서 '저급 언어'란 질이 낮은 언어를 뜻하는 것이 아니라, 보다 컴퓨터에 가까운 언어를 의미한다. '컴퓨터에 가깝다'는 것은 하드웨어 - 커널(운영 체제) - 장치 드라이버 - 쉘(Shell) - 응용 프로그램(App) - 사용자라는 구조에서 하단에 있음을 의미한다.(여담으로 Intel 계열 CPU에는 Ring0부터 Ring3까지의 총 4단계의 권한이 있는데 정확히 위 커널 - 장치 드라이버 - 쉘 - 응용 프로그램의 권한을 상정해 설정한 것이다.) 예컨대 응용 프로그램의 해석을 통해 실행되는 인터프리터형 언어인 자바스크립트 Java 같은 경우는 고급 언어에 속하는 것이다. C언어는 추상화된 고급 언어로 분류되지만 포인터를 이용하여 메모리에 직접 접근한다는 점에서 저급 언어의 특징도 갖고 있다. [3] 어셈블리어가 저급 언어, C언어가 고급 언어로 분류되는 핵심적인 이유는 하드웨어 아키텍처(ISA)에 독립적인가 아닌가로 구분된다. 어셈블리어는 이식성이 없지만 C언어는 컴파일러만 적절한 것을 사용하면 다른 기종에서도 동작한다는 이식성은 있다. [4] 물론 이 두 방식은 어느 정도의 절대적인 가독성 비교를 하기엔 어렵고, 취향 및 익숙함의 문제라고 보는 것이 맞다. 먼저 오래 접하고 본 것이 어떤 문법이냐에 따라서 다르다는 말이다. [5] 하지만 요즘에는 한 가지 문법만 지원하는 경우는 거의 없으므로 사실상 취향 문제라고 할 수 있다. [6] '넷구루'라는 회사에서 티오베 순위에서 어셈블리어 순위가 높아진 이유를 IoT 증가로 평가했다. [7] 1922년 영국 우스터셔 출생으로, 로열 홀러웨이 대학교에서 수학을 전공하고 응용수학으로 박사 학위를 받았다. [8] $기호를 붙이는 것은 어셈블리어 AT&T 문법으로 레지스터를 메모리 위치를 나타낸다 [9] 하지만 우리가 쓰는 모든 프로그램은 그 명령어들이 잘 조합되고 배열돼서 만들어진 결과물이다. [10] 사실 난해한 프로그래밍 언어와 같은 종류에서도 최소한의 명령어만으로도 튜링 완전인 언어들이 많이 있고, 이런 언어들 역시 명령어를 외우는 것 자체는 어렵지 않다. 그걸 이용해서 실제 프로그램을 짜는 것이 고급 언어보다 어려울 뿐... [11] Intel IA-16 어셈블리어이며, MASM 어셈블러 문법으로 되어 있다. 그리고 메시지의 출력은 MS-DOS의 운영체제 API(INT 21h)에 의존한다. 다시 말해, DOS 환경이 아닌 순수 생 16비트 환경이라면 이보다 코드는 더 길어진다(!) [12] '상수'라는 뜻이다. 윗 단락 예시에서 10을 더했듯이 고정된 수를 더할 때 쓰인다. 기억장치에서 숫자를 불러올 필요 없이 바로(immediately) 더할 수 있어서 immediate라는 이름이 붙었다. [13] 물론, C 언어도 만들어질 당시에는 에디터의 자동완성 기능 같은 건 상상할 수도 없었으니 오늘날 함수명이나 변수 하나가 심하면 한 줄씩 차지하는 언어들에 비하면 타수를 줄이기 위해 strxfrm(string transformation)이나 fma(fused multiply-add) 등 라이브러리 함수명처럼 축약형을 애용하는 전통을 따르는 편이다. [14] 심지어 지금은 요청이 없지만 배송 중에 200번대 집에서도 배송 요청이 들어오곤 한다는 것까지 알아내서 이를 예측해 미리 200번대 호수에 배송할 쌀까지 실어놓고 동네를 도는 배송 계획도 짤 줄 안다! 전문 용어로는 분기 예측, 슈퍼스칼라 예측 등으로 불린다. 정확히는 CPU가 하는 일이지만 컴파일러가 보조해 주지 않으면 작동하지 않는 기능이다. 그 덕에 높으신 분이 쌀을 주문하는 주소를 알아낼 수 있는 부작용이 생기기도 했다. [15] 한때 본체에 ' 터보 버튼'이라는 것이 있었던 이유이기도 하다. [16] 어느 정도로 발적화였냐면, while(1){ ... } 이 for(;;){ ... } 보다 느린 컴파일러가 많았을 정도. 오늘날 컴파일러는 둘 다 무한루프인 걸 알아서 속도가 같지만, 과거 컴파일러의 경우에는 while 문의 경우 루프를 돌 때마다 항상 저 1 을 테스트해서 속도가 떨어졌었다. 이 때문에 연식이 좀 된(주로 80~90년대에 현역으로 활동하던 사람들) 프로그래머들은 지금도 습관적으로 while(1) 보다 for(;;) 를 애용하는 경우를 종종 볼 수 있다. [17] 어셈블리어 외의 언어는 명령어 하나가 기계어 명령 몇 개로 바뀔지 예측하기도 어렵고, 원하는 개수로 만들기는 거의 불가능하다고 보면 된다. [18] 같은 맥락으로 C, C++ 코드 최적화에 서투른 사람이 짠 스파게티 코드는, Go, Java, JavaScript 등 추상화가 더욱 잘 되어 있는 언어상에서 최적화가 잘 된 채로 짜인, 같은 기능을 하는 코드에 비해서 더 느릴 수도 있다. [19] C언어, C++과 같은 고급언어는 컴파일러를 통해 기계어로 번역돼서 절차가 복잡하다. [20] BSS 참조. 그리고 ATTiny의 램은 1KB 이하다. [21] 고급언어로 짠 게임 중 유명한 것은 울티마 I같은 RPG. 특히 8비트 애플용 화면은 보잘 것 없었기에 베이직으로 짠 프로그램이 여럿 나왔다. MSX용 상업용 프로그램은 어셈블리어. [22] 어셈블리어는 기계어와 1:1 대응이 되므로 바이너리 ↔ 어셈블리어 양방향 전환이 자유로운 편이다. [23] 물론, C의 호환성도 따지고 보면 미신이라고 반박하는 경우도 있는데 그래도 거의 완전히 갈아엎어야 하는 것과 부분부분 바꿔주면 되는 것의 차이는 크다. 그리고 C와 같은 고급 언어들은 일단 로직 루틴은 확실하게 플랫폼 독립적일 수 있다. 단지 프로그래머가 얼마나 추상화를 잘 해서 로직 루틴과 기타 루틴들을 잘 분리하느냐의 문제일 뿐이다. 잘 짜놓기만 한다면, 플랫폼 의존적인 부분만 다시 코딩하면 된다. 아예 갈아엎는 어셈블리와는 비교가 불가능하다. [24] 복잡한 현대의 CPU는 단순히 CPU 클럭수만으로 성능이 결정되지 않는다. 분기 예측, 캐시 적중률 등의 여러 요인이 성능에 큰 영향을 미치는데, 어셈블리어로 코딩하면서 이런 것까지 전부 신경 쓰기란 불가능에 가깝다. [25] CPU의 복잡성과 컴파일러에 의한 최적화가 결합하면서, 프로그래머 딴에는 '이렇게 수정하면 더 빨라지겠지?'라는 생각으로 코드를 수정해도 성능에 별 차이가 없거나 심지어 더 느려지는 일이 매우 비일비재하다. 때문에 함부로 최적화하는 건 절대 금물이고 벤치마크를 통한 검증은 반드시 필요하다. 프로그래밍 언어를 써도 이런 상황인데, 어셈블리어를 직접 사용하면 더 심해진다. 이처럼 최적화와 관련된 문제는 인간의 직관을 벗어나는 경우가 많고, 이런 부분은 컴파일러가 더 정확한 결정을 내리기는 경우가 많다. [26] 1989년 터보C 2.01이 인라인 어셈블리를 지원하는 등 볼랜드사의 터보C, 터보파스칼 컴파일러가 인라인 어셈블리를 쓸 수 있게 된 다음에 좋아한 개발자가 많았다. [27] 사실 Ghidra라는 무료 소프트웨어도 있기는 하다. 무려 미국 국방부에서 제작하여 배포하고 있는 소프트웨어이기에 그만큼 높은 성능을 보여주기는 하지만 그래도 IDA만큼은 못하다. [28] 컴파일러는 말 그대로 편집, 또는 수집을 한다는 소리이다. 예를 들어, C언어의 컴파일러는 소스 코드를 목적 코드로 바꾼 뒤, 여러 컴파일에 필요한 파일을 모은 후에 바이너리(실행 파일)로 만들어 출력을 한다. 어셈블리어를 기계어로 바꾸는 과정에는 그런 거 없다. 그러기에 어셈블러와 컴파일러로 나누는 것이다. [29] 그 당시 에니악이 한 대당 가격으로 50만 달러인데 지금 가치로는 6백만 달러, 한화로 대략 70억원 언저리이다. 거기다 진공관은 자주 깨지니 그만큼 갈아주고, 안에 벌레 같은 게 들어오는 순간 개고생을 해야 했다. [30] 물론 아무리 어셈블리어로 짜도 못 짜는 사람은 더럽게 랙 걸린다. 그만큼 크리스 소이어의 실력이 대단하다는 걸 알 수 있다. [31] 트랜스포트 타이쿤은 386DX에서도 돌아간다! [32] MSX의 경우 MSX-BASIC을 탑재하고 있어서 이를 이용하면 손쉽게 게임을 만들 수 있었다. 그러나 MSX에 사용된 Z80의 성능으로는 순도 100%의 BASIC으로 짜면 더럽게 느렸다. 이를 타개하려면 PEEK, POKE 등의 명령어로 기계어를 입출력해야 했다. 'MSX 베이식 군'(MSXべーしっ君)이라는 BASIC 컴파일러를 동원하면 쾌적하게 돌아갔지만 고속화가 불가능한 영역에서는 고속화를 끄는 번거로운 처리를 해줘야 했던 데다 그렇게 고속화해봤자 어셈블리어보다는 느리다는 한계도 엄연히 존재한다. 그래도 베이식 군에서는 주석 기능을 통한 기계어 삽입 기능을 지원하여 중간중간에 기계어를 넣어서 고속화를 꾀할 수 있다. [33] 넓게 보면 아타리 쇼크도 어셈블리어 때문에 발생했다고 볼 수 있다. 당시까지만 해도 아타리는 굉장히 잘 나가는 게임회사였고, 주어진 개발기간 또는 예산 이내에서 많은 게임을 찍어내야 했으며, 그 정점에 달했던 것이 영화 E.T.의 게임화였다. E.T.는 그 당시 인기영화였기에 아타리는 E.T.의 판권을 구입했고, 크리스마스 시즌에 맞춰 게임을 발매하기로 계획했으나 저수준 언어를 통해 개발할 물리적인 시간이 부족한 것이 화근이었다. 시간을 개발에 맞추지 않고 개발을 시간에 맞춰서 무리하게 내놓은 결과 게임은 에러가 많았고 별 다른 기능이 많지 않았던 것. 때문에 대량 리콜사태가 발생했고, 아타리는 이후 다시는 1인자 자리로 올라오지 못할 정도로 망했다. 물론 당시의 개발 환경 상 기계어와 어셈블리어 중 어느 쪽을 더 많이 썼는지 검증할 수 있는 자료는 없지만, 지금까지 쌓인 게임 프로그래밍 기술과 고수준 프로그래밍 언어를 활용한다면 아타리 2600을 기준으로 정말 짧게는 단위까지 게임 개발 기간을 단축시킬 수 있다. [34] 저 시절 세가의 경우 시장에서 널리 쓰이는 (그리고 당시에는 고속이었던) 칩을 CPU로 쓰고, C언어 등을 지원하거나 작곡을 PC에서 GUI로 할 수 있는 라이브러리들(대표적으로 SMPS)을 개발사들과 공유하는 등 나름 개발자 친화 정책을 폈지만, 복제도 개발도 어렵게 하드웨어를 설계하고 고수준 언어에 인색했던 닌텐도 진영에서는 세가 진영에 비해 개발 난이도가 높았다. 물론 그 세가도 32비트 시절에 세가 새턴이라는 거대한 삽질로 인해 어셈블리어 프로그래밍이 강요되기도 했고... [35] MS의 매크로 어셈블러와 베이직 인터프리터는 IBM PC와 함께, 그리고 볼랜드의 터보 시리즈 컴파일러가 8비트 컴퓨터용으로 출시됐지만, 당시 컴퓨터책과 잡지에서는 C언어를 저급언어라 부르면서도 어셈블리어 강좌를 연재했고, 개인 프로그래머는 변환테이블을 보고 핸드어셈블한 걸 기계어 모니터 프로그램으로 직접 입력하기도 했다. 디버그는 트레이스(TRON, TROFF)를 이용해 레지스터와 포인터, 플래그를 직접 보며 근성으로! [36] 기본적으로 카세트 테이프는 CD보다 제작 단가가 높다. 90년대 초반까지만 해도 조금이라도 돈을 더 아끼려고 오래된 어학 테이프 등을 지우고 음악을 녹음해서 담고 다니던 시절이 있었다. [37] http://xcoolcat7.tistory.com/292에서 발췌 [38] 파일 내 바이러스를 검출하는 것만 놓고 본다면 굳이 어셈블리어가 필요한 일은 아니겠으나, 당시 유행했던 여러 바이러스들은 플로피 디스크나 하드 디스크의 부트 섹터를 변조하는 부트 바이러스, 그리고 인터럽트 벡터를 건드리는 램 상주형 바이러스(TSR, Terminate and Stay Resident) 같은 것들이 많았어서 이런 바이러스들에 대응하기 위해서는 디스크 섹터 직접 접근과 같은 저수준의 권한이 반드시 필요했으므로 어셈블리어로 짤 수 밖에 없었다. [39] 이들의 대표작으로 Second Reality가 있다. 모듈 음악 문서에서 확인 가능.