본문 바로가기
기타

웹어셈블리 (WASM)?

by KBS 2022. 5. 18.
728x90

웹어셈블리?

웹어셈블리는 자바스크립트가 아닌 다른 프로그래밍 언어로 작성된 코드를 브라우저에서 실행시키기 위한 방법입니다. 그래서 사람들이 웹어셈블리가 빠르다고 이야기하는 건, 자바스크립트와 비교했을때 빠르다는 말입니다.

자바스크립트는 1995년에 만들어졌습니다. 자바스크립트는 빠르게 동작하도록 다지안되지 않았으며, 처음 10년동안은 실제로 빠르지 않았습니다.

2008년에 사람들이 성능 전쟁이라고 말하는 기간이 시작되었다. 많은 브라우저들이 JIT(Just-In-Time)를 도입하였고 실제로 코드를 더 빨라지도록 만들었습니다.

JIT가 등장하면서 자바스크립트의 성능에는 많은 변화가 생겼습니다.. 자바스크립트의 실행 속도가 눈에띄게 빨라진 것 때문입니다. 그리고 현재 우리는 아마 도다른 변곡점에 있는지도 모릅니다. 웹어셈블리가 등장했기 때문입니다.

JIT

자바스크립트는 태생이 느렸지만, JIT 덕분에 빨라지게 되었습니다. 그렇다면 어떻게 하길래 빨리진 것일까요?

개발자로서 자바스크립트를 페이지에 추가하면, 우리는 목표와 문제를 갖게됩니다. 

  • 목표 : 우리는 컴퓨터에게 무엇을 할지 말하고 싶습니다
  • 문제 : 우리와 컴퓨터는 서로 다른 언어를 사용해서 대화하고 있습니다.

우리는 인간의 언어를 사용하고, 컴퓨터는 기계의 언어를 사용합니다. 비록 우리가 자바스크립트나 다른 하이레벨 프로그래밍 언어들이 인간의 언어가 아니라고 생각할지라도, 이들은 실제로는 기계의 언어가 아닌 인간이 알아볼 수 있게 만든 인간의 언어입니다. 

그러므로 자바스크립트 엔진의 임무는 우리가 사용하는 인간 언어를 기계가 이해할 수 있는 무언가로 변경시키는 것 입니다.

프로그래밍에서 기계 언어로 번역하는 방식에는 일반적으로 2가지가 있습니다. 바로 인터프리터와 컴파일러 입니다. 

인터프리터를 사용하면, 실시간으로 한 줄, 한 줄 번역을 진행합니다. 반면, 컴파일러는 실시간으로 번역하지 않습니다. 컴파일러는 미리 작동해서 번역 내용을 생성하고 그것을 저장해 둡니다.

인터프리터의 장단점

인터프리터는 빠르게 준비해서 실행할 수 있습니다. 코드를 실행하기 위해 전체 코드를 컴파일하는 단계를 거칠 필요가 없습니다. 첫 번째 줄을 번역하면 바로 실행할 수 있습니다.

이 덕분에, 인터프리터는 자바스크립트 같은 언어에 잘 어울리는 것 같습니다. 웹 개발자에게는 시작하자마자 바르게 코드가 실행되도록 하는 것이 중요합니다.

동일한 코드를 한번 이상 실행하게 되면 인터프리터의 단점이 들어나게 되는데요. 예를들어 반복문 내부에 있다고 가정해봅시다. 동일한 번역 작업을 계속해서 반복해야만 합니다.

컴파일러의 장점과 단점

컴파일러는 인터프리터에 비해 초기 구동시간은 조금 더 오래 걸리게 됩니다. 처음부터 모든 컴파일 과정을 진행해야하기 때문입니다. 하지만 이후에 코드가 반복해서 실행되면 더 빠르게 되는데 루프가 진행되는 동안 동일한 번역을 반복할 필요가 없기 때문입니다.

또다른 차이점은 컴파일러가 코드를 좀 더 빠르게 실행하기 위해 전체 코드를 둘러보고 편집을 하는 시간이 더 소요된다는 점입니다. 이런 편집을 최적화 라고 합니다.

인터프리터는 런타임에 동작하기 때문에, 번역하는 동안에 이러한 최적화를 위해 많은 시간을 소요할 수 없습니다.

