최근 수정 시각 : 2022-06-26 00:32:56

어셈블리어

프로그래밍 사이트 선정 상위 점유율 프로그래밍 언어 목록
{{{#!wiki style="margin: 0 -10px -5px"
{{{#!wiki style="display:inline-block; margin:0 0 -5px; min-width:25%"
{{{#!folding ⠀[ IEEE Spectrum 2021 ]⠀
{{{#!wiki style="margin:-6px -1px -10px"
IEEE Spectrum에서 집계한 2021년 기준, 웹 분야 상위 10개 프로그래밍 언어
1 Python 2 Java 3 Javascript 4 C# 5 Go
6 HTML 7 PHP 8 Dart 9 Ruby 10 Rust
IEEE Spectrum에서 집계한 2021년 기준, 모바일 분야 상위 10개 프로그래밍 언어
1 Java 2 C 3 C++ 4 C# 5 Swift
6 Dart 7 Kotlin 8 Scala 9 Objective-C 10 Delphi
}}}}}}}}}
⠀[ Stack Overflow 2021 ]⠀
||<table width=100%><width=2000><-10><bgcolor=#FFA500><table bgcolor=#fff,#222> Stack Overflow에서 조사한 2021년 기준 사용률 상위 25개 프로그래밍 언어
1 JavaScript 2 HTML, CSS 3 Python 4 SQL 5 Java
6 Node.js 7 TypeScript 8 C# 9 Bash 10 C++
11 PHP 12 C 13 PowerShell 14 Go 15 Kotlin
16 Rust 17 Ruby 18 Dart 19 어셈블리어 20 Swift
21 R 22 VBA 23 MATLAB 24 Groovy 25 Objective-C
⠀[ TIOBE 2022 ]⠀
||<table width=100%><width=2000><-10><bgcolor=deepskyblue><table bgcolor=#fff,#222> TIOBE에서 선정한 2022년 2월 기준 검색어 점유율 상위 20개 프로그래밍 언어
1 Python 2 C 3 Java 4 C++ 5 C#
6 Visual Basic .NET 7 JavaScript 8 PHP 9 어셈블리어 10 SQL
11 Classic
Visual Basic
12 R 13 Go 14 Fortran 15 Groovy
16 Swift 17 Ruby 18 Perl 19 MATLAB 20 Delphi /
Object Pascal
{{{#!wiki style="margin:0 -10px -5px"
{{{#!folding [ 21위 ~ 50위 펼치기 · 접기 ]
{{{#!wiki style="margin:-6px -1px -10px"
21 Objective-C 22 Prolog 23 Scratch 24 SAS 25 LISP
26 COBOL 27 Rust 28 Ada 29 Dart 30 Transact-SQL
31 PL/SQL 32 ABAP 33 VBScript 34 LabVIEW 35 Julia
36 Scala 37 TypeScript 38 Kotlin 39 Haskell 40 Lua
41 Apex 42 Visual FoxPro 43 Bash 44 Scheme 45 D
46 PL/I 47 Elixir 48 Logo 49 PostScript 50 Ladder Logic }}}}}}}}}
⠀[ PYPL 2022 ]⠀
||<table width=100%><width=2000><-10><bgcolor=green><table bgcolor=#fff,#222> PYPL에서 선정한 2022년 3월 기준 검색어 점유율 상위 20개 프로그래밍 언어
1 Python 2 Java 3 JavaScript 4 C# 5 C/ C++
6 PHP 7 R 8 Objective-C 9 TypeScript 10 Swift
11 MATLAB 12 Kotlin 13 Go 14 Rust 15 Ruby
16 VBA 17 Ada 18 Abap 19 Dart 20 Visual Basic
}}}
프로그래밍 언어 목록 · 분류 · 문법

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


Hello, World! 출력 코드 예시 【 펼치기 · 접기 】
#!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, C00h
    int 21h
main endp
end main
[1]

1. 개요

Assembly language

프로그래밍 언어의 하나. 기계어에서 한 단계 위의 언어이며 기계어와 함께 단 둘 뿐인 저급(Low Level) 언어에 속한다.[2][3]

기계어라는 게 컴퓨터 관점에서 바로 읽을 수 있다는 것 빼고는 인간의 관점에서는 사용이 불편한 언어이기 때문에 이를 보완하기 위해 나온 것이 어셈블리어다. 따라서 어셈블리어의 특징은 기계어 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 구조에 대해 이해한 다음 진행하는 게 유리하다. 따라서 컴퓨터공학과의 경우에도 1학년은 학습에 유리하지 않다.

