본문으로 건너뛰기

WebAssembly 체험

무료2017-12-10#Front-End#WebAssembly入门#WebAssembly指南#WebAssembly Tutorial#Tutorial Guide

Hello, WebAssembly?

一.What?

WebAssembly or wasm is a new portable, size- and load-time-efficient format suitable for compilation to the web.

이식 가능하고, 부피가 작으며 로딩이 빠른 (바이너리) 형식으로, Web 에 컴파일하는 데 적합합니다

주요 목표는Web 환경에서 고성능 애플리케이션을 지원하는 것입니다. 그러나 설계상 Web 특성에 의존하지 않으며, Web 특성을 위한 기능도 제공하지 않으며, 다른 환경에서도 사용할 수 있습니다

간단히 이해하면, 컴파일 목표 형식을 하나 정의하여, 이 형식을 지원하는 모든 환경에서 네이티브에 가까운 실행 성능을 얻을 수 있도록 한 것입니다. native 모듈 확장을 허용하는 것과 같아서, 성능이 가혹한 장면에서는, 다른 더 적절한 언어 (예를 들어 C++) 로 구현하고, 사전에 WebAssembly 형식으로 컴파일하면, native 에 필적하는 성능 체험을 얻을 수 있습니다

그 설계 목표는 2 방면으로 나뉩니다:

  • 빠르고, 안전하며, 이식 가능한 시맨틱

  • 빠름: 네이티브 코드에 가까운 성능으로 실행하며, 모든 현대 하드웨어에 공통된 기능을 활용

  • 안전: 코드는 검증되며, 메모리 안전한 샌드박스 환경에서 실행되어, 데이터 손상 또는 보안 위반을 방지

  • 정의 양호: 합법적인 프로그램과 그 동작을 충분하고 정확하게 정의하며, 비공식과 공식 모두를 추론하기 쉬운 방식으로

  • 하드웨어 ���립: 모든 현대 아키텍처, 데스크톱 또는 모바일 기기, 및 임베디드 시스템에서 컴파일 가능

  • 언어 독립: 특정 언어, 프로그래밍 모델, 또는 객체 모델에 치우치지 않음

  • 플랫폼 독립: 브라우저에 임베드하거나, stand-alone VM 으로 실행하거나, 다른 환경에 통합 가능

  • 오픈: 프로그램이 간단하고 보편적인 방식으로 환경과 대화 가능

  • 효율적이고, 이식 가능한 표현

  • 소형: 전형적인 텍스트 또는 네이티브 코드 형식보다 부피가 작은 바이너리 형식으로, 고속 전송 가능

  • 모듈화: 프로그램은 더 작은 부분으로 분할 가능하며, 개별적으로 전송, 캐시, 사용 가능

  • 高效: 단일 패스 (주사) 로 고속으로 디코드, 검증, 컴파일 가능. 실시간 (JIT) 또는 사전 (AOT) 컴파일과 동등

  • 스트리밍: 모든 데이터를 얻기 전에, 최대한 빨리 디코드, 검증, 컴파일 시작 가능

  • 병렬화: 디코드, 검증, 컴파일을 여러 개의 독립된 병렬 태스크로 분할 가능

  • 이식 가능: 현대 하드웨어에서 널리 지원되지 않는 아키텍처를 가정하지 않음

주류 브라우저 (Chrome, Edge, Firefox, 및 WebKit) 에 의해 표준화 프로세스가 공동으로 추진되고 있습니다:

WebAssembly is currently being designed as an open standard by a W3C Community Group that includes representatives from all major browsers.

P.S. 이 일은 브라우저 벤더가 주도하여 진행합니다 (그들 4 명이 함께 일을 일으키고 있어, 매우 기대됩니다).顺便에 개방 표준을 확립합니다 (Web 환경뿐만 아님). 동력원은 JS 런타임 성능을 더욱 향상시키고 싶은 것으로, V8 에 JIT 를 도입한 후, 성능을 더욱 향상시키는 것은 더 이상 불가능합니다. JS 언어 특성方面的 제한 (예를 들어 해석형, 약타입) 에 직면하고 있기 때문입니다. Web 능력이 점점 더 강대해지고, 클라이언트 JS 가 점점 더 무거워지며, JS 실행 성능을 더욱 향상시키는 수요 여전히 존재하므로, WebAssembly 의 釜底抽薪이 탄생했습니다

二.wasm 과 wast

WebAssembly 가 하나의 바이너리 형식을 정의하고 있다는 것은 알고 있습니다. 이 형식이wasm입니다. 예를 들어:

0061 736d 0100 0000 0187 8080 8000 0160
027f 7f01 7f03 8280 8080 0001 0004 8480
8080 0001 7000 0005 8380 8080 0001 0001
0681 8080 8000 0007 9080 8080 0002 066d
656d 6f72 7902 0003 6763 6400 000a ab80
8080 0001 a580 8080 0001 017f 0240 2000
450d 0003 4020 0120 0022 026f 2100 2002
2101 2000 0d00 0b20 020f 0b20 010b 

