WebAssembly 기능 감지

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

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

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

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

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

특성 선택 및 그룹화

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

<ph type="x-smartling-placeholder">
</ph> 선택한 기능의 브라우저 지원을 보여주는 표 <ph type="x-smartling-placeholder">
</ph> 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 기능을 사용하여 코드를 빌드하고 로드하는 유일한 방법이었습니다.