2. 장점

"1번 레지스터(기억장치)에 들어있는 값에 10( 이진수 1010)을 더해서 0번 레지스터에 넣어라"라는 명령을 MIPS 아키텍처의 CPU에 내려보자.
  • 2진수 기계어: 001000 00001 00000 0000000000001010
  • 16진수 기계어: 20 20 00 00 00 00 00 0A
  • 어셈블리어: addi $0, $1, 10[7] // 뜻: $0 = $1 + 1010 (2)

단순히 바꾼 것뿐이지만 소리내서 읽을 수 있는 단어가 보이는 등 가독성이 좋아진다. 또한 프로그래머가 따로 주석[8]을 달 수 있게 되어 한 개 이상의 명령 집합이 통틀어서 어떤 역할을 하는 루틴인지 나름대로 설명을 써 놓을 수 있으니 프로그래밍을 하기 훨씬 수월해졌다.

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

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

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

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

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

3. 단점

어디까지나 기계어 대비 생산성이 높아졌을 뿐, 고급 언어에 비하면 생산성이 매우 떨어진다.[11]

고급 언어에 비하면 단어가 심하게 축약되어 있어(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년도에 휴대용 게임기 게임보이용으로 출시된 포켓몬스터 골드 버전 역시 어셈블리어로 프로그래밍되었다. 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 컴파일러가 해주는 최적화를 능가하는 수준의 최적화까지 하기란 웬만한 편집증이 아니고서야 사실 거의 불가능하다. 실제로, 역사가 좀 된 프로그램 중에는 과거 어셈블리어로 짰다가 C 컴파일러들이 충분히 발달한 이후, C 언어로 다시 만들고보니 퍼포먼스가 오히려 크게 상승했다는 경우도 종종 있는데 이는 C 컴파일러의 경우 최신 CPU 실행환경에 최적화된 바이너리 코드를 만드는 반면, 수제 어셈블리어 코드는 십수년 전 기준의 CPU 구조에 묶여 융통성 없는 바이너리 코드를 만들기 때문.

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

5. 디컴파일러

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

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

6. 여담

  • 어셈블리어를 기계어로 바꿔주는 프로그램을 어셈블러라고 한다.[25]
  • 현대의 고성능 CPU아키텍처는 대부분 RISC 방식의 어셈블리 명령셋을 지원하고 있다. RISC와 대비되는 CISC 방식은 좀 더 사람에 친화적이고 RISC는 좀 더 기계에 친화적이다. 즉 현대의 CPU 제조사들은 이미 어셈블리 언어를 사람이 직접 써서 코딩하는 상황을 전혀 고려하고 있지 않다.
  • 가상머신에도 어셈블리어가 사용된다. Java 바이트코드 .NET Framework|.NET CLR의 CIL이 대표적인 가상머신 어셈블리어. 다만, 가상머신 어셈블리어는 다양한 자료형을 지원하거나 함수를 지원하는 등 실제 어셈블리어와는 차이를 보이며, 대개 JIT 컴파일러를 거치거나 인터프리터를 통해 해석된다.
  • 존 폰 노이만의 제자가 어셈블리어를 만들자 폰 노이만이 노발대발 했다는 이야기가 있다. 고작 그런 거로 컴퓨터님(…)의 성능을 낭비하려 한다나 뭐라나. 물론 이 당시 컴퓨터의 위상은 요즘 기준 수십억원대 최고급 연산장치였다.[26] 그렇기 때문에 국가 단위로 한, 두개 있는 정도였기 때문에 성능을 낭비한다는 것은 있을 수 없는 일 이기에 폰 노이만 같이 기계어로 자유자재로 코딩이 가능한 효율이 높은 괴수가 많이 필요한 시대였다는 점도 있다. 세월이 지나고 심하게 비효율적인 코드도 높은 성능의 하드웨어와 컴파일러의 효과로 준수한 성능을 내기 때문에, 컴퓨터 한 대가 프로그래머 한 달 월급보다 싸진 요즘은 예전과 반대로 프로그래밍의 효율이 훨씬 중요하게 취급된다.
  • 크리스 소이어 트랜스포트 타이쿤 롤러코스터 타이쿤 1, 2까지도 이 언어로 짜여 있다. 그래서 오브젝트가 난무하는 시나리오 후반부에도 음악파일을 가져올 때를 제외하고 랙이 거의 존재하지 않을 정도로 최적화가 잘 되어있다.[27] 또한 저사양에서도 무난하게 돌아가기 때문에 전세계적으로 많은 사랑을 받을 수 있는 이유 중의 하나가 되었다.[28] 하지만 오픈소스 프로젝트가 가동되면서 OTTD, OpenRCT2, OpenLoco 등은 모두 C언어로 재구성되었다.
  • 아타리 쇼크 이전에는 게임 프로그래밍이 곧 어셈블리어 프로그래밍이고 시스템 프로그래밍이었던 시절인지라 어떤 게임기이든 닥치고 어셈블리어로 작성할 수밖에 없었다. 슈퍼패미콤, 메가드라이브, 패미콤, MSX 등 8, 16비트 세대에 나온 대부분의 게임은 거의가 다 어셈블리어로 개발되었다. 덕분에 게임개발기간은 6~12개월까지 확장되었다. [29] 게임보이까지 어셈블리어로 개발되어 어셈블리어 세대다.(...) 몇몇은 자체 언어를 제작하는 등 예외를 두고 있기는 하지만. 당시의 하드웨어 성능으로는 아무래도 어셈블리어로 직접 쓴 코드와 컴파일러가 생성하는 코드의 퍼포먼스 차이가 꽤 컸기 때문이다. 이후 컴파일러 성능이 좋아져서 요즘은 굳이 어셈블리어로 삽질할 필요가 없어졌다.[30] 그만큼 게임기의 성능이 좋아지고 게임의 용량이 커졌다는 뜻이다. 대신에 게임에 고급언어가 들어가며 발적화 문제가 대두되기 시작했다.
  • 1980년대 컴퓨터 잡지(예를 들어 일본 I/O 지)를 보면 프로그램의 어셈블리어 코드가 빽빽하게 인쇄된 페이지가 수백장씩 있었다. 독자는 그걸 한글자 한글자 직접 입력해서 프로그램을 복사...?하곤 했다. 물론 당시에도 카세트 테입 등의 저장매체가 있었으니 그런 것에 프로그램을 담아 배포할 수도 있었을 터이나, 이런 잡지의 주목적은 프로그래밍 언어의 학습이었으므로 코드를 직접 입력하며 그 내용을 공부하라는 의미가 있었고, 애초에 카세트 테이프를 읽을 수 있는 데이터 레코더마저도 갖추지 않은 컴퓨터도 많은데다, 지금은 거저 줘도 쓰지 않을 카세트 테이프도 당시 기준으로는 부록이라고 무료로 마음껏 배포할 수 있는 자원도 아니었다.[31] 인쇄물로 나눠주는 것이 가장 확실한 배포방법이기도 했던 셈. 여러모로 지금에 와서는 상상하기 힘든 일이다. 한 바이트라도 틀리면...
  • 안철수가 만든 V3는 1995년 회사를 설립하기 전까지 어셈블리어로 개발되었다.[32][33] 이후 1995년에 정식으로 회사를 세우면서 어셈블리어를 전부 C로 갈아엎었는 작업을 거쳤다고 한다.
  • 어셈블리 코드를 짜는 시뮬레이션 게임이 스팀에 판매중이다... TIS-100. 너무 어렵게 생각한다면 Human Resource Machine을 해보는 것도 도움이 될것이다.
  • 바람의 나라 (1996)는 어셈블리어로 코딩되었다. 6.30 버전 (2004) 이후로 DirectX를 지원하는 버전으로 모두 교체되었다.
  • 1992년부터 계속되어온 핀란드의 어셈블리어 프로그래밍 경연대회가 있다. 주로 데모씬을 공개하는데, 여기서 활약해던 게 바로 현재 3DMark의 개발사 퓨처마크의 전신인 퓨처 크루.
  • 2004년 엄청난 저용량임에도 꽤 볼만한 그래픽을 보여준 FPS게임인 .kkrieger는 이 언어와 C++을 혼용해 작성되었다고 한다.
  • 핀란드에서 매년 어셈블리어를 이용한 데모씬 프로그래밍 경연대회가 열린다. 92년부터 시작된 유서깊은 행사로 여기서 나온게 퓨처 크루의 세컨드 리얼리티. 2020년은 코로나로 인해 온라인으로 개최됐다.


[1] 보편적으로 사용되는 어셈블리어 중 하나인 NASM x86 방식의 어셈블리어로 구현한 Hello, World! 출력 코드. [2] 여기서 '저급 언어'란 질이 낮은 언어를 뜻하는 것이 아니라, 보다 컴퓨터에 가까운 언어를 의미한다. C언어는 추상화된 고급 언어로 분류되지만 포인터를 이용하여 메모리에 직접 접근하는 것은 저급 언어의 특징이다. [3] C언어 C++를 저급언어라고 하는 사람도 있지만 이는 사실 둘 중 하나를 분명히 하기 어려운, 저급과 고급 언어 사이에 있는 중급언어이다. 더 정확하게는, 저급 언어의 특성을 일부 지닌 고급언어이다. [4] 물론 이 두 방식은 어느 정도의 절대적인 가독성 비교를 하기엔 어렵고, 취향 및 익숙함의 문제라고 보는 것이 맞다. 먼저 오래 접하고 본 것이 어떤 문법이냐에 따라서 다르다는 말이다. [5] 하지만 요즘에는 한 가지 문법만 지원하는 경우는 거의 없으므로 사실상 취향 문제라고 할 수 있다. [6] '넷구루'라는 회사에서 티오베 순위에서 어셈블리어 순위가 높아진 이유를 IoT 증가로 평가했다. [7] $기호를 붙이는 것은 어셈블리어 AT&T 문법으로 레지스터를 메모리 위치를 나타낸다 [8] 어셈블러 또는 컴파일러가 코드로 인식하지 않는 부분이다. 프로그래머가 원하는 자연어를 쓸 수 있다. C 계열 언어들 기준으로 //를 사용하는데 가령 a = 10 //b = x 라는 코드에서 a = 10이라는 코드는 컴파일러가 인식을 하지만 b = x라는 코드는 앞에 // 하나 붙였다는 이유만으로 코드로 인식되지 않는다. 그렇기 때문에 코드가 하는 일이나 함수의 파라미터나 리턴이 의미하는 바등을 쓸 수 있다. [9] 하지만 우리가 쓰는 모든 프로그램은 그 명령어들이 잘 조합되고 배열되어서 만들어진 결과물이다. [10] 사실 난해한 프로그래밍 언어와 같은 종류에서도 최소한의 명령어만 가지고 튜링 완전인 언어들이 많이 있고, 이런 언어들 역시 명령어를 외우는 것 자체는 어렵지 않다. 그걸 이용해서 실제 프로그램을 짜는 것이 고급 언어보다 어려울 뿐... [11] 객체지향형 프로그래밍어인 Python으로 위의 어셈블리 언어와 똑같은 결과를 출력하려면 print("Hello, world") 한 줄이면 충분하다. [12] '상수'라는 뜻이다. 윗 단락 예시에서 10을 더했듯이 고정된 수를 더할 때 쓰인다. 기억장치에서 숫자를 불러올 필요 없이 바로(immediately) 더할 수 있기 때문에 immediate라는 이름이 붙었다. [13] 물론, C 언어도 만들어질 당시에 에디터의 자동완성 기능 같은 건 상상할 수도 없었기 때문에 오늘날 함수명이나 변수 하나가 심하면 한줄씩 차지하는 언어들에 비하면 타수를 줄이기 위하여 strxfrm(string transformation)이나 fma(fused multiply-add) 등의 라이브러리 함수명처럼 축약형을 애용하는 전통을 따르는 편이다. [14] 심지어 지금은 요청이 없지만 배송 중에 200번대 집에서도 배송 요청이 들어오곤 한다는 것까지 알아내서 이를 예측해 미리 200번대 호수에 배송할 쌀까지 실어놓고 동네를 도는 배송 계획도 짤 줄 안다! 전문 용어로는 분기 예측, 슈퍼스칼라 예측 등으로 불린다. 정확히는 CPU가 하는 일이지만 컴파일러가 보조해 주지 않으면 작동하지 않는 기능이다. 그 덕에 높으신 분이 쌀을 주문하는 주소를 알아낼 수 있는 부작용이 생기기도 했다. [15] 한때 본체에 ' 터보 버튼'이라는 것이 있었던 이유이기도 하다. [16] 어느 정도로 발적화였냐면, while(1){ ... } 이 for(;;){ ... } 보다 느린 컴파일러가 많았다. 오늘날 컴파일러는 둘다 무한루프인 걸 알기 때문에 속도가 같지만, 과거 컴파일러의 경우에는 while 문의 경우 루프를 돌 때마다 항상 저 1 을 테스트했기 때문에 속도가 떨어졌다. 때문에, 연식이 좀 된 프로그래머들의 경우 지금도 습관적으로 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] 1989년 터보C 2.01이 인라인 어셈블리를 지원하는 등 볼랜드사의 터보C, 터보파스칼 컴파일러가 인라인 어셈블리를 쓸 수 있게 된 다음에 좋아한 개발자가 많았다. [25] 컴파일러는 말 그대로 편집, 또는 수집을 한다는 소리이다. 예를 들어, C언어의 컴파일러는 소스 코드를 목적 코드로 바꾼 뒤, 여러 컴파일에 필요한 파일을 모은 후에 바이너리(실행 파일)로 만들어 출력을 한다. 어셈블리어를 기계어로 바꾸는 과정에는 그런 거 없다. 그러기에 어셈블러와 컴파일러로 나누는 것이다. [26] 그 당시 에니악이 한 대당 가격으로 50만달러인데 지금 가치로는 6백만달러. 72억원 정도이다. 거기다 진공관은 자주 깨지니 그만큼 갈아주고, 안에 벌레같은 게 들어오는 순간 개고생을 해야 했다. [27] 물론 아무리 어셈블리어로 짜도 못짜는 사람은 더럽게 랙 걸린다. 그만큼 크리스 소이어의 실력이 대단하다는걸 알 수 있다. [28] 트랜스포트 타이쿤은 386DX에서도 돌아간다! [29] 아타리 쇼크는 이런 게임개발시간과 관련있게 발생했다. 아타리는 그당시 굉장히 잘나가는 게임회사였고, 다음 게임을 준비하는데 그 다음작품이 E.T.게임이었다. E.T.는 그 당시 인기영화였기에, 영화를 배경으로 한 게임이 잘 나갈 것이라 생각한 것. 그래서 아타리는 E.T.의 판권을 구입했고, 크리스마스 시즌에 맞춰 게임을 개발하려는데 만들 시간이 부족한 것이었다. 시즌에 맞춰 게임을 만들다보니, 게임은 에러가 많았고 별 다른 기능이 많지 않았던것. 때문에 대량 리콜사태가 발생했고, 이것이 아타리 쇼크이다.(그러나 게임개발에 쓰이는 코드를 아타리가 어셈블리어로 했는지는 모르지만 그렇게 생산성이 좋지못한 언어로 제작함으로써 제작기간이 오래걸리는것이 문제였다.현대에는 마인크래프트를 일주일만에 만들어 버리고 아타리 게임을 적으면 2시간 많으면 3일안에 만들 수 있다. [30] MS의 매크로 어셈블러와 베이직 인터프리터는 IBM PC와 함께, 그리고 볼랜드의 터보 시리즈 컴파일러가 8비트 컴퓨터용으로 출시됐지만, 당시 컴퓨터책과 잡지에서는 C언어를 저급언어라 부르면서도 어셈블리어 강좌를 연재했고, 개인프로그래머는 변환테이블을 보고 핸드어셈블한 걸 기계어 모니터 프로그램으로 직접 입력하기도 했다. 디버그는 트레이스(TRON, TROFF)를 이용해 레지스터와 포인터, 플래그를 직접 보며 근성으로! [31] 기본적으로 카세트 테이프는 CD보다 제작 단가가 높다. 90년대 초반까지만 해도 조금이라도 돈을 더 아끼려고 오래된 어학 테이프 등을 지우고 음악을 녹음해서 담고 다니던 시절이 있었다. [32] http://xcoolcat7.tistory.com/292에서 발췌 [33] 다만 이진 파일 속에서 바이러스를 찾는다는게 딱히 기계어나 어셈블리어와 상관이 있는 건 아니다. 이런 경우는 바이러스가 파일 내부를 건드리는 부분만 잡아서 검출만 할 수 있으면 되기 때문에 파일 자체의 구조 분석이 더 자주 쓰인다. 그 시대 프로그램들이 다 그랬듯이 V1도 그 흐름을 따라서 어셈블리어로 개발되었을 뿐이다.