자바스크립트에 대해 공부할수록 너무나 많은 개념이 '엔진이 이와 같이 처리해 준다'라는 짧은 설명으로 끝나버리는 것을 보게 됐다. 처음 공부하는 입장에서 이 내용이 잘 와닿지가 않았다. 아마도 엔진이 뭔지에 대한 개념이 부족해서가 아닐까 생각했다. 그래서 자바스크립트 엔진에 대해 몇 가지 찾아본 내용을 기록해 둔다.
고수준 언어와 저수준 언어
컴퓨터는 전기 신호로 모든 데이터를 표현한다. 그리고 전기로 어떠한 값을 표현할 수 있는 방법은 전기를 흐르게 하거나, 흐르지 않게 하는, 두 가지 방법이 유일하다. 그리고, 실제로 컴퓨터는 모든 데이터를 1과 0으로만 표현한다. 전기를 흘려보내서 1을, 전기의 흐름을 끊어서 0을 표현하는 것이다.
그렇다 보니, 컴퓨터에게는 숫자 1과 0으로만 이루어진 코드가 가장 친숙하다. 1과 0 이외의 다른 값으로 코드를 작성하면 컴퓨터는 전달된 명령을 수행하기 위해 코드를 숫자 1과 0으로 변환하는 과정을 거쳐야 하기 때문이다. 해당 과정을 거치다 보면 컴퓨터가 명령을 처리하는 속도가 늦어지기도 하고, 추가적인 연산을 위해 컴퓨팅 파워를 조금 더 많이 사용해야 할 수도 있다.
바꿔 얘기하면, 컴퓨터 친화적인 언어를 사용하면 실행 속도가 굉장히 빠르다. 코드르 숫자 1과 0으로 변환하는 과정이 덜 번거롭고 부담되기 때문이다. 그래서 많은 연산양이나 실행 속도가 굉장히 중요한 프로그램을 개발할 땐 컴퓨터 친화적인 개발 언어를 사용하는 게 좋다(예를 들어, 굉장히 큰 연산을 하는 인공지능 모델링의 경우 저수준 언어를 사용한 데이터 관리 및 엔지니어링을 하는 것이 성능 상 유리할 수 있다).
하지만, 컴퓨터 친화적인 프로그래밍 언어는 사람이 사용하기 어렵다. 아래는 컴퓨터에게 굉장히 친화적인 '어셈블리어'로 작성하고, 바이너리로 컴파일한 'Hello, World!' 코드이다(GPT에게 물어봐서 작성한 코드라 실제와 다를 수 있다).
b8 04 00 00 00 bb 01 00 00 00 b9 00 00 00 00 ba 0d 00 00 00 cd 80 b8 01 00 00 00 31 db cd 80
GPT가 오답을 줬을 수도 있지만, 아무튼 숫자 바이너리로 적혀있는(위의 경우 16진수로 코드가 표현돼 있다) 머신 코드를 보고 사람이 코드의 의미를 직관적으로 이해하는 건 굉장히 어렵다. 사람은 기본적으로 숫자가 아닌 '텍스트'로 의사소통을 하기 때문이다.
그래서, 보통은 사람이 이해하기 용이한 언어적 표현을 사용할 수 있게 프로그래밍 언어가 만들어지고, 실제 코드를 실행할 때 해당 코드를 컴퓨터가 이해할 수 있는 머신 코드로 변환하는 과정을 거치는 게 일반적이다. 이때, 코드가 사람에게 친화적으로 더 많이 추상화 돼있을수록 컴퓨터가 해당 코드를 번역하고 이해하는 데 더 많은 비용이 들어간다. 대신, 사람에게 더 친숙하게 추상화돼 있기 때문에 사람이 코드를 이해하고 사용하는 데에는 유리하다.
이렇게, 프로그래밍 언어는 사람과 인간 중 누구에게 더 친화적으로 만들어졌는지에 따라 '고수준 언어'와 '저수준 언어'로 구분할 수 있다. 그리고, 자바스크립트는 대표적인 고수준 언어 중 하나이다.
컴파일러와 인터프리터
컴퓨터가 실제로 데이터를 처리할 때 사용되는 바이너리 코드가 아닌 이상, 컴퓨터는 사람이 작성한 코드를 번역하는 과정을 반드시 거쳐야 한다. 그리고, 컴퓨터가 프로그래밍 언어를 번역하는 방식에도 여러 가지가 있는데, 대표적으로 컴파일 방식과 인터프리터 방식이 있다.
컴파일 방식은 작성된 코드를 실행하기 전에 통째로 번역한다. 그리고, 번역의 결과로 컴퓨터가 바로 실행할 수 있는 실행 파일을 생성한다(흔히 .exe
확장자로 실행 파일이 만들어진다). 최초에 모든 코드를 전부 번역해서 실행 파일을 만들기 때문에 최초 실행까지의 시간과 리소스 투입이 많이 필요하지만, 한번 실행 파일을 만들고 나면 빠른 속도로 명령 처리가 가능하다. 또한, 코드가 실행되기 전에 작성된 코드가 평가되는 과정을 거치기 때문에 오류를 미리 발견할 수 있다는 장점도 있다.
인터프리터 방식은 처음 실행할 때 전체 코드를 번역하여 실행 파일을 생성하는 게 아니라, 코드를 한 줄씩(Line by line) 번역한다. 코드의 번역과 실행이 분리되는 게 아니라 동시에 진행되기 때문에 초기 실행 속도가 빠르다는 장점이 있다. 하지만, 전체 프로젝트의 코드를 계속 한줄씩 번역-실행을 반복하기 때문에 전체 실행 속도는 늦어진다는 당점이 있다. 또한, 코드에 오류가 있을 경우 프로그램이 실행되는 중간에 확인이 돼서 오류를 빠르게 대처하는 데 어려움이 있다는 단점도 있다.
컴파일 방식 | 인터프리터 방식 |
최초에 모든 코드를 통째로 머신 코드로 번역. | 실행 시점에 한줄씩 머신 코드로 번역. |
최초 실행까지 시간이 오래걸림. | 최초 실행이 빠르게 됨. |
전체 프로그램의 실행 속도가 상대적으로 빠름. | 전체 프로그램의 실행 속도가 상대적으로 느림. |
오류를 사전에 확인 가능. | 오류를 실행 시점에서 확인 가능. |
exe 확장자 실행 파일 생성을 위해 메모리를 사용. | 메모리를 사용하지 않음. |
별도 실행 파일을 한번에 실행하기 때문에 코드 유출 우려가 적음 | 중간 코드로 해석되기 때문에 코드 유출 가능성 존재(보안 문제) |
프로그래밍 언어는 사용 목적에 따라 컴파일 또는 인터프리터 중 하나의 방식으로 프로그래밍 언어를 컴퓨터가 이해 가능한 바이너리 코드로 변환한다. 그리고, 이렇게 코드를 변환하고 실행하는 과정을 관리하기 위한 별도의 프로그램이 필요하고, 자바스크립트는 자바스크립트 엔진이 그 역할을 수행해 준다. 즉, 자바스크립트 엔진은 자바스크립트로 작성된 코드를 컴퓨터가 이해할 수 있는 바이너리 코드로 변환해서 컴퓨터에게 명령을 실행시키는 도구인 것이다.
자바스크립트 엔진은 자바스크립트 코드를 바이너리 코드로 변환해 주면서, 그 과정에서 자바스크립트 언어의 문법에 맞게 코드가 동작시킨다(메모리 관리를 수행한다). 예를 들어, 자바스크립트의 원시 타입이 불변성을 갖는다는 특징은 자바스크립트 엔진이 그렇게 메모리 상의 데이터를 관리하기 때문에 그러한 동작이 만들어지는 것이다. 더 정확하겐, 자바스크립트 언어의 고유한 특성을 반영할 수 있게 엔진이 설계된 것이라고 할 수 있다. 엔진은 ECMAScript 언어의 변화와 정책에 맞게 계속 진화하고 발전한다.
참고로, 자바스크립트 코드를 번역해 주는 엔진도 하나의 '프로그램'이기 때문에 어떠한 프로그래밍 언어를 통해 스크립트가 작성되고 만들어졌다. 많은 자바스크립트 엔진은 C++로 만들어졌다는 사실도 참고 차 알아두면 좋을 것 같다.
자바스크립트는 최초엔 인터프리터 방식을 주로 채택해 실행 환경이 만들어졌지만, 비교적 최근엔 'JIT 컴파일'이라는 컴파일 방식을 사용하는 쪽으로 패러다임이 바뀌었다. JIT 컴파일 방식은 가장 대중적으로 사용되는 브라우저인 크롬 브라우저와, 자바스크립트 언어의 서버 애플리케이션 구현을 위한 런타임인 Node.js
에 사용되는 V8 엔진이 언급한 JIT 컴파일 방식을 사용해 코드를 컴파일한다(JIT 컴파일에 대해선 별도로 포스팅해 볼 예정이다).
엔진과 런타임
공부를 하다 보면 '엔진'만큼이나 많이 등장하는 표현이 바로 '런타임'이다. 런타임의 사전적 의미는 프로그램이 실행되고 있는 동안의 모든 동작을 의미하지만, 프로그램이 실행되는 전반적인 환경과 동작의 제어 및 관리 방식을 일컫는 의미로도 사용된다.
자바스크립트로 작성된 프로그램이 동작하는 대표적인 런타임은 브라우저와 Node.js
가 있다(브라우저와 Node.js
는 자바스크립트 언어의 런타임, 즉 실행 과정을 제어 및 관리하는 환경이다). 그리고, 자바스크립트 엔진은 런타임을 구성하는 여러 요소 중 하나이다(자바스크립트 엔진은 런타임에 의해 관리된다).
아래는 브라우저 런타임의 구성을 도식화한 이미지다. 이미지를 보면, 자바스크립트의 브라우저 런타임에는 자바스크립트 엔진뿐만 아니라 Web API, 콜백 큐 등이 포함되는 것을 알 수 있다.
자바스크립트가 브라우저의 DOM에 접근하여 동작을 제어할 수 있는 건 자바스크립트 엔진이 아니라 런타임에서 제공하는 Web API를 사용하는 것이다. DOM API 뿐만 아니라 타이머, Fetch API 등, 브라우저 런타임은 자바스크립트가 하나의 웹 애플리케이션을 성공적으로 만들 수 있게 지원하는 다양한 API 기능 등을 제공한다.
또한, 싱글 스레드인 자바스크립트의 실행 흐름을 비동기적으로 관리하기 위한 콜백 큐 또한 런타임에서 제공되는 도구이다. 자바스크립트 언어를 사용하면 코드의 평가와 실행에 관여하는 자바스크립트 엔진과 함께, 런타임에서 제공하는 다양한 기능들을 활용해 개발하는 것이 가능하다.
Node.js
는 브라우저와는 다른 런타임이기 때문에, Web API 등 브라우저 런타임에서 제공되는 여러 기능들을 사용하는 게 불가능하다. 대신에, 브라우저에서는 활용이 불가능한 Node.js
런타임만의 별도 API들이 구현되어 있으며, 이는 서버 애플리케이션 개발에 조금 더 최적화되어있다.
브라우저와 Node.js
는 자바스크립트의 런타임이라는 것과, 자바스크립트 언어를 사용한다(자바스크립트 엔진을 이용해 코드를 기계어로 번역한다)는 공통점이 있을 뿐이지, 제 각기 목적에 따라 다른 개발 환경을 제공한다. 즉, 엔진은 공유하지만 런타임은 서로 다른 것이라는, 엔진과 런타임의 차이에 대해 잘 이해해야 한다.
결론
개발을 하면서 엔진이나 런타임을 직접 깊게 뜯어볼 일이 많지 않을 것 같다. 그래도, 내가 작성한 코드가 어떤 환경에서 돌아가고, 그 환경은 어떤 식의 처리까지 해주는지 잘 아는 게 안정적이고 생산성 높은 개발을 하는데 도움이 될 것 같다. 나중에 기회가 되면 지금 작성한 얕은 수준의 내용 말고, 좀 더 깊은 내용도 공부해 보면 좋을 것 같다.
'JavaScript' 카테고리의 다른 글
프로토타입 톺아보기 (1) | 2024.11.13 |
---|---|
241102 TIL (1) | 2024.11.03 |
this 톺아보기: 함수 호출 시점과 this 바인딩 (2) | 2024.10.28 |
실행 컨텍스트 톺아보기: 실행 컨텍스트 구성 요소 (0) | 2024.10.22 |
실행 컨텍스트 톺아보기: 기본 개념, 메모리 사용 방식 (8) | 2024.10.19 |