[네이버클라우드] AIaaS 개발자 과정/웹 프로그래밍

[네이버클라우드캠프] 2023.5.23 웹 프로그래밍(1) - 컴파일과 인터프리트(Hybrid, JIT, AOT)

_꼬마돌 2023. 5. 23. 09:35
반응형

컴파일과 인터프리트 (Hybrid, JIT, AOT)

컴파일 방식

C언어 코드가 리눅스OS 에서 실행되는 과정
1. C언어 코드 작성
C언어로 코드 작성. 일련의 명령문으로 구성되어 프로그램의 동작을 정의한다. 

2. 헤더 파일 포함
소스 코드 파일(.c 확장자)에서는 프로그램에서 사용할 함수나 라이브러리의 선언을 포함하는 헤더 파일을 포함해야 한다. 주로 '#include <stdio.h>'와 같은 형태로 사용되며, 이는 표준 입출력 함수인 'printf()'를 사용하기 위해 stdio.h 헤더 파일을 포함한다는 의미이다. 

3. 전처리 단계 (pre-compile, preprocessor)
컴파일 이전에 전처리기(preprocessor)가 소스 코드 파일을 처리한다. 전처리기는 '#include'와 같은 전처리 지시문을 처리하고, 매크로를 확장하거나 특정 조건에 따라 코드 블록을 제외하는 등의 작업을 수행한다. 

4. 컴파일 단계
전처리된 소스 코드 파일(. c)이 컴파일러에 의해 컴파일된다. 컴파일러는 소스 코드를 읽어 기계어에 가까운 중간 표현 형태로 변환한다. 이 중간 표현 형태는 어셈블리어 코드로 표현된다. 

5. 어셈블 단계
어셈블러(assembler)는 어셈블리어 코드 파일(.s 확장자)을 받아 기계어 코드 파일 또는 오브젝트 파일(.o 확장자)로 변환한다. 어셈블 단계에서는 어셈블리어 코드가 기계어로 번역된다. 

6. 링크 단계
링커(linker)는 오브젝트 파일과 필요한 라이브러리 파일들을 결합하여 실행 가능한 바이너리 파일을 생성한다. 이 단계에서는 'printf()'와 같은 함수를 포함한 외부 참조들이 해결된다. 주로 'gcc' 명령어를 사용하여 링크를 수행한다. 

7. 실행
생성된 바이너리 파일은 리눅스 운영 체제에서 실행된다. 운영 체제는 프로그램을 메모리에 로드하고, CPU가 명령어를 실행하여 프로그램이 동작하게 한다. 프로그램 실행 중 'main()' 함수가 호출되며, 프로그램은 'main()'
 함수의 시작 부분에서 실행을 시작한다. 

CPU와 RAM 단계는 컴파일 단계 이후인 실행 단계에서 이루어지는데, CPU는 명령어를 실행하고, RAM은 프로그램의 데이터와 코드를 저장하며 CPU에 필요한 데이터를 제공한다. 이 단계에서는 프로그램이 메모리에 로드되어 실제로 동작하게 된다. 
이러한 과정을 거져 C언어 코드는 컴파일되어 실행 가능한 프로그램으로 변환되며, 이후에는 운영 체제에서 해당 프로그램을 실행할 수 있다. 

Function Prototype (함수 프로토타입)
함수의 시그니처 또는 선언부. 함수가 어떤 형태로 정의되어야 하는지를 미리 컴파일러에게 알려주는 역할을 한다. C 와 C++ 과 같은 정적 타입 언어에서 주로 사용된다. 

함수 프로토타입은 함수의 이름, 매개변수의 타입과 이름, 반환 값의 타입으로 구성되며, 일반적으로 함수의 정의보다 앞에 선언된다. 함수 프로토타입을 사용하면 함수 호출 시 인자의 타입이나 반환 값의 타입이 예상대로 맞는지 컴파일러가 검사할 수 있다. 또한, 다른 소스 파일에서 함수를 사용할 때 필요한 정보를 제공한다. 함수 프로토타입은 코드의 가독성과 유지보수성을 향상시키며, 컴파일러에게 올바른 함수 사용법을 알려주는 중요한 역할을 한다. 

 

컴파일

gcc -E hello.c > hello.i
C 소스코드 파일 (hello.c)을 전처리하여 그 결과를 'hello.i' 라는 파일로 저장하는 명령어. 

gcc -S -emit-llvm hello.i
gcc 컴파일러를 사용하여 'hello.i' 파일을 llvm 어셈블리 파일로 변환하는 명령어. 
gcc 컴파일러에 '-S' 옵션을 주어 어셈블리 출력을 요청하고, '-emit-llvm' 옵션을 사용하여 llvm 어셈블리로 변환한다. 결과는 'hello.i' 파일과 동일한 이름의 llvm 어셈블리 파일인 'hello.s' 로 생성된다. 

