WebAssembly 기능 감지

모든 브라우저에서 사용자를 지원하면서 최신 WebAssembly 기능을 사용하는 방법을 알아봅니다.

WebAssembly 1.0이 4년 전에 출시되었지만 개발은 거기서 멈추지 않았습니다. 새로운 기능은 제안서 표준화 프로세스를 통해 추가됩니다. 웹의 새 기능과 마찬가지로, 구현 순서와 타임라인은 엔진마다 크게 다를 수 있습니다. 이러한 새 기능을 사용하려면 모든 사용자가 제외되지 않도록 해야 합니다. 이 도움말에서는 이를 달성하는 방법을 설명합니다.

일부 새로운 기능은 일반적인 작업에 새로운 명령을 추가하여 코드 크기를 개선하고, 일부는 강력한 성능 기본 요소를 추가하며, 다른 기능은 개발자 환경과 나머지 웹과의 통합을 개선합니다.

공식 저장소에서 제안 및 각 단계의 전체 목록을 확인하거나 공식 기능 로드맵 페이지를 통해 엔진의 구현 상태를 추적할 수 있습니다.

모든 브라우저 사용자가 애플리케이션을 사용할 수 있도록 하려면 사용할 기능을 파악해야 합니다. 그런 다음 브라우저 지원에 따라 그룹을 그룹으로 나눕니다. 그런 다음 각 그룹에 대해 개별적으로 코드베이스를 컴파일합니다. 마지막으로 브라우저 측에서 지원되는 기능을 감지하고 해당하는 자바스크립트 및 Wasm 번들을 로드해야 합니다.

선택 및 그룹화 기능

임의의 특성 세트를 예로 들어 설명하면서 단계를 살펴보겠습니다. 크기 및 성능상의 이유로 라이브러리에서 SIMD, 스레드 및 예외 처리를 사용하려고 한다고 가정하겠습니다. 이들 업체의 브라우저 지원은 다음과 같습니다.

선택한 기능의 브라우저 지원을 보여주는 표
webassembly.org/roadmap에서 이 기능 표를 확인하세요.

브라우저를 다음 동질 집단으로 분할하여 각 사용자에게 가장 최적화된 경험을 제공할 수 있습니다.

  • Chrome 기반 브라우저: 스레드, SIMD, 예외 처리가 모두 지원됩니다.
  • Firefox: 스레드 및 SIMD는 지원되지만 예외 처리는 지원되지 않습니다.
  • Safari: 스레드는 지원되지만 SIMD 및 예외 처리는 지원되지 않습니다.
  • 기타 브라우저: 기본 WebAssembly 지원만 가정합니다.

이 분석은 각 브라우저의 최신 버전에서 지원되는 기능별로 분할됩니다. 최신 브라우저는 상시 자동 업데이트되므로 대부분의 경우 최신 버전에 대해서만 신경 쓰면 됩니다. 하지만 기준 WebAssembly를 대체 동질 집단으로 포함하기만 하면 오래된 브라우저를 사용하는 사용자에게도 작동하는 애플리케이션을 제공할 수 있습니다.

다양한 기능 세트에 대한 컴파일

WebAssembly에는 런타임에서 지원되는 기능을 감지하는 방법이 내장되어 있지 않으므로, 모듈의 모든 명령은 대상에서 지원되어야 합니다. 따라서 각각의 기능 세트에 대해 소스 코드를 Wasm으로 컴파일해야 합니다.

각 툴체인과 빌드 시스템은 서로 다르므로, 이러한 기능을 조정하는 방법은 자신의 컴파일러 문서를 참조해야 합니다. 편의상 다음 예에서는 단일 파일 C++ 라이브러리를 사용하고 Emscripten으로 이를 컴파일하는 방법을 보여드리겠습니다.

SSE2 에뮬레이션을 통해 SIMD를 사용하고 Pthreads 라이브러리 지원을 통한 스레드를 사용하고 Wasm 예외 처리대체 자바스크립트 구현 중에서 선택합니다.

