Javascript


Javascript 란?


자바스크립트 엔진 - V8


자바스크립트 엔진은 자바스크립트 코드를 이해하고 실행을 도와줍니다. 대표적인 엔진으로 V8 Engine (Chrome, Node.js 에서 사용)이 있으며, 각 브라우저 별로 여러가지 엔진들이 존재합니다.


V8 엔진이란?

  • Google이 주도하여 개발한 오픈소스임.
  • C++로 작성된 고성능의 자바스크립트 & 웹 어셈블리 엔진임.
  • Call Stack 과 Memory Heap으로 구성되어 있음.
  • 웹 브라우저의 성능 향상 메커니즘으로 도입되었으며 다른 엔진보다 훨씬 향상된 인터프리터가 되었음.

⇒ V8과 다른 엔진의 가장 큰 차이점은 V8 엔진의 JIT(Just In Time) 컴파일러임.

     - JIT 컴파일러는 런타임에 모든 자바스크립트를 기계어 코드도 컴파일하고 중간 코드를 생성하지 않음.

V8 엔진의 동작 과정


  1. 소스코드 파싱
  2. 자바스크립트 소스코드를 가져와서 먼저 파서(Parser)에 넘기면, 소스코드를 분석한 후 AST(Abstract Syctax Tree,추상 구문 트리) 로 변환합니다.
  3. Ignition 바이트코드(ByteCode)로 변환
  4. Ignition은 자바스크립트 코드를 바이트코드로 변환하는 인터프리터이다. 원본 소스 코드보다 컴퓨터가 해석하기 쉬운 바이트 코드로 변환하여, 수시로 코드를 파싱하는 작업을 최소화하고 코드의 양도 줄임으로써 메모리 공간도 효율적으로 관리할 수 있게 된다.
  • 인터프리터(Interpreter)란?
  • 컴파일러와 반대로 인터프리터는 프로그램 실행시 한 번에 한문장씩 번역한다. 그렇기 때문에 한번에 전체를 스캔하고 실행파일을 만들어서 실행하는 컴파일러보다 실행시간이 더 걸린다. 인터프리터는 메모리 효율이 좋다. 대표적인 언어로 Python,Ruby,Javascript 등이 있음.
  • 컴파일러란(Compiler)?
  • 프로그램 전체를 스캔하여 이를 모두 기계어로 번역한다. 전체를 스캔하기 때문에 대개 컴파일러는 초기 스캔 시간이 오래 걸린다. 하지만 전체 실행 시간만 따지고 보면 인터프리터보다 빠르다. 대표적인 언어로 C,C++,JAVA 등이 있음.
  • 자바스크립트는 정적 타이핑 언어가 아닌 동적 타이핑 언어입니다. 소스코드가 실행되기 전에는 알 수 없는 값들이 너무 많아 최적화가 힘들다는 단점이 있습니다. 때문에 모든 소스를 한 번에 해석하는 컴파일 방식이 아닌 코드 한 줄씩 실행될 때마다 해석하는 인터프리트 방식을 채택하여 세 가지 이점을 가져가고자 하였습니다.
  • 메모리 사용량 감소 : 자바스크립트 코드에서 기계어로 컴파일하는 것보다 바이트 코드로 컴파일 하는 것이 더 편하다.
  • 파싱 시 오버헤드 감소 : 바이트 코드는 간결하기 때문에 다시 파싱 하기 편하다.
  • 컴파일 파이프라인의 복잡성 감소 : TurboFan을 통한 Optimizing 혹은 Deoptimizing 처리시에도 바이트코드가 편하다.
  1. TurboFan으로 자주 사용하는 바이트코드를 컴파일
  • TurboFan이란?
    • TurboFan은 V8 v5.9 버전 이전에 사용되었던 Crankshaft 컴파일러를 완전히 대체한 최적화 담당 컴파일러임.
    • TurboFan은 바이트 코드로 수시로 변환하는 과정을 최소화하기 위해 사용됨.
    • V8 런타임 중에 Profiler에게 함수나 변수들의 호출 빈도와 같은 데이터를 모으라고 시키며 이 데이터를 이용하여 TurboFan이 자기 기준에 맞는 코드를 가져와서 최적화를 시킴.