이 16 진수 문자열에 대응하는 C 코드는:

// 辗转相除法求最大公约数
int gcd(int m, int n) {
    if (m == 0) return n;
    return gcd(n % m, m);
}

wasm 의 가독성은 0 입니다. 이 문제를 완화하기 위해, 가독성이 조금 더 좋은 텍스트 형식을 정의했습니다. wast라고 부릅니다:

(module
 (table 0 anyfunc)
 (memory $0 1)
 (export "memory" (memory $0))
 (export "gcd" (func $gcd))
 (func $gcd (; 0 ;) (param $0 i32) (param $1 i32) (result i32)
  (local $2 i32)
  (block $label$0
   (br_if $label$0
    (i32.eqz
     (get_local $0)
    )
   )
   (loop $label$1
    (set_local $0
     (i32.rem_s
      (get_local $1)
      (tee_local $2
       (get_local $0)
      )
     )
    )
    (set_local $1
     (get_local $2)
    )
    (br_if $label$1
     (get_local $0)
    )
   )
   (return
    (get_local $2)
   )
  )
  (get_local $1)
 )
)

괄호는 조금 Lisp 풍이지만, 적어도 읽을 수 있습니다. 예를 들어:

// 导出了两个东西,分别叫 `memory`和`gcd`
(export "memory" (memory $0))
(export "gcd" (func $gcd))
// 函数签名,接受 2 个 int32 类型参数,返回 int32 类型值
(func $gcd (; 0 ;) (param $0 i32) (param $1 i32) (result i32)
// 函数体...就不猜了

P.S.wast 와 wasm 은 상호 변환 가능합니다. 상세한 내용은 WABT: The WebAssembly Binary Toolkit 참조

또한, 브라우저의 Source 패널에서 다른 텍스트 명령을 볼 수 있습니다:

func (param i32 i32) (result i32)
(local i32)
  block
    get_local 0
    i32.eqz
    br_if 0
    loop
      get_local 1
      get_local 0
      tee_local 2
      i32.rem_s
      set_local 0
      get_local 2
      set_local 1
      get_local 0
      br_if 0
    end
    get_local 2
    return
  end
  get_local 1
end

wast 와 매우 비슷하지만, 이름이 있는지 없는지 모르겠습니다. 또는 wast 에 속하는 것일까요? 이것은 브라우저가 wasm 에서 변환한 것입니다

三.체험 환경

환경 요건:

  • C/C++ 컴파일 환경 Emscripten

  • WebAssembly 를 지원하는 브라우저 (최신 Chrome 은 기본적으로 지원)

온라인 환경

무상의 체험 환경이 있습니다: WebAssembly Explorer

COMPILE하고 DOWNLOAD 하면 wasm 을 얻을 수 있어, 매우 편리합니다

주의: 기본적으로 C++ 환경입니다. C 를 사용하고 싶다면, 왼쪽에서 C99 또는 C89 를 선택하세요.否则함수명이 망가져서 인코딩됩니다. 예를 들어 C++11 의 wast:

(module
 (table 0 anyfunc)
 (memory $0 1)
 (export "memory" (memory $0))
 (export "_Z3gcdii" (func $_Z3gcdii))
 (func $_Z3gcdii (; 0 ;) (param $0 i32) (param $1 i32) (result i32)
  (local $2 i32)
  (block $label$0
   (br_if $label$0
    (i32.eqz
     (get_local $0)
    )
   )
   (loop $label$1
    (set_local $0
     (i32.rem_s
      (get_local $1)
      (tee_local $2
       (get_local $0)
      )
     )
    )
    (set_local $1
     (get_local $2)
    )
    (br_if $label$1
     (get_local $0)
    )
   )
   (return
    (get_local $2)
   )
  )
  (get_local $1)
 )
)

함수명이 _Z3gcdii 로 인코딩되었습니다. 네임스페이스 등의 것이 의심스럽습니다. C++ 는 잘 모르니,素直히 C 를 사용합니다

P.S.C/C++ 외에도, 다른 언어로도 WebAssembly 를 체험할 수 있습니다. 예를 들어 Rust

로컬 환경

  1. 플랫폼 SDK 다운로드

  2. 설치 절차 에 따름

문제 없다면, 여기까지 하면 설치 완료입니다. emcc -v 로 시험해 보세요:

INFO:root:(Emscripten: Running sanity checks)
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 1.37.22
clang version 4.0.0  (emscripten 1.37.22 : 1.37.22)
Target: x86_64-pc-windows-msvc
Thread model: posix
InstalledDir: D:\emsdk-portable-64bit\clang\e1.37.22_64bit
INFO:root:(Emscripten: Running sanity checks)

Windows 환경에서는 DLL 결손 (MSVCP140.dll) 오류를 마주칠 수 있습니다. 수동으로 필요한 C++ 환경 을 설치할 수 있습니다. 상세한 내용은 MSVCP140.dll not found · Issue #5605 · kripken/emscripten 참조

그 후, 하나 인코딩하여 시험할 수 있습니다 (이전의 C 코드를 파일 gcd.c 로 저장):

emcc ./c/gcd.c -Os -s WASM=1 -s SIDE_MODULE=1 -s BINARYEN_ASYNC_COMPILATION=0 -o ./output/gcd.wasm

P.S. 더 많은 용법은 Emscripten Tutorial 참조

얻어진 gcd.wasm 의 내용은 다음과 같습니다:

0061 736d 0100 0000 000c 0664 796c 696e
6b80 80c0 0200 010a 0260 027f 7f01 7f60
0000 0241 0403 656e 760a 6d65 6d6f 7279
4261 7365 037f 0003 656e 7606 6d65 6d6f
7279 0200 8002 0365 6e76 0574 6162 6c65
0170 0000 0365 6e76 0974 6162 6c65 4261
7365 037f 0003 0403 0001 0106 0b02 7f01
4100 0b7f 0141 000b 072b 0312 5f5f 706f
7374 5f69 6e73 7461 6e74 6961 7465 0002
0b72 756e 506f 7374 5365 7473 0001 045f
6763 6400 0009 0100 0a40 0327 0101 7f20
0004 4003 4020 0120 006f 2202 0440 2000
2101 2002 2100 0c01 0b0b 0520 0121 000b
2000 0b03 0001 0b12 0023 0024 0223 0241
8080 c002 6a24 0310 010b 

주의: 메서드명은 기본적으로 언더스코어 (_) 프리픽스가 추가됩니다. 본 예에서는 익스포트된 메서드명은 _gcd 입니다. 상세한 내용은 Interacting with code 참조:

The keys passed into mergeInto generate functions that are prefixed by _. In other words my_func: function() {}, becomes function _my_func() {}, as all C methods in emscripten have a _ prefix. Keys starting with $ have the $ stripped and no underscore added.

JS 에서 모듈 인터페이스를 사용할 때는 언더스코어를 추가해야 합니다 (그것을 제거할 수 있는 설정 항목이 있는지는 모르겠습니다)

四.체험

WebAssembly.compile(new Uint8Array(`
    0061 736d 0100 0000 0187 8080 8000 0160
    027f 7f01 7f03 8280 8080 0001 0004 8480
    8080 0001 7000 0005 8380 8080 0001 0001
    0681 8080 8000 0007 9080 8080 0002 066d
    656d 6f72 7902 0003 6763 6400 000a ab80
    8080 0001 a580 8080 0001 017f 0240 2000
    450d 0003 4020 0120 0022 026f 2100 2002
    2101 2000 0d00 0b20 020f 0b20 010b 
    `.match(/\S{2}/g).map(s => parseInt(s, 16))
)).then(module => {
    const instance = new WebAssembly.Instance(module);
    console.log(instance.exports);
    const { gcd } = instance.exports;
    console.log('gcd(328, 648)', gcd(328, 648));
});

16 진수 문자열은 온라인 체험에서이며, 최초의 wasm 샘플 내용과 일치합니다. 이들을 Chrome 의 Console 에 붙여넣어 실행하면 됩니다. 모두 정상이면, 오류가 발생합니다:

VM40:1 Uncaught (in promise) CompileError: WasmCompile: Wasm code generation disallowed in this context

이는 기본 CSP(콘텐츠 보안 정책) 제한 때문입니다. 쉽게 해결할 수 있습니다. 시크릿 모드 (Ctrl/CMD + Shift + N) 를 열면 됩니다

출력을 얻습니다:

{memory: Memory, gcd: ?}
gcd(328, 648) 8

1 행目は WebAssembly 를 로드하여 얻은 모듈의 익스포트 내용으로, 메모리 객체와 gcd 메서드를 포함합니다. 2 행目の 출력은 하이퍼포먼스 모듈을 호출하여 계산한 최대공약수입니다

WebAssembly.compile 등의 관련 API 는 다음을 참조할 수 있습니다:

또한, 로컬 컴파일로 얻은 버전은 imports env 를 요구합니다 (게다가 함수명에 언더스코어 _ 프리픽스가 추가됩니다):

WebAssembly.compile(new Uint8Array(`
    0061 736d 0100 0000 000c 0664 796c 696e
    6b80 80c0 0200 010a 0260 027f 7f01 7f60
    0000 0241 0403 656e 760a 6d65 6d6f 7279
    4261 7365 037f 0003 656e 7606 6d65 6d6f
    7279 0200 8002 0365 6e76 0574 6162 6c65
    0170 0000 0365 6e76 0974 6162 6c65 4261
    7365 037f 0003 0403 0001 0106 0b02 7f01
    4100 0b7f 0141 000b 072b 0312 5f5f 706f
    7374 5f69 6e73 7461 6e74 6961 7465 0002
    0b72 756e 506f 7374 5365 7473 0001 045f
    6763 6400 0009 0100 0a40 0327 0101 7f20
    0004 4003 4020 0120 006f 2202 0440 2000
    2101 2002 2100 0c01 0b0b 0520 0121 000b
    2000 0b03 0001 0b12 0023 0024 0223 0241
    8080 c002 6a24 0310 010b 
    `.match(/\S{2}/g).map(s => parseInt(s, 16))
)).then(module => {
    let imports = {
        env: {
            memoryBase: 0,
            memory: new WebAssembly.Memory({ initial: 256 }),
            tableBase: 0,
            table: new WebAssembly.Table({ initial: 0, element: 'anyfunc' })
        }
    };
    const instance = new WebAssembly.Instance(module, imports);
    console.log(instance.exports);
    // 注意下划线前缀
    const { _gcd } = instance.exports;
    console.log('gcd(328, 648)', _gcd(328, 648));
});

유사한 출력을 얻습니다:

{__post_instantiate: ?, runPostSets: ?, _gcd: ?}
gcd(328, 648) 8

Emscripten 이 기본적으로 몇 가지 중요하지 않은 것을 추가한 것 같습니다. 기능적으로는 간이판과 동등합니다

五.장점 단점 및 적용 장면

장점

  • 코드 부피가 매우 작음

300k 정도 (압축 후) 의 JavaScript 로직을 WebAssembly 로 다시 쓰면, 부피는 불과 90k 정도입니다

그러나 WebAssembly 를 사용하려면 50k-100k 의 JavaScript 라이브러리를 인프라스트럭처로 도입해야 합니다

  • 보안이 조금 향상

소스 코드에 대응하는 WebAssembly 텍스트 명령은 여전히 전혀 숨겨지지 않았지만, 리버스 코스트가 조금 높아졌습니다

  • 성능 향상

이론상 WebAssembly 는 native 에 가까운 실행 성능을 가집니다. 해석環節을 스킵하며, 파일 부피도 전송 면에서 유리하기 때문입니다

물론, 전제는 비즈니스 코드량이 매우 크고, 극치 성능을 요구하는 장면입니다. benchmark 등의 중복 실행 장면에서는, JIT 는 AOT 보다 그다지 느리지 않습니다

단점

현재 能力有限 입니다:

  • 몇 종류의 기본 데이터형만 지원 (i32 / i64 / f32 / f64 / i8 / i16)

  • DOM 및 다른 Web API 에 직접 액세스 불가

  • GC 를 제어 불가

적용 장면

WebAssembly 는 브라우저를 위해 표준적인 실행 가능 바이너리 형식을 정의했습니다. 이로 인해, 더 많은 개발자가 통일된 컴파일 메커니즘을 통해 참여할 수 있어, 번영한 Web 생태계를 공동 구축할 수 있습니다. 비전은 미호하지만, 몇 가지 실제 문제에 직면하고 있습니다

먼저 WebAssembly 의 초심은 "Web 환경에서 하이퍼포먼스 애플리케이션을 지원"하는 것입니다. 성능 병목 현상을 돌파하기 위해, 가능한 적용 장면은:

  • 비디오 디코드

  • 이미지 처리

  • 3D/WebVR/AR 가시화

  • 렌더링 엔진

  • 물리 엔진

  • 압축/암호화 알고리즘

  • ...등 연산량이 비교적 많은 장면

물론, 이러한 서포트는 장래에 브라우저에 내장될 가능���도 있으며, "확장 플러그인" 등의 방식으로 할 필요는 없습니다. 그러나 WebAssembly 의진정한 의미는, 고성능 "native" 모듈을自行 확장할 수 있는 능력을 제공한 것입니다. 브라우저가 제공하고, 호환성이 허용 가능해질 때까지 기다리려면 상당히 긴 시간이 걸릴 가능성이 있습니다. 그러나, 이 능력이 있으면, 시장 주류 브라우저가某个 네이티브 특성을 서포트하는 것을 괴롭게 기다릴 필요는 없으며, 스스로 손을 움직여搞定할 수 있습니다. 게다가 호환성 차이는 존재하지 않습니다. 반대로, 인기 있는 커뮤니티 모듈이 용현하고, 점차 브라우저 네이티브 서포트로서 흡수되며, 생태계가 Web 환경에 피드백됩니다

참고 자료

댓글

아직 댓글이 없습니다

댓글 작성