JIT 컴파일러

인터프리터의 비효율성인 반복문에서 매번 같은 코드를 다시 번역해야하는 문제을 제거하기 위해 브라우저는 인터프리터에 컴파일러를 혼합하기 시작했습니다.

브라우저 마다 방식이 조금식 다르긴 하지만, 기본 개념은 동일합니다. 이들은 자바스크립트 엔진에 모니터(혹은 컴파일러)라고 불리는 부분을 추가했습니다. 이 모니터는 코드가 실행되는 것을 관찰하면서 해당 코드가 얼마나 많이 실행되고, 어떤 타입이 사용되는 지를 기록합니다.

먼저, 모니터는 인터프리터를 통해서 모든 것을 실행합니다. 어떤 함수가 적은 횟수로 실행되기 시작하면 JIT는 그 함수를 컴파일 하기 위해 전송합니다. 그 이후에 JIT는 컴파일된 정보를 저장합니다. 함수의 각 줄은 "스텁"으로 컴파일됩니다. 스텁은 줄번호와 변수 타입에 의해 색인됩니다. 만약 동일한 코드가 동일한 변수 타입에 의해 실행되는 것을 모니터가 발견하게 되면, 해당 코드의 컴파일된 버전을 내보냅니다.

이 과정은 속도를 향상시켜 줍니다. 하지만 컴파일러는 더 많은 일을 할 수 있습니다. 컴파일러는 최적화를 하기 위해 시간을 들여서 가장 효율적인 방법을 찾아낼 수 있습니다.

기본 컴파일러가 이러한 최적화의 일부를 담당합니다. 기본 컴파일러는 너무 많은 시간이 소요되는 것을 원치 않는데, 왜냐하면 실행 시간을 너무 오랫동안 멈추고 싶지 않기 때문입니다.

하지만, 만약 코드가 정말로 자주 실행되게 된다면 최적화를 더 하기 위해 추가적인 시간을 들이게 됩니다.

최적화 컴파일러

코드의 한 부분이 아주 자주 실행되면, 모니터는 그 부분을 최적화 컴파일러에게 전송합니다. 이 컴파일러는 또 다른 더욱 바른 버전의 함수를 생성하며, 이함수 역시 저장됩니다. 

더 빠른 버전의 코드를 생성하기 위해, 최적화 컴파일러는 몇가지 가정을 합니다. 예를들어, 만약 특정한 생성자로부터 만들어지는 모든 객체들이 같은 모양을 갖는다고 가정할 수 있다면, 이를 이용해서 몇가지 최적화를 할 수 있습니다.

최적화 컴파일러는 이러한 판단을 하기 위해 모니터가 코드의 실행을 지켜보며 수집한 정보들을 사용합니다. 이전에 반복문을 순회하는 동안 항상 참이었던 값이 있다면, 그 값이 계속해서 참일 거라고 가정할 수 있을 것 입니다.

하지만 물론 자바스크립트에서는 어떤 것도 보장할 수 없습니다. 객체가 99번동안 항상 같은 모양을 하고 있더라도, 100번재는 다를수도 있습니다. 그러므로 컴파일된 코드는 실행하기 전에 가정이 유효한지를 확인해야만 합니다. 유요하다면 코드를 실행하고 그렇지 않다면 가정이 틀렷다고 생각하고 코드를 버립니다. 그러면 실행은 다시 인터프리터 혹은 기본 컴파일된 버전으로 돌아갑니다. 이러한 과정을 역최적화 또는 구제라고 합니다.

최적화 컴파일러는 주로 코드를 빠르게 만들지만, 가끔 예상하지 못한 성능 문제를 야기하기도 합니다. 만약 코드가 계속해서 최적화되고 역최적화 된다면, 단순히 기본 컴파일된 버전을 실행시키는 것 보다 느려지게 됩니다. 

대부분의 브라우저들은 이러한 최적화/역최적화 싸이클이 발생했을 대 중지할 수 있는 제한사항이 있습니다. 만약 JIT가 10번 이상의 최적화를 시도하고 계속해서 다시 역최적화를 한다면 더이상 최적화를 그만할 수도 있습니다. 