TurboFan 최적화 기법

  • Hidden Class
    • 자바스크립트는 기본적으로 클래스가 없고 대신 Prototype이라는 개념이 있다. 자바스크립트 내에서 사용되는 Number,Boolean 등과 같은 정적 타입 데이터 외 모든 데이터를 객체로 취급됨.
    • 자바스크립트 엔진 내부의 Hidden Class라는 개념을 이용하여 각 객체에 대한 속성 값의 포인터만 가지고, 해당되는 구조를 참조함.
  • Inline Caching

Javascript 엔진 구조도

V8 엔진의 구조도를 간단히 나타낸 그림

출처 : https://joshua1988.github.io/web-development/translation/javascript/how-js-works-inside-engine/

 

엔진의 주요 두 구성요소는

  • Memory Heap : 메모리 할당이 일어나는 곳
  • Call Stack : 코드 실행에 따라 호출 스택이 쌓이는 곳

출처 : https://joshua1988.github.io/web-development/translation/javascript/how-js-works-inside-engine/

 

참고 자료: 참고 자료:

컴파일러(compiler)와 인터프리터(interpreter)의 차이

[2020.10.16] Google Chrome V8 엔진을 파헤쳐보자

자바스크립트의 동작원리: 엔진, 런타임, 호출 스택

'Web > JavaScript' 카테고리의 다른 글

[Node.js] 이벤트 디멀티플렉싱  (0) 2024.03.05
Node.js 란?  (0) 2024.03.05
[Javascript] 얕은 복사와 깊은 복사  (0) 2024.02.05

이벤트 디멀티플렉싱은 무엇인가?

이 매커니즘은 감시된 일련의 리소스들로부터 들어오는 I/O 이벤트를 수집하여 큐에 넣고 처리할 수 있는 새 이벤트가 있을 때까지 차단함.

socketA,pipeB
wahchedList.add(socketA, FOR_READ); // [1]
while(events = demultiplexer.watch(watchedList)){ //[2]
	foreach(event in events){
		//read는 블록되지 않으며 비어있을지언정, 항상 데이터를 반환함.
		data = event.resource.read();
		if(data === RESOURCE_CLOSED) demultiplexer.unwatch(event.resource);
		else resolve(data);
	}
}

[2]에서 디멀티플렉서에 감시할 것들을 추가. 이 호출은 동기식이며, 감시 대상 중 하나라도 데이터를 리턴하기 전까지 차단됨. 이때 이벤트 디멀티플렉서는 호출로부터 복귀하여 새로운 이벤트들을 처리할 수 있게됨(비동기)

[3]에서 디멀티플렉서가 반환한 이벤트가 처리됨. 여기를 Event Loop라고 부르며, 이곳에 도달했다는 것은 역으로 읽기 작업이 완료되었다는 것이므로 차단되지 않은 상황이라고 보증됨.

구체적으로, 바로 값을 가져올 수 없는 작업형 함수를 만날 경우(I/O) 일단 약속된 상수 값을 리턴시키고 해당 함수를 디멀티플렉서에 추가함. 이때 완료 후 호출될 핸들러(callback)와 이벤트가 디멀티플렉서에 들어감. 이벤트가 완료될 경우 디멀티플렉서가 이벤트를 반환함. 반환된 이벤트는 큐에 push되고, 이벤트 루프는 이벤트 큐를 순회하며 각각의 이벤트들에 대한 핸들러를 실행합니다.

출처 : 이벤트 디멀티플렉싱과 Reactor 패턴 그리고 Node.js의 구조

이벤트 디멀티플렉싱 패턴을 사용하면 Busy-waiting을 사용하지 않고도 단일 스레드 내에서 여러 I/O 작업을 처리할 수 있습니다. 기존의 동기식 블로킹 I/O가 스레드를 만들어 다중 I/O 작업을 처리했다면 이는 하나의 스레드만 사용하여 시간에 따라 작업을 분리해 처리합니다. 따라서 위의 그림처럼 유휴시간(파란색)을 최소로 줄일 수 있습니다. 또한 프로세스 간의 경쟁 혹은 여러 스레드들의 동기화 걱정이 없는 훨씬 간단한 동시성 전략을 사용할 수 있습니다.

반응자 패턴 (Reactor Pattern)

리엑터 패턴의 핵심은 각 I/O 작업과 관련된 핸들러(callback)를 갖는 것입니다. 이 핸들러는 이벤트 루프에 의해 처리되는 즉시 호출됩니다. 리엑터 패턴은 위에서 설명한 이벤트 디멀티플렉싱을 활용하는 패턴입니다.

