Node.js 비동기에 대해 알아보다가 비동기 작업이 libuv라는 라이브러리를 통해 진행된다는 걸 알게되었습니다. Node.js 구성을 보니 v8이라는 엔진과 libuv가 받치고 있던데, 크롬에서도 v8엔진을 사용하고, v8이 뭔지 궁금해서 찾아보게되었습니다.
우리가 컴퓨터에 쓴 코드는 변형되거나 컴파일되어서 기계어가 됩니다.
자바스크립트 엔진이란 자바스크립트 코드를 마이크로프로세서가 이해할 수 있는 더 낮은 수준의 언어 혹은 기계어로 변환해주는 역할을 합니다.
다양한 자바스크립트 엔진이 존재하지만, V8엔진은 Chrome과 Nodejs에서 사용됩니다.
이 v8엔진은 C++로 작성되었고 고성능의 자바스크립트 전용의 웹어셈블리 엔진입니다.
일반적으로 자바스크립트 엔진은 코드 한 줄을 해석하고 바로 실행하는 인터프리터 형식이지만, v8엔진은 자바스크립트 코드를 바이트코드로 컴파일하고 실행하는 방식을 사용합니다.
v5.9이하
현재
5.9 버전 이후부터는 Fullcodegen과 Crankshaft 두 가지를 제거하게되었습니다.
V8은 소스코드를 가져와서 Parser
에서 넘깁니다. 그리고 파싱
합니다.
파싱
이란, 소스코드를 불러온 후 AST(Abstract Syntax Tree)
로 변환하는 과정을 말합니다.
다음에 AST
를 Ignition으로 넘깁니다
JavaScript에서 Parser
은 lazy parser
을 사용합니다.
이 lazy parser
의 목표는 중복되는 함수에 대해 사전에 구문 분석함으로 함수를 건너뛸 수 있도록 하는 작업을 수행합니다.
Ignition
은 자바스크립트 코드를 바이트 코드로 변환하는 인터프리터입니다.
이후 이 바이트 코드를 실행함으로서 우리의 코드가 작동하게되고, 자주 사용하는 코드일 경우에는 TurboFan
에 보내져서 Optimized Machine Code
로 최적화된 코드로 다시 컴파일되고, 덜 사용된다 싶으면 Deoptimizing
합니다.
v5.9이하에서는 Full-codegen
을 사용하였지만, Ignition
을 사용함으로서 완전히 대체되었습니다. Full-codegen
은 전체 소스코드를 한번에 컴파일 하였습니다. 하지만 이방식은 컴파일할 때 메모리 점유를 굉장히 많이 했기 떄문에 코드를 한줄 한줄 실행될 때마다 해석하는 인터프리터 방식을 채택 Ignition
으로 대체되었습니다.
Ignition
으로 바뀌면서 세 가지 이점을 가지게되었습니다,
Optimizing
, Deoptimizing
이든 바이트 코드 하나만 생각하면 되기 때문바이트 코드는 node --print-bytecode test.js
하면 볼 수 있습니다.
(후에.. 레지스터에 대해서 공부하게 되면 더 뜯어봐야겠습니다.)
Crankshaft
컴파일러를 대체하는 최적화 담당 컴파일러입니다.
최적화 기법으로는 히든 클래스
와 인라인 캐싱
등 여러가지 기법을 사용합니다.
자바스크립트는 동적 타입의 언어로, 객체에서 속성을 즉시 추가하거나 제거할 수 있습니다. 이 떄문에 많은 성능을 요구하는 동적 조회를 더 필요로합니다. v8엔진은 히든 클래스를 사용해 이 문제를 해결할려고합니다
히든 클래스는 새 객체를 생성할 때 이에 대해 히든 클래스를 생성합니다. 그런다음 새 프로퍼티를 추가해 동일한 객체를 수정한다면 v8엔진에서 이전 클래스의 모든 프로퍼티가 포함된 새 히든 클래스를 만들고 새 프로퍼티를 포함합니다.
빈 객체를 생성하면 v8은 오프셋 없이 해당 히든 클래스를 만듭니다.
새 프로퍼티를 추가해 해당 객체를 수정합니다. v8엔진은 이전 히든 클래스의 모든 프로퍼티를 상속해 새 히든 클래스를 새성하고 이를 프로퍼티 오프셋 0에 할당합니다.
컴파일러는 프로퍼티 이름에 접근할 때 우회할 수 있으며 v8은 클래스 C01을 직접가리킵니다.
v8은 객체에 새로운 프로퍼티를 추가할 때, hidden class를 생성하고, hidden class에 프로퍼티의 정적인 위치(offset)을 저장함으로 실제 데이터가 저장되어있는 위치에 대한 Pointer를 제공합니다.
동일한 메소드에 대한 반복되는 호출이 동일한 유형의 객체에서 발생하는 경향이 있다는 점에서 의존하여 만들어진 기법입니다.
히든 클래스를 이용한다고 하더라도 특정 프로퍼티에 접근하기 위해서는 메모리에 두번 접근해야합니다. hidden class에 접근하고 offset을 가져온뒤, offset을 통해 실재 프로퍼티에 접근하기 떄문입니다.
인라인 캐싱은 이때, 히든 클래스에 대한 접근을 생략하고 해당 메모리에 접근하기 떄문에 성능을 향상시킬 수 있습니다.
https://helloinyong.tistory.com/290
https://evan-moon.github.io/2019/06/28/v8-analysis/
https://alledy.netlify.app/posts/v8-engine/
https://hwan-shell.tistory.com/343
https://www.youtube.com/watch?v=p-iiEDtpy6I&t=701s&ab_channel=JSConf
https://engineering.huiseoul.com/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%9E%91%EB%8F%99%ED%95%98%EB%8A%94%EA%B0%80-v8-%EC%97%94%EC%A7%84%EC%9D%98-%EB%82%B4%EB%B6%80-%EC%B5%9C%EC%A0%81%ED%99%94%EB%90%9C-%EC%BD%94%EB%93%9C%EB%A5%BC-%EC%9E%91%EC%84%B1%EC%9D%84-%EC%9C%84%ED%95%9C-%EB%8B%A4%EC%84%AF-%EA%B0%80%EC%A7%80-%ED%8C%81-6c6f9832c1d9
<>