어셈블리

기계의 뇌에는 생각을 위해 존재하는 부분이 있으며 더하기, 빼기 혹은 논리 연산 등을 수행합니다. 또한 그와 가까운 곳에 단기 기억을 제공하는 부분과 장기 기억을 제공하는 부분도 있습니다. 이들 각가의 부분은 

  • 생각을 담당하는 부분은 산술 논리 장치 (ALU : Arithmetic Logic Unit)
  • 단기 기억은 레지스터에 의해 제공됩니다.
  • 장기 기억은 RAM(RAM : Random Access Memory)

기계 코드 내에서 문장들은 명령(instruction)이라고 불립니다.

이러한 명령들 중 하나가 뇌 속으로 들어오면 명령은 뇌의 회로에 의해  각기 다른 의미를 가진 부분으로 나누어집니다.

기계어 위에 있는 주석을 잘 살펴봅시다. 이를 통해 우리 인간이 기계 코드를 좀 더 쉽게 이해할 수 있는데, 이것이 바로 어셈블리 입니다. 상징적 기계 코드라고도 불립니다. 이것이 인간이 기계의 코드를 이해할 수 있는 방법입니다.여기서 어셈블리와 기계 코드 사이에는 꽤 직접적인 관곙가 있는 것을 볼 수 있습니다. 이 때문에 여러 종류의 기계 아키텍쳐에 맞는 각기 다른 어셈블리가 존재합니다. 다른 아키텍쳐의 머신에게는 그 머신에게만 통용되는 별도의 어셈블리가 필요합니다. 즉, 번역의 목표 대상이 하나만 있는 것이 아닙니다. 단순히 하나의 기계 코드가 있는 것이 아니라 아주 다양한 종류의 기계 코드가 있습니다. 사람들이 각자 다른 언어로 이야기 하듯이, 기계들도 각자 다른 언어로 이야기 합니다. 하이레벨 프로그래밍 언어들 중 하나를 어셈블리 언어들 중 하나로 번역한다고 해봅시다. 가능한 한 하가지 방법은 각 언어에서 각 어셈블리로 번역할 수 있는 각기 다른 모든 번역기를 만드는 것 입니다. 하지만 이방법은 비효율적입니다. 이를 해결하기 위해 대부분의 컴파일러는 둘 사이에 최소한 하나의 레이어를 두게 됩니다. 컴파일러는 상위 레벨 프로그래밍 언어를 읽어서, 너무 상위 레벨도 아니면서 기계 코드의 레벨에서 동작하지도 않는 무언가로 번역합니다. 이것을 중간 표현 방식(IR : Intermediate representation)이라고 합니다. 컴파일러의 프론트엔드는 하이레벨 언어들 중 어떤 것이든 읽어서 IR로 번역하고, 컴파일러의 백엔드는 IR을 대상 아키텍쳐의 어셈블리 코드로 번역합니다. 

오늘날의 자바스크립트 성능은?

자바스크립트와 웹어셈블리의 성능차이를 이해하려면, 먼저 자바스크립트 엔진이 하는일을 이해해야 합니다. 어플리케이션의 시작 과정을 간단하게 설명하면

  • 구문해석(Parsing) : 소스코드를 인터프리터가 실행할 수 있는 형태로 가공하는데 걸리는 시간
  • 컴파일 + 최적화 : 기본컴파일러와 최적화 컴파일러에서 걸리는 시간. 일부 최적화 컴파일러의 작업은 메인 스레드에서 진행되지 않기 때문에 여기에서 제외된다.
  • 재-최적화 : JIT가 그들이 가정이 틀렸을 때 재조정하는데 걸리는 시간. 이는 다시 최적화를 하거나 기본 코드로 되돌리는 작업을 포함한다.
  • 실행 : 코드를 실행하는데 걸리는 시간
  • 가비지 컬렉션 : 불필요한 메모리를 비우는 데 걸리는 시간

위의 작업들은 구분된 단위를 갖거나 특정한 순서로 진행되지 않고, 서로 맞물려서 진행됩니다. 구문 해석이 일부 진행된 후, 일부 실행, 일부 컴파일 다시 구문해석, 일부 실행 등의 형태로 진행됩니다.