gcc -o hello.s
'hello.s' 파일을 컴파일하여 실행 파일을 생성하는 명령어.
'-o' 옵션은 출력 파일의 이름을 지정하는데 사용되며, 이 경우에는 'hello.s' 라는 어셈블리 언어 파일을 컴파일하여 실행파일로 생성한다. 

gcc hello.o -o hello.exe
'hello.o' 파일을 링킹하여 'hello.exe' 라는 실행파일을 생성하는 명령어.
'hello.o' : object 파일인 'hello.o'를 링킹. C 소스 코드를 컴파일하여 생성된 오브젝트 파일.
'-o hello.exe' : 출력 파일의 이름을 'hello.exe'로 지정. 링킹된 실행 파일을 'hello.exe'로 생성.

인터프리트 방식

인터프리트 방식은 프로그램을 실행하기 전에 컴파일하지 않고, 소스 코드를 한 줄씩 해석하고 실행하는 방식이다. 컴파일 방식과는 다른 원리를 가지며, 코드를 직접 실행하기 때문에 중간 단계의 컴파일된 형태를 생성하지 않는다.

일반적인 인터프리트 방식은 다음과 같은 과정을 거친다:
1. 소스 코트를 실행하고자 하는 인터프리터에 전달.
2. 인터프리터는 소스 코드를 한 줄씩 읽고 해석하여 실행.
3. 각 줄을 해석하고 실행하는 동안, 인터프리터는 필요한 경우 즉석에서 해당 줄을 기계어로 변환하여 실행.
4. 소스 코드의 마지막 줄까지 읽고 실행이 완료되면 프로그램 종료.

인터프리트 방식은 코드를 수정하고 즉시 실행해볼 수 있으므로 개발과 디버깅이 편리할 수 있다. 하지만 인터프리트 방식은 실행 시간에 코드를 해석하고 실행하는 과정이 필요하므로, 전체적으로 컴파일 방식에 비해 느리다.

Java

Hybrid 방식   =   컴파일 방식   +   인터프리트 방식

Java는 일반적으로 하이브리드 방식으로 동작하는 언어이다. Java 소스 코드는 컴파일과 인터프리트 단계를 거치며, 아래의 과정을 따른다:

1. 컴파일
Java 소스코드 (.java 파일)는 Java 컴파일러 ('javac')에 의해 바이트 코드로 컴파일된다. 이 바이트 코드는 JVM(Java Virtual Machine)에서 실행할 수 있는 중간 형태의 형식이다. 컴파일된 바이트 코드 파일은 '.class' 확장자를 갖는다.

2. 인터프리트
JVM은 컴파일된 바이트 코드를 실행하는 인터프리터로 동작한다. JVM은 바이트 코드를 한 줄씩 해석하고 실행하며, 필요에 따라 바이트 코드를 기계어로 변환하여 실행한다. 이 과정은 JVM 내부에서 이루어지므로, JVM은 플랫폼에 종속되는 실행 환경을 제공한다. 

Java의 하이브리드 방식은 개발자가 Java 소스코드를 컴파일하여 JVM이 인터프리트하는 중간 형태의 바이트 코드로 변환하면서 발생한다. 이렇게 하면 Java 프로그램은 다양한 플랫폼에서 실행 가능하며, JVM은 실행 환경의 특정 플랫폼에 맞게 최적화된 기계어로 바이트 코드를 해석하고 실행한다. 

 

Java가 하이브리드 방식으로 동작하는 이유

Java가 하이브리드 방식으로 동작하는 이유
1. 플랫폼 독립성
Java는 "Write Once, Run Anywhere" 라는 원칙을 가지고 있다. 즉, 한 번 작성된 Java 소스 코드는 여러 플랫폼에서 실행될 수 있어야 한다. 이를 가능하게 하기 위해 Java 소스 코드는 컴파일 단계에서 바이트 코드로 변환되고, 이 바이트 코드는 JVM에서 실행된다. JVM은 각 플랫폼에 맞게 구현된 인터프리터나 JIT(Just-In-Time) 컴파일러를 사용하여 바이트 코드를 실행한다. 

2. 동적 로딩과 실행 중 수정
Java는 동적으로 클래스를 로딩하고 실행할 수 있는 기능을 제공한다. 이는 애플리케이션 실행 중에 필요한 클래스를 동적으로 로딩하여 사용할 수 있도록 한다. 이러한 동적 로딩은 어플리케이션의 유연성과 확장성을 높이는 데 도움을 준다. 또한, 실행 중에 클래스 파일을 수정하면 해당 클래스를 다시 컴파일하고 배포하지 않아도 되는 장점이 있다. 