출처 : 이벤트 디멀티플렉싱과 Reactor 패턴 그리고 Node.js의 구조

 

  1. 애플리케이션이 요청을 이벤트 디멀티플렉서에 제출하여, 새로운 IO 작업을 생성한다. 애플리케이션은 또한 작업이 완료되면 호출할 핸들러(콜백)를 지정한다. 새로운 요청을 이벤트 디멀티플렉서에 제출하는 것은 논블로킹 요청으로, 즉시 애플리케이션에 통제권을 반환한다.
  2. IO 작업 세트가 완료되면, 이벤트 디멀티플렉서가 해당 이벤트 세트를 이벤트 큐로 푸시 한다.
  3. 이 때, 이벤트 루프는 이벤트 큐 항목에서 반복된다.
  4. 각 이벤트에 연결된 핸들러가 호출된다.
  5. a. 애플리케이션 코드의 일부인 핸들러(콜백) 실행이 완료되면, 이벤트 루프를 다시 제어한다. 5b. 콜백이 실행되는 동안, 새로운 비동기 작업을 요청할 수 있으며, 이는 이벤트 디멀티플렉서에 새로운 항목 추가를 야기한다.
  6. 이벤트 큐의 모든항목이 처리되면, 이벤트 루프는 이벤트 디멀티플렉서를 다시 차단하며, 이 경우 새로운 이벤트가 가능해질 때 다시 트리거한다.

즉, 리엑터 패턴은 관찰 대상 리소스가 반응(콜백)하면 해당 이벤트의 핸들러를 추적해 실행하는 디자인 패턴입니다. 노드의 전신이라 할 수 있는 패턴입니다.

출처 : 이벤트 디멀티플렉싱과 Reactor 패턴 그리고 Node.js의 구조

Node.js는 Reactor 패턴을 사용하며 JS Core API, V8 및 libuv에 의존하는 구조라고 할 수 있습니다.

  • Binding: libuv 외 로우레벨 기능들을 JS에 랩핑하고 사용 가능하게 만들어 줌.
  • V8: 구글에서 만든 크롬용 JS 엔진. V8 덕에 Node.js 가 매우 빠르고 효율적으로 작동한다.
  • JS Core API: Node.js API 를 구현하는 자바스크립트 코어

 

참고 사이트 : Plus Ultra

'Web > JavaScript' 카테고리의 다른 글

JavaScript란?  (0) 2024.03.05
Node.js 란?  (0) 2024.03.05
[Javascript] 얕은 복사와 깊은 복사  (0) 2024.02.05

