WebAssembly 기능 감지

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

WebAssembly 1.0은 4년 전에 출시되었지만 개발은 여기서 멈추지 않았습니다. 새로운 기능은 제안서 표준화 절차를 통해 추가됩니다. 일반적으로 웹의 새로운 기능과 마찬가지로 구현 순서와 타임라인은 엔진마다 크게 다를 수 있습니다. 이러한 새 기능을 사용하려면 모든 사용자가 제외되지 않도록 해야 합니다. 이 도움말에서는 이를 달성하는 접근 방식을 알아봅니다.

일부 새로운 기능은 일반적인 작업에 대한 새로운 지침을 추가하여 코드 크기를 개선하고, 어떤 기능은 강력한 성능 프리미티브를 추가하며, 다른 기능은 개발자 환경 및 나머지 웹과의 통합을 개선합니다.

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

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

특성 선택 및 그룹화

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

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

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

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

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

다양한 기능 세트 컴파일

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

도구 모음과 빌드 시스템은 각각 다르므로 이러한 기능을 조정하는 방법은 자체 컴파일러의 문서를 참고해야 합니다. 단순성을 위해 다음 예에서는 단일 파일 C++ 라이브러리를 사용하고 Emscripten으로 컴파일하는 방법을 보여줍니다.

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

# 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 기능을 사용하여 코드를 빌드하고 로드하는 유일한 방법입니다.