그렇다면 웹어셈블리는 뭐가 다를까?

각각의 단계를 어떻게 다루는지는 브라우저마다 다릅니다. 

가져오기(Fetching)

단순히 서버에서 파일을 가져오는 일에도 시간이 소요됩니다. 웹어셈블리는 자바스크립트보다 더 간결하기 때문에 데이터를 가져오는 속도가 더 빠릅니다. 압축 알고리즘이 자바스크립트 번들 파일의 사이즈를 상당히 줄일 수 있을지는 몰라도, 압축된 바이너리 형태의 웹어셈블리가 더 작습니다.

즉, 서버에서 클라이언트로 전송하는 시간이 더 적게 걸리게 됩니다. 느린 네트워크 환경에서는 더욱더 의미가 있습니다.

구문해석(Parsing)

자바스크립트 소스가 브라우저에게 전달되게 되면, 추상 구문 트리(이하 AST : Abstract Syntax Tree)로 변환됩니다. 브라우저는 종종 이 작업을 지연해서 실행하는데, 초기 실행에 필요한 부분만 먼저 해석하고 아직 호출되지 않은 함수들은 스텁만 만들어 둡니다. 이때 AST는 중간 표현 장식으로 변횐되는데, 이는 각 자바스크립트 엔진에 따라 다릅니다. 반면, 웹어셈블리는 그 자체로 이미 중간 표현 형식이기 때문에 이 변환 작업을 거칠 필요가 없습니다. 웹어셈블리는 단지 해독된 이후에 에러가 없는지만 검증되면 됩니다. 

컴파일  + 최적화

자바스크립트는 코드를 실행하는 도중에 컴파일 됩니다. 런타임에 어떤 타입이 사용되는지에 따라 동일한 코드가 다양한 버전으로 컴파일 되기도 합니다. 

브라우저들은 웹어셈블리를 각자 다른 방식으로 컴파일 합니다. 일부 브라우저들은 실행하기 전에 기본 컴파일단계를 거치고, 다른 브라우저들은 JIT를 사용합니다. 

어떤 방식이든 웹어셈블리는 기계 코드에 가까운 상태로 시작합니다. 예를들면 프로그램의 일부로써 타입이 포함되어 있는데, 이는 다음의 몇가지 이유로 더 빠릅니다. 

  1. 최적회된 코드를 컴파일하기 전에, 어떤 타입이 사용되었는지 확인하기 위해 코드를 실행해볼 필요가 없습니다.
  2. 같은 코드에 대해서 여러가지 타입들이 사용되고 있을 때, 여러가지 버전으로 컴파일할 필요가 없습니다.
  3. LLVM에서 이미 많은 최적화가 진행된 상태이기 때문에, 컴파일이나 최적화에 필요한 작업이 적습니다.

재-최적화

JIT는 가끔 최적화된 버전을 버리고, 다시 최적화해야합니다. 이는 JIT가 코드 실행에 기초하여 만들어낸 가정이 잘못된 것으로 판명될 때 발생합니다. 예를들어 순환문 내부에서 사용되는 변수에 이전 주기와는 다른 값이 할당되거나, 프로토타입 체인내부에 다른 함수가 추가되면 역최적화가 발생합니다.

역최적화에는 두가지 비용이 따르게 되는데, 첫재로 최적화된 코드를 내버리고 기본 코드로 되돌릴 때의 시간이 소모됩니다. 둘째로, 해당 함수가 여전히 많은 빈도로 호출되고 있다면 JIT는 해당 함수를 다시 최적화 컴파일러에게 보내기로 결정할 수 도 있는데, 여기에서 컴파일을 두번 하는 것에 따른 비용이 발생합니다.

웹어셈블리에서는 타입과 같은 것들이 명시적이기 대문에 JIT가 런타임에 데이터를 수집해서 타입을 추론할 필요가 없습니다. 즉, 재-최적화 단계가 필요 없다는 의미입니다. 

실행

자바스크립트가 빠르게 실행될 수 있도록 작성하는 것은 가능합니다. 이를 위해서는 JIT가 어떻게 최적화 하는지에 때해서 알고 있어야 합니다. 하지만, 대부분의 개발잘들은 JIT 내부에 대해서 알지 못합니다. 게다가 JIT가 사용하는 최적화는 브라우저마다 달라서, 특정 브라우저에 맞춰 코딩을 하면 다른 브라우저에서는 성능이 느릴 수 있습니다.