3. 성능 최적화
Java는 JVM이 바이트 코드를 해석하고 실행하는 인터프리터를 사용하면서도 성능을 향상시키기 위해 JIT 컴파일러를 사용한다. JIT 컴파일러는 인터프리터가 반복적으로 실행되는 코드를 동적으로 컴파일하여 기계어로 변환한다. 이를 통해 인터프리터 방식과 컴파일 방식의 장점을 결합하여 더 높은 실행 성능을 달성할 수 있다. 

 

JIT (Just-In-Time) Compile

중간 언어 실행 방식의 실행속도 문제 해결

JIT (Just-In-Time) 컴파일은 프로그램을 실행하는 도중에 실행 코드를 실제 기계어로 컴파일하는 방식이다. JIT 컴파일러는 인터프리터와 컴파일러의 장점을 결합하여 실행 속도를 향상시키는 기술이다.

JIT 컴파일의 동작 방식
1. 인터프리터 실행
프로그램이 실행될 때, 일반적으로 인터프리터에 의해 소스 코드가 한 줄씩 해석되고 실행된다.
2. 프로파일링 정보 수집
인터프리터는 실행 중에 프로그램의 동작을 프로파일링하여 실행 주파수가 높은 코드 영역, 루프, 또는 핫 스팟(hot spot)으로 간주되는 부분을 식별한다. 이러한 프로파일링 정보는 JIT 컴파일러에게 전달된다. 
3. 컴파일
JIT 컴파일러는 프로파일링 정보를 기반으로 선택된 핫 스팟 부분을 기계어로 직접 컴파일한다. 이때, 컴파일된 코드는 최적화된 형태로 생성된다.
4. 실행
컴파일된 코드는 인터프리터가 해석하는 동안 실행된다. 이러한 JIT 컴파일은 실행 시간에 필요한 부분만을 컴파일하여 실행 속도를 향상시킨다. 나머지 부분은 여전히 인터프리터에 의해 해석되어 실행된다. 

JIT 컴파일의 주요 이점은 반복적으로 실행되는 핫 스팟 부분을 기계어로 컴파일하여 실행 속도를 크게 향상시킬 수 있다는 점이다. JIT 컴파일러는 프로그램의 실행 특성에 따라 동적으로 코드를 최적화하므로, 실행 시간에 최적화된 코드를 생성할 수 있다. 이는 인터프리터 방식보다 더 빠른 실행속도를 제공한다. 

 

AOT (Ahead of Time) Compile

중간 언어 실행 방식의 실행속도 문제 해결

AOT (Ahead of Time) 컴파일은 프로그램을 실행하기 전에 미리 전체 소스 코드를 기계어로 컴파일하는 방식이다. AOT 컴파일은 프로그램을 실행하는 동안 컴파일 단계를 거치지 않고 이미 컴파일된 기계어 코드를 직접 실행한다. 

AOT 컴파일의 동작 방식
1. 컴파일
AOT 컴파일러는 소스 코드를 전체적으로 컴파일하여 기계어로 변환한다. 이렇게 컴파일된 기계어 코드는 실행 환경에 맞게 최적화되어 최상의 실행 성능을 제공한다. 
2. 실행
AOT 컴파일된 기계어 코드는 프로그램을 실행하는 동안 직접 실행된다. 프로그램 실행 시에는 컴파일 과정이 필요하지 않으므로 기계어 코드를 바로 실행할 수 있다. 

AOT 컴파일의 주요 특징
1. 성능
AOT 컴파일은 프로그램을 실행하기 전에 이미 기계어로 컴파일되기 때문에 실행 속도가 빠르다. 컴파일 단계가 생략되므로 인터프리터나 JIT 컴파일러에 비해 초기 실행 속도가 빠르고, 최적화된 기계어 코드를 실행하여 효율적인 실행 성능을 제공한다. 
2. 이식성
AOT 컴파일은 컴파일된 기계어 코드를 실행하기 때문에 특정 플랫폼에 종속되지 않는다. 따라서 AOT 컴파일된 프로그램은 다양한 플랫폼에서 실행될 수 있다. 이는 플랫폼 독립성을 가진 언어나 프레임워크에서 중요한 요소이다.
3. 초기 배포 크기
AOT 컴파일은 전체 소스 코드를 컴파일하여 실행 파일에 포함시키기 때문에 초기 배포 크기가 크게 될 수 있다. 모든 코드가 기계어로 변환되기 때문에 실행 파일의 크기가 상대적으로 커질 수 있다. 

반응형