Node.js란?


  • 오픈 소스 Javascript 엔진인 Chrome V8에 비동기 이벤트 처리 라이브러리인 libuv를 결합해 만든 플랫폼임.

  • 즉, Javascript 로 브라우저 밖에서 서버를 구축하는 등의 코드를 실행할 수 있게 해주는 런타임 환경임.

  • Node.js는 단일 프로세서에서 작동하기 때문에 멀티코어를 완전히 사용하려면 코어갯수만큼의 프로세스를 띄우고 라우터나 로드 밸런서 등으로 요청을 각 프로세스로 분산시켜주어야함.

  • Node 의 핵심 철학은 비동기로 동작하여 I/O 작업일 경우 워커스레드에 던져두고 이벤트 루프는 다른일을 한다는 것이며 이때 I/O작업 처리는 libuv가 담당함.

  • 그러면, Node.js 싱글 스레드일까요?

    보통 Javascript는 싱글 스레드라고 합니다. 그럼 자바스크립트가 실행되는 환경인 노드는 싱글일까?

    ⇒ 정확하게 말하면 노드는 싱글스레드가 아닙니다.

    “자바스크립트를 실행하는 스레드는 단 하나이므로…”

  • 그러면, libuv는 뭔가요?

    libuv란 C/C++로 작성된, Node.js가 사용하는 비동기 I/O 라이브러리입니다.

    이는 사실 운영체제의 커널을 추상화한 Wrapping된 라이브러리로 커널이 어떤 비동기 API로 지원하는지 알고 있다고합니다.

    • 그러면, 어떻게 처리를하나요?

      우리가 libuv에게 파일 읽기와 같은 비동기 작업을 요청하면 libuv는 이 작업을 커널이 지원하는지 확인합니다. 만약 지원한다면 libuv가 커널에게 비동기적으로 요청했다가 응답이 오면 그 응답을 우리에게 전달해줌. 만약 요청한 작업을 커널이 지원하지 않는다면 자신만의 워커스레드가 담긴 스레드 풀을 사용함.

      libuv는 기본적으로 4개의 스레드를 가진 스레드 풀을 생성함.

      결국에는, 이벤트 루프가 싱글 스레드이며, 블로킹 I/O 관련된 작업을 처리하는 백그라운드는 멀티스레드로 이루어져있다. I/O관련 블로킹 작업은 OS또는 libuv의 thread pool에서 처리하여 이벤트 루프가 blocking되지 않게 함.

      여기서 그러면, libuv와 Event loop와의 관계가 뭐길래, 어떻게 싱글 스레드로 논블로킹 비동기 작업을 지원하는가에 대해 궁금증이 생깁니다.

      간단하게 정리하자면, Node.js는 I/O작업을 자신의 메인 스레드가 아닌 다른 스레드에 위임함으로써 싱글 스레드로 논 블로킹 I/O를 지원함. 다시 말해, Node.js는 I/O작업을 libuv에 위임함으로써 논 블로킹 I/O를 지원하고 그 기반에는 이벤트 루프가 있음.

  • 그러면, 싱글 스레드라면서 비동기 작업은 어떻게 가능할까요?

    스레드가 하나라면 동시에 하나의 작업만 처리할 수 있습니다. 어떻게 비동기 처리가 가능할까요?

    비동기란 먼저 실행된 코드의 작업이 끝나기 전에 더 나중에 실행된 코드의 작업이 끝날 수 있음을 의미합니다. 즉, 동시성을 가지고 있는 코드들이죠.

    스레드가 하나임에도 동시성이 지원되는 이유는 바로 “이벤트 루프” 라는 친구 때문입니다.

    그렇다면 이벤트 루프는 무엇일까요?

    • 이벤트 루프

      • 이벤트 루프는 Node.js가 여러 비동기 작업을 관리하기 위한 구현체입니다.

      • 이벤트 루프는 싱글 스레드이며 메인 쓰레드이며 비즈니스 로직을 수행합니다.

        console.log(’…’) 와 같은 동기 작업이 아니라 file.readFile(’test.txt’,callback)과 같은 비동기 작업들을 모아서 관리하고 순서대로 실행할 수 있게 해주는 도구임.

        Timer Phase, Pending Callbacks Phase, Idel,Prepare Phase, Poll Phase, Check Phase, Close Callbacks Phase로 구성되어있음.

        한 페이즈에서 다음 페이즈로 넘어가는 것을 틱(Tick)이라고 합니다.

'Web > JavaScript' 카테고리의 다른 글

JavaScript란?  (0) 2024.03.05
[Node.js] 이벤트 디멀티플렉싱  (0) 2024.03.05
[Javascript] 얕은 복사와 깊은 복사  (0) 2024.02.05

회사다닐 때, 정리해두었던 자료를 늦게나마 블로그에 적어보려고합니다.

먼저, spread 문법과 map을 사용한 데이터 할당

// 1번
const newList = [...rawMemeberTypeList]; 

// 2번
const newList = rawMemberTypeList.map((item) => {...item});

Q. 1번은 어떻게 복사가 될까요?

 

A. 얕은 복사로서, "newList"는 "rawMember..." 와 같은 배열 요소를 참조합니다.
기존의 배열 "rawMember" 과 새로운 "newList" 중 하나를 수정하면 다른 배열에도 영향을 미친답니다.

 

Q. 2번은 어떻게 복사가 될까요?

 

A. 깊은 복사로서, map 함수를 통해 "rawMemberTypeList" 의 각 요소를 복사하여 새로운 배열 "newList" 를 생성합니다.
이렇게 하면 "newList"와 "rawMemberTypeList" 의 복제본이므로 두 배열은 독립적으로 존재합니다.
기존 "rawMember..." 나 "newList" 중 하나를 수정해도 다른 배열에 영향을 미치지 않습니다.

 

'Web > JavaScript' 카테고리의 다른 글

JavaScript란?  (0) 2024.03.05
[Node.js] 이벤트 디멀티플렉싱  (0) 2024.03.05
Node.js 란?  (0) 2024.03.05

+ Recent posts