웹어셈블리로 작성된 코드를 실행하는 것은 일반적으로 더 빠릅니다. 자바스크립트를 위해 JIT가 하는 많은 최적화 작업들(타입 특수화 같은은 웹어셈블리에서는 전혀 필요 없습니다.  뿐만 아니라 웹어셈블리는 컴파일러를 목표로 디자인되었습니다. 즉, 인간 프로그래머가 작성하는 용도가 아닌, 컴파일러가 생성해내는 용도로 디자인 되었다는 의미입니다.

가비지 컬렉션

자바스크립트에서 개발자들은 더이상 필요없는 오래된 변수들을 메모리에서 제거하는 작업에 대해 신경쓸 필요가 없습니다. 대신에 자바스크립트 엔진이 가비지 컬렉터를 이용해 이러한 작업을 자동으로 해줍니다.

ㅏ지만 만약 예측가능한 성능을 원한다면, 이는 문제가 될 수도 있습니다. 가비지 컬렉터가 언제 작동할 지 제어할 수 없기 때문에, 안좋은 타이밍에 실행될지도 모릅니다. 대부분의 브라우저들은 이를 잘 스케줄링해주고 있지만, 여전히 코드 실행을 방해하는 요인이 될 수도 있습니다.

최소한 현재까지는 웹어셈블리는 가비지 컬렉션을 지원하지 않습니다. 메모리는 수동으로 관리됩니다. 이로인해 개발자들이 프로그래밍 하기는 더 어려울수도 있지만, 성능에 있어서는 더 안정된 결과를 만들어낼 수 있을 것 입니다. 

결론

웹어셈블리는 많은 경우에 자바스크립트보다 빠릅니다. 

  • 웹어셈블리 코드가 자바스크립트보다 더 간결하기 때문에 코드를 가져오는 데에 더 적은 시간이 걸립니다.
  • 웹어셈블리 코드를 해독하는 시간이 자바스크립트의 구문 해석하는 시간보다 적게 걸립니다.
  • 웹어셈블리 코드는 자바스크립트 코드보다 더 기계어에 가깝고, 서버단에서 미리 최적화가 되어 있기 때문에 컴파일하고 최적화 하는 시간이 적게 걸립니다.
  • 웹어셉블리는 타입이나 다른정보가 미리 내장되어 있기 때문에, 자바스크립트 엔진이 실행시점에 분석할 필요가 없어서 재-최적화하는 시간이 필요 없습니다.
  • 웹어셈블리 코드는 성능을 위해 컴파일러를 느리게 만드는 요인들에 대해 개발자들이 미리 알고 있을 필요가 없으며, 머신에 적합한 명령어 셋을 갖고 있디 대문에 실행시간도 주로 더 적게 걸립니다.
  • 메모리를 직접 관리하기 때문에 가비지 컬렉션이 필요 없습니다.

이것이 바로 같은 작업을 할 대 웹어셈블리가 자바스크립트보다 더 좋은 성능을 내는 이유입니다. 몇몇 경우에는 기대한 것 만큼 웹어셈블리가 빠르지 않을 때도 있지만, 머지않아 이를 개선할 변화가 있을 것 입니다.


- 참고 : https://dongwoo.blog/2017/06/06/%eb%b2%88%ec%97%ad-%ec%b9%b4%ed%88%b0%ec%9c%bc%eb%a1%9c-%ec%86%8c%ea%b0%9c%ed%95%98%eb%8a%94-%ec%9b%b9%ec%96%b4%ec%85%88%eb%b8%94%eb%a6%ac/

728x90

'기타' 카테고리의 다른 글

[webRTC, React, TypeScript] 간단하게 1:1 화상 통화를 만들어보자  (1) 2022.05.27
MQTT  (0) 2022.05.19
소켓 통신 기본 개념 정리  (0) 2022.05.19
동영상 압축, MP4 와 H.264  (0) 2022.05.19
Web RTC?  (0) 2022.05.18

댓글