본문 바로가기
개발/자바스크립트

[자바스크립트] 자바스크립트의 동작에 대하여 #1(feat.엔진, 런타임, 콜스택)

by 핸디(Handy) 2021. 3. 26.

저번에 인터뷰의 질문 '자바스크립트는 어떻게 동작하는가?'에 대한 답을 해보았다.

 

[인터뷰] 자바스크립트의 내부 동작 원리에 대해서 설명하라

자바스크립트... 처음 입사하고 맞이한 이 친구,,, 일 년을 넘게 보고 있자니 이젠 익숙을 넘어 친근하기까지 한 언어가 되어버렸다. 하지만 이제껏 내부 구조를 살펴볼 생각보다는 var 부터 선언

all-dev-kang.tistory.com

그래서 이번에는 좀 더 깊숙이 들어가 보고자 한다.

일단 이 글은 blog.sessionstack.com/how-does-javascript-actually-work-part-1-b0bacc073cf 를 토대로 작성한 것이다.


자바스크립트가 단일 쓰레드(single-threaded)이고 콜백 큐(callback queue)를 이용한다는 건 자바스크립트 대부분 기술도서의 앞부분에 간단하게 나와있는 내용이다.

앞서 언급한 내용으로 단일 쓰레드는 작업대가 하나고, 콜백 큐는 대기 중인 작업으로 빗대어 설명하였다. 

그러니 이제 자바스크립트의 단일 쓰레드와 콜백 큐를 깊숙히 학습하여 좋은 웹에 대해 고민할 수 있는 역량을 키워보자.


자바스크립트 엔진은 각각 브라우저별로 구현이 다르게 되어있다.

따라서 성능이 조금씩 차이가 나며, 그중 가장 많이 알려진 것은 역시나 갓구글형님의 크롬 자바스크립트 엔진(V8)이다.

이전에 썼던 글을 보면 배열의 sort함수로 성능 차이를 확인했었다.
2021.03.23 - [개발/자바스크립트] - [자바스크립트] Array.sort()에 대하여 (feat, 브라우저)

아무튼 다시 돌아가서 V8엔진은 크게 두 부분으로 구성된다.

메모리힙(Memory Heap)콜스택(Call Stack)이다.

메모리힙 : 메모리 할당이 이뤄지는 공간.
콜스택 : 코드가 실행되면서 Stack frame이 쌓이는 공간.

여기서 말하는 Stack frame에 대해 더 알아보자.

function multiply(x, y) {
	return x * y;
}
function printSquare(x){
	var s = multiply(x,x);
	console.log(s);
}

printSquare(5);

이렇게 printSquare(5), multiply(x, x)처럼 하나의 프레임처럼 생긴 것이 스택프레임이다. 따라서 위에서 언급했던 Call Stack이란 스택 프레임이 쌓이는 자료구조이다.

다만 Call Stack의 크기에도 제한이 있다. 이런 제한을 넘어서까지 Call Stack에 스택프레임이 쌓이게 되면 우리에게 너무나 익숙한 그것!! 바로 스택오버플로우(Stack overflow)가 발생한다.

그래서 아주 간단하게 피보나치수열을 재귀 함수로 구현하고 큰 수를 때려박으면 어느 순간 터진다. 따라서 꼭 성능 테스트를 통해 이러한 오류를 사전에 방지하는 것이 매우 중요하다.


이제 콜 스택에 대한 내용에 대한 중요성은 확인했다. 그다음은 블로킹에 대해 알아보자

콜 스택은 잘 관리했다. 근데 특정 작업이 길어진다면 어떤 일이 일어날까?

단일 스레드인 만큼 특정 작업이 엄청나게 오래 걸리면 다른 작업이 멈추는 블로킹상황이 발생한다. 위에 설명대로는 작업대가 하나인데 특정 작업 하나가 엄청 길게 되어 다른 작업이 멈추게 되는 상황이다.

여기에 다른 작업이란 유저 반응에 대한 응답, UI 변경 등도 포함됨으로 사용자 입장에서는 웹이 멈췄다고 느낄 것이고 대부분 나가는 반응을 보여 줄 것이다.

아래의 보고서(디지털 성능이 사용자 행동에 미치는 영향)에 대한 내용을 보자

보시다시피 대부분 3초까지가 한계다. 따라서 웹이 멈추는 상황은 최대한 피해야 하는 건 FE 개발자로서 당연히 신경 써야 하는 영역이다.

실제로 내가 개발하는 시각화 툴에서 빅데이터에 100만 개의 data가 들어오는 경우 랜더링 하다가 멈춘다. 

또한 멈추지 않더라도 차트 렌더링을 위해 몇 초간 웹이 멈춰 보이는 현상이 발생한다.

이런 상황을 방지하기 위해 100만 개의 데이터를 sampling 해서 뽑아오는 추가 기능을 제공하거나, 100만개의 랜더링을 위해 web worker를 사용한다.

최적화를 했다고 하더라고 결국엔 차트 렌더링을 위해 몇 초가 걸리는 경우가 있다. 성능은 무한대가 아니므로 언젠간 이슈가 발생한다.

따라서 이런 경우에도 멈추지 않았다는 것을 유저에게 알려주기 위해 비동기 콜백(asynchronous callbacks) 으로 렌더링을 진행하고 throbber, loading 이미지를 띄워줌으로써 반응성을 떨어드리지 않기 위해 노력한다.

여기서 의문이 생길 수 있다. 어차피 작업대는 하나인데 비동기 콜백을 사용한다고 해서 뭐가 좋아지는가? 이다.

해답은 작업대에서 진행할수 있는 작업량은 똑같다. 또한 작업대는 여전히 하나다. 대신 비동기 콜백을 사용하면, 작업을 여러개로 나눠서 할수가 있다.

이것이 어떤의미인가하면 렌더링-> 유저 이벤트 -> 렌더링 중단 -> 이벤트 응답 -> 렌더링 진행 -> 로딩 화면 갱신 -> 렌더링 .... 으로

어떤 의미로 Round Robin 느낌으로 짧게 짧게 작업을 수행한다. 하지만 여기서 하나의 작업을 여러개로 쪼개는건 엔진이 아닌 우리가 해야할 일이다. 엔진은 한번 들어온 작업은 끝날때까지 잡아논다.

따라서 긴 작업을 여러개로 쪼개고, 그 쪼갠 작업들을 이벤트 큐로 보내 이벤트루프가 적절히 처리하도록 사전 작업을 잘 해주는 것이 테크닉이다.


이번 포스트를 통해 콜 스택에 대해 진득하게 알아보았고 런타임 시에 발생할 수 있는 성능 이슈를 확인했고 성능 최적화에 대한 필요성을 확인했다.

따라서 이어지는 포스트에서는 최적화를 할 수 있는 방안에 대해 더욱 기피하게 알아보도록 하겠다.

blog.sessionstack.com/how-javascript-works-inside-the-v8-engine-5-tips-on-how-to-write-optimized-code-ac089e62b12e

댓글