# First bundle: threads + SIMD + Wasm exceptions
$ emcc main.cpp -o main.threads-simd-exceptions.mjs -pthread -msimd128 -msse2 -fwasm-exceptions
# Second bundle: threads + SIMD + JS exceptions fallback
$ emcc main.cpp -o main.threads-simd.mjs -pthread -msimd128 -msse2 -fexceptions
# Third bundle: threads + JS exception fallback
$ emcc main.cpp -o main.threads.mjs -pthread -fexceptions
# Fourth bundle: basic Wasm with JS exceptions fallback
$ emcc main.cpp -o main.basic.mjs -fexceptions

C++ 코드 자체는 #ifdef __EMSCRIPTEN_PTHREADS__#ifdef __SSE2__를 사용하여 같은 함수의 병렬 (스레드 및 SIMD) 구현과 컴파일 시간에 직렬 구현 중에서 조건부로 선택할 수 있습니다. 다음과 같이 표시됩니다.

void process_data(std::vector<int>& some_input) {
#ifdef __EMSCRIPTEN_PTHREADS__
#ifdef __SSE2__
  // …implementation using threads and SIMD for max speed
#else
  // …implementation using threads but not SIMD
#endif
#else
  // …fallback implementation for browsers without those features
#endif
}

예외 처리에는 #ifdef 지시문이 필요하지 않습니다. 컴파일 플래그를 통해 선택한 기본 구현과 관계없이 C++에서 동일한 방식으로 사용할 수 있기 때문입니다.

올바른 번들 로드

모든 기능 동질 집단을 위한 번들을 빌드한 후에는 기본 JavaScript 애플리케이션에서 올바른 번들을 로드해야 합니다. 이렇게 하려면 먼저 현재 브라우저에서 지원되는 기능을 감지해야 합니다. Wasm-feature-detect 라이브러리를 사용하면 됩니다. 동적 가져오기와 함께 사용하면 모든 브라우저에서 가장 최적화된 번들을 로드할 수 있습니다.

import { simd, threads, exceptions } from 'https://unpkg.com/wasm-feature-detect?module';

let initModule;
if (await threads()) {
  if (await simd()) {
    if (await exceptions()) {
      initModule = import('./main.threads-simd-exceptions.mjs');
    } else {
      initModule = import('./main.threads-simd.mjs');
    }
  } else {
    initModule = import('./main.threads.mjs');
  }
} else {
  initModule = import('./main.basic.mjs');
}

const Module = await initModule();
// now you can use `Module` Emscripten object like you normally would

맺음말

이 게시물에서는 다양한 기능 세트에 대한 번들을 선택, 빌드, 전환하는 방법을 알아보았습니다.

특성의 수가 증가하면 기능 동질 집단의 수가 유지되지 않을 수 있습니다. 이 문제를 완화하려면 실제 사용자 데이터를 기반으로 기능 동질 집단을 선택하고 인기가 낮은 브라우저는 건너뛰고 약간 덜 최적화된 동질 집단으로 돌아갈 수 있습니다. 모든 사용자에게 애플리케이션이 작동하는 한 이 접근 방식은 점진적 개선과 런타임 성능 간에 합리적인 균형을 이룰 수 있습니다.

향후 WebAssembly는 지원되는 기능을 감지하고 모듈 내에서 동일한 함수의 다른 구현 간에 전환할 수 있는 기본 제공 방법을 마련할 수 있습니다. 그러나 이러한 메커니즘은 그 자체가 위의 접근 방식을 사용하여 조건부로 감지하고 로드해야 하는 MVP 이후 기능입니다. 그때까지는 이 접근 방식이 모든 브라우저에서 새로운 WebAssembly 기능을 사용하여 코드를 빌드하고 로드하는 유일한 방법으로 남아 있습니다.