CS

Compiler 언어와 interpreter 언어는 정확하게 어떤 차이가 있을까?

DEV_GOLF 2023. 4. 8. 20:34
반응형

대표적으로 필자가 사용하는 언어인 Java와 Kotlin은 compile 방식으로 해석되고 있습니다. 그리고 compiler로 해석된 자바 바이트 코드는 JVM의 interpreter로 기계어로 번역되어 실행되고 있습니다. 그리고 OS 코드영역에 올라가 OS에서 기계어를 읽어 적절한 동작을 합니다.

 

그렇다면 Java나 Kotlin은 컴파일 방식을 택했고 JVM은 왜 interpreter 방식을 택했을까요? 이 둘의 차이점은 무엇일까요?

 

⚙️ Compiler 

compiler는 소스코드를 기계어로 해석합니다. 과정은 다음과 같습니다.

  1.  소스 코드 읽기 : 컴파일러는 소스 코드 파일을 읽어들입니다.

  2. 어휘 분석 : 소스 코드를 토큰이라는 작은 요소로 분해합니다. 이 단계에서 컴파일러는 주석, 공백, 탭 문자 등을 제거하고, 키워드, 연산자, 식별자, 리터럴 등의 토큰으로 분류합니다.

  3. 구문 분석 : 토큰들을 구조화 하여 추상 구문 트리를 생성합니다. 이 과정에서 문법에 따라 토큰들을 조합하여 프로그램의 구조를 나타내는 트리 형태의 데이터 구조를 생성합니다. 

  4. 의미 분석 : 추상 구문 트리를 검사하여 프로그램의 의미를 검증합니다. 타입 검사 변수의 정의 및 사용 검사, 함 수 호출의 유효성 검사 등이 수행됩니다. 오류가 발견되면 컴파일러는 에러 메시지를 생성하고 컴파일 과정을 중단합니다.

  5. 중간 코드 생성 : 의미 분석이 완료되면 추상 구문 트리를 중간 표현으로 변환합니다. 중간 코드는 소스 코드와 기계어 사이에 있는 중간 단계로 컴파일러에 의해 추가적인 최적화가 수행될 수 있습니다. 
  6. 최적화 : 중간 코드에 대한 다양한 최적화 기법을 적용하여 프로그램의 실행 효율을 향상 시킵니다. 이 과정에서 불필요한 코드 제거, 상수 폴딩, 루프 최적화 등의 작업이 수행됩니다.
  7. 기계어 생성 : 최적화된 중간 코드를 기계어 (바이너리 코드)로 변환합니다. 이 단계에서 기계어 명령어를 생성하고 메모리 할당, 레지스터 할당 등과 같은 저수준 작업을 수행합니다.

  8. 링킹 : 기계어로 변환된 코드에 필요한 외부 라이브러리, 함수, 모듈 등을 연결합니다. 

위 과정을 보면 알 수 있듯이 컴파일러의 가장 큰 특징은 코드 전체를 통째로 해석합니다. 하지만 이로인한 이점도 존재하는데 컴파일러는 실행 파일을 생성하기 때문에 보안적으로 소스 코드가 직접 외부에 노출 되지 않아 안전하고 JIT Compiler 같은 경우는 캐싱을 하기 때문에 자주 호출 된다면 Interpreter 보다 좋은 perfomance를 보여줄 수 있습니다. 

 

그럼 Interpreter 방식을 살펴보겠습니다.

⚙️ Interpreter 

Interpreter도 compiler와 목적은 같습니다. 바로 과정을 살펴 봅시다.

  1. 소스 코드 읽기 : 소스 코드를 한 줄씩 읽어들입니다.
  2. 어휘 분석 : 소스 코드를 토큰이라는 작은 요소로 분해합니다. 이 단계에서 컴파일러는 주석, 공백, 탭 문자 등을 제거하고, 키워드, 연산자, 식별자, 리터럴 등의 토큰으로 분류합니다.

  3. 구분 분석 : 토큰들을 구조화 하여 추상 구문 트리를 생성합니다. 이 과정에서 문법에 따라 토큰들을 조합하여 프로그램의 구조를 나타내는 트리 형태의 데이터 구조를 생성합니다. 

  4. 의미 분석 : 추상 구문 트리를 검사하여 프로그램의 의미를 검증합니다. 타입 검사 변수의 정의 및 사용 검사, 함 수 호출의 유효성 검사 등이 수행됩니다. 오류가 발견되면 컴파일러는 에러 메시지를 생성하고 컴파일 과정을 중단합니다.

  5. 실행 : 의미가 분석 되면 추상 구문 트리를 순차적으로 실행합니다. 이 과정에서 프로그램의 각 문장이 실행되고 변수 값이 계산되며, 함수가 호출됩니다. 인터프리터는 소스 코드를 한 줄씩 실행하므로, 실행 시점에서 소스 코드에 대한 변경사항이 즉시 반영됩니다.

Interpreter는 소스 코드를 한 줄씩 읽어들이기 때문에 전체적인 속도는 compiler에 비해 느릴 수 있지만 소스 코드의 수정이 빈번하게 발생하는 경우 유리할 수 있습니다. 또한 디버깅 기능을 제공해주기 때문에 오류가 났을 때 좀 더 원인을 파악하기 쉬울 것입니다. 

🤔 둘의 차이 ?

우선 Compiler는 코드를 기계어로 번역하는 과정에서 새로운 실행파일을 만드는데 반해 Interpreter는 소스 코드를 한 줄씩 읽어들이면서 실행합니다. 

 

이 과정의 차이 때문에 Compiler 실행 파일 생성으로 인해 Interpreter에 비해 파일 크기 자체가 크고 수정이 필요한 경우 Re-compile을 해야합니다. 하지만 Interpreter는 수정된 부분만 읽어들이기 때문에 수정 후 바로 실행이 되어 변경이 많은 경우에는 Interpreter가 유리할 수 있습니다. 

하지만 Compiler는 직접적으로 소스 코드를 노출 하지 않고 실행파일로 읽히기 때문에 보안적으로 안전합니다. 이 외에 디버깅이 어렵고 쉽고의 차이도 존재합니다. 

 

🧑‍💻 JVM과 그 언어들은 그러면 어떻게 처리할까?

JVM은 Just-In-Time 이라는 compiler를 이용하여 자바 바이트 코드로 먼저 컴파일 한 뒤 Interpreter를 이용하여 읽어 들이기 때문에 Interpreter 실행 속도를 개선하였고, 또한 Interpreter 방식으로 기계어 번역을 하기 때문에 디버깅이 가능합니다. 또한 JIT Compiler는 한 번 컴파일 된 실행파일을 캐싱하여 저장하기 때문에 자주 호출되는 경우 캐시를 가져와 유리하게 성능을 가져갈 수 있습니다. 

 

정리

뭐가 더 좋은지를 비교하는 글이 아니란걸 미리 알려드립니다. 결국 각자의 장점이 있는거고 JVM과 그 외 언어들 같이 혼합하여 사용하는 경우도 있습니다. 그리고 적절한 장점을 모아 보안적으로도 안전하고 성능또한 개선된 방식으로 애플리케이션을 실행시키고 있다는 점을 돌아보았을 땐 둘 의 특성을 잘 고려하여 섞으면 더 좋은 성능의 실행파일을 만들 수 있습니다.