2022. 10. 23. 16:47ㆍJavaScript
어제부터 갑자기 웹에서 티스토리가 안 들어가져서 당연히 카카오 사태 때문에 발생한 오류로 인식하고 하염없이 기다리는중 너무 복구가 안돼 오픈 채팅방을 찾아가 질문을 하니 나만 안되는 것이었다. 나의 문제인 점을 인지하고 먼저 쿠키를 싹 정리했는데 첫 번째 조치 시도 만에 해결이 되었다........
우선 #11에서의 내용은 간략하게만 알고있었는데 제이크 님의 문서를 읽다 보니 좀 더 딥하게 알아보고 싶은 욕구가 생겨 탐구를 해보았다.
v8엔진에 대해 알아보기전에 일반적으로 소스코드는 어떤 원리로 실행되는지에 대해 알아보자
코드 실행의 기본 원리
- 프로그래밍 언어로 코드를 작성한다.
- 인터프리팅, 컴파일 등의 과정을 통해 0,1로 이루어진 기계어로 변환된다.
- 0,1로 이루어진 기계어 코드가 ‘메모리’라는 곳에 저장된다.
- CPU는 메모리에 있는 0,1로 이루어진 코드를 읽어 on/off전기 신호로 실행시킨다.
*on/off 전기신호는 이진수 계산으로 각종 연산을 수행하며, 사실 메모리에 저장된 데이터도 전기 신호이다.
컴파일?
- 어떤 언어의 코드 전체를 다른 언어로 바꿔주는 과정이다.
컴파일러?
- 고급 프로그래밍 언어를 실행 프로그램으로 만들기 위해 저급 프로그래밍 언어(어셈블리어 언어, object코드, machinecode)로 바꾸는 데 사용된다.
컴퓨터는 멍청하기 때문에 0과 1(기계어)밖에 이해를 못 한다. 따라서 컴퓨터에게 어떤 명령을 시키려면 이진법으로 작성된 코드로 변환해줘야 한다.
언어의 종류
1) 고급언어
- 고급 프로그래밍 언어(High-Level-language)란 사람이 이해하기 쉽게 작성된 프로그래밍 언어로서, 저급 프로그래밍 언어보다 가독성이 높고 다루기 간단하다는 장점이 있다.
- 컴파일러나 인터프리터에 의해 저급 프로그래밍 언어로 번역되어 실행된다.
let result = 1 + obj.x;
2) 바이트코드(Bytecode)
- 고급언어로 작성된 소스 코드를 가상 머신이 이해할 수 있는 중간 코드로 컴파일한 것을 말한다.
- 가상 머신은 이 바이트코드를 각각의 하드웨어 아키텍처에 맞는 기계어로 다시 컴파일한다.
- 어셈블리어에 가까운 형태를 띠고 어떨 때는 가상 머신용 오브젝트 코드까지 바이트코드라 부른다.
LdaSmi [1]
start r0
LdaNamedProperty a0, [0], [4]
Add r0, [6]
3)기계어(Machine code)
- CPU가 직접해독하고 실행할 수 있는 비트 단위로 쓰인 컴퓨터 언어를 말한다.
10101010010101001 1001010
참고) 어셈블리어(assembly language)
- 기계어로 구성된 명령어를 사람이 알아보기 쉬운 니모닉 기호(mnemonic symbol)를 정해
- 기계어와 일대일 대응이 되는 컴퓨터 프로그래밍의 저급 언어이다.
명령어 예제 설명 분류 (오랜만에 보니 토나오네..)
PUSH | push %eax | eax의 값을 스택에 저장 | 스택조작 |
POP | pop %eax | 스택 가장 상위에 있는 값을 꺼내서 eax에 저장 | 스택조작 |
MOV | mov %eax, %ebx | 메모리나 레지스터의 값을 옮길 때 사용 | 데이터 이동 |
DEC | dec %eax | %eax의 값을 1감소시킨다. | 데이터 조작 |
INC | inc %eax | %eax의 값을 1증가시킨다 | 데이터 조작 |
고급언어는 어떤 방식으로 기계어로 변환될까?
코드 해석/변환 방식
- 컴파일러(Compiler)
- 인터프리터(interpreter)
:코드를 한 줄씩 읽어 내려가며 중간 단계의 Bytecode로 변환한다.(이 과정을 ‘인터 프리팅’이라고 한다)
❗자바스크립트는 인터프리터 언어이다.
그럼, 자바스크립트 코드는 구체적으로 어떻게 기계어로 변환되어 실행될까?
자바스크립트 구동원리
:자바스크립트 > Byte code > 기계어 > CPU 코드 실행
1) 자바스크립트 코드 작성
- 개발자가 자바스크립트 코드를 작성한다.
2) Byte code 변환
- 자바스크립트 엔진은 코드를 위에서부터 한 줄씩 해석하면서(인터프리터 언어) 가상 머신이 이해할 수 있는 Byte code로 변환한다.
3) 기계어 변환
- 가상 머신은 Byte code를 CPU가 이해할 수 있는 기계어로 변환한다.
- 단, CPU 마다 기계어를 다르게 해석하므로 가상 머신은 CPU별로 최적화된 기계어를 만들어 낸다.
- 따라서 개발자는 CPU마다 다른 자바스크립트 코드를 작성할 필요가 없다.(하나의 자바스크립트 코드만 작성하면 된다.)
4) CPU 코드 실행
- 컴퓨터의 CPU는 가상 머신이 만들어낸 기계어를 수행한다.
- 기계어(전기신호)대로 메모리에 데이터를 저장하고, 읽고 연산하고, 출력하는 작업 등을 수행한다.
자바스크립트 엔진은 어떤 방식으로 동작할까?
자바스크립트 엔진 V8
- V8은 C++ 작성된 Google의 오픈 소스 고성능 javaScript 및 WebAssembly 엔진이다.
- Chrome 및 Node.js에 사용된다.
작동원리
- V8엔진은 javaScript코드를 가져와서 파서(Parser)에게 넘긴다.
- 파서(Paser)는 소스코드를 분석한 후 AST(Abstract Syntax Tree), 추상 구문 트리로 변환된다.
- AST(Abstract Syntax Tree)를 lgnition에게 넘긴다. << 이 친구가 바로 자바스크립트를 Bytecode로 변환하는 인터프리터이다.
- 앞의 과정을 통해 우리의 소스 코드가 실제로 작동하게 된다.
📝 자주 사용되는 코드는 TurboFan으로 보내져서 Optimized Machine Code, 즉 최적화된 코드로 다시 컴파일된다. 그러다 사용이 덜 된다 싶으면 Deoptimizing(최적화 해제) 하기도 한다.
❗ V8은 자바스크립트를 최적화하는 데 사용되지만 c++로 작성되었으며 다중 스레드 방식을 사용하여 모든 작업을 한 번에 관리한다.
😄 V8은 원래 8기 통 엔진의 종류를 의미하는 단어다.
lgnition? 엔진에 시동걸 때 사용하는 점화기를 의미한다.
과부하되면 코드가 뜨거워져 TurboFan으로 최적화해서 과열을 식혀준다.
컴파일러가 훨씬 빠른데 V8엔진이 컴파일러 대신 인터프리터를 사용하는 이유는 뭘까?
- Ignition 인터프리터를 사용하는 주된 이유는 메모리 사용량을 줄이는 것이다.
- DEEP.. 컴퓨터가 해석하기 쉬운 바이트 코드로 변환함으로써 원본 코드를 다시 파싱 해야 하는 수고를 덜고 코드의 양도 줄이면서 코드 실행 때 차지하는 메모리 공간을 절약한다.
- 전체 프로그램을 컴파일하는 컴파일러와 달리 인터프리터는 필요한 라인만 컴파일하기 때문이다.
추상 구문 트리(Abstract Syntax Tree)
- 추상 구문 트리는 컴파일러 소스코드의 추상 구조를 구축하는 데 사용된다.
- 대부분의 고급언어는 AST를 사용하여 높은 수준의 코드 표현을 낮은 수준으로 변환한다.
- 코드를 AST로 변환하면 변수 유형, 위치, 명령문의 순서 등과 같은 코드에 대한 필요한 세부 정보가 포함된다.
- 그러므로 컴파일러는 주석과 같은 불필요한 항목을 처리할 필요가 없다.
JavaScript코드를 가져와서 AST를 생성해보자.
//함수 선언
function add(x,y) {
let result = x+y;
console.log(result);
}
//함수 호출
add(5,10)
esprima를 이용하면 코드를 AST로 변환해준다.
{
"type": "Program",
"body": [
{
"type": "FunctionDeclaration",
"id": {
"type": "Identifier",
"name": "add"
},
"params": [
{
"type": "Identifier",
"name": "x"
},
{
"type": "Identifier",
"name": "y"
}
],
"body": {
"type": "BlockStatement",
"body": [
...
],
"kind": "let"
},
...
],
"sourceType": "script"
}
- AST는 코드의 각 줄에 대해 키 값 쌍을 정의한다.
- 초기 유형 식별자는 AST가 프로그램에 속한다는 것을 정의하고 모든 코드 라인은 객체의 배열인 본문 내부에 정의된다.
- 주석은 무시한다.
최적화 프로세스와 AST사용 외에도 V8은 JavaScript의 성능을 향상하기 위해 무엇을 더 사용할까?
JavaScript 코드 최적화를 위한 히든 클래스
- JavaScript는 동적인 언어다. 즉, 개체에서 속성을 즉석에서 추가하거나 제거할 수 있다.
그러나 이 접근 방식은 더 많은 동적 조회를 요구하므로 JavaScript 성능이 저하된다.
숨겨진 클래스의 작동 방식
새 객체를 생성할 때 V8엔진은 이에 대한 새로운 히든 클래스를 생성한다.
그 후 새 속성을 추가하여 동일한 개체를 수정하면 V8 엔진은 이전 클래스의 모든 속성을 사용하여 새 히든 클래스를 생성하고 새 속성을 포함한다.
빈 객체(const userObject = ()를 생성하면 V8은 오프셋 없이 해당 히든 클래스(C01)를 생성한다.
다음 새 속성을 추가하여 해당 개체를 수정한다. (userObject.name = “Chameera.”)
이제 V8엔진은 새로운 히든 클래스(C02)를 생성하여 이전 히든 클래스(C01)의 모든 속성을 상속하고 이름 속성을 오프셋 0에 할당한다.
이렇게 하면 속성 이름에 액세스 할 때 컴파일러가 사전 조회를 우회할 수 있으며 V8은 클래스 C01을 직접 가리킨다.
이 개체에 다른 속성을 추가하면 동일한 프로세스가 발생한다. 또 다른 숨겨진 클래스가 생성되고 오프셋으로 이전 속성과 새 속성이 모두 포함된다.
히든 클래스 개념을 사용하면 사전 조회를 우회할 수 있을 뿐만 아니라; 유사한 객체가 생성되거나 수정될 때 이미 생성된 클래스를 재사용할 수 있다.
고성능 JavaScript 코드 작성
따라서 JavaScript 코드의 성능을 최대화하려면 동적 속성 추가를 줄여야 할 수 있다.
NodeJS에서 루프를 실행한다고 가정한다. 개체에 대한 동적 속성을 추가하면 루프 내에서 성능 차이가 나타난다. 따라서 루프 외부에 속성을 만들고 루프 내부에 동적으로 추가하는 대신 사용하는 것이 좋다. 따라서 V8이 기존 히든 클래스를 재사용할 때 훨씬 더 나은 성능을 보일 것이다.
Reference
https://curryyou.tistory.com/237
https://ko.wikipedia.org/wiki/컴파일러
https://ko.wikipedia.org/wiki/기계어
https://blog.bitsrc.io/secret-behind-javascript-performance-v8-hidden-classes-ba4 d0 ebfb89 d
'JavaScript' 카테고리의 다른 글
자바스크립트 개발자라면 알아야할 33가지 개념 #13 DOM 이해하기 (0) | 2022.10.25 |
---|---|
자바스크립트 개발자가 알아야할 33가지 #12 비트연산 실제로 활용하기 !! (1) | 2022.10.24 |
자바스크립트 개발자가 알아야할 33가지 #10 스케줄링:setTimeout과 setInterval (0) | 2022.10.20 |
자바스크립트 개발자가 알아야할 33가지 #8 IIFE마스터하기 (0) | 2022.10.19 |
자바스크립트 개발자가 알아야할 33가지 #표현식(Expression)과 문장(Statement) (0) | 2022.10.19 |