WebAssembly 모듈의 효율적인 로드

WebAssembly로 작업할 때 모듈을 다운로드하고 컴파일하고 인스턴스화한 다음 JavaScript에서 내보내는 내용을 사용하려는 경우가 많습니다. 이 게시물에서는 최적의 효율성을 위해 권장되는 접근 방식을 설명합니다.

WebAssembly를 사용할 때는 모듈을 다운로드하고 컴파일한 후 인스턴스화한 다음 JavaScript에서 내보내는 항목을 사용하는 경우가 많습니다. 이 게시물에서는 이러한 작업을 수행하는 일반적이지만 최적화되지 않은 코드 스니펫으로 시작하여 가능한 여러 가지 최적화에 대해 설명하고 결국 JavaScript에서 WebAssembly를 실행하는 가장 간단하고 효율적인 방법을 보여줍니다.

이 코드 스니펫은 최적화되지 않은 방식이지만 다운로드-컴파일-인스턴스화 댄스를 완료합니다.

사용하지 마세요.

(async () => {
  const response = await fetch('fibonacci.wasm');
  const buffer = await response.arrayBuffer();
  const module = new WebAssembly.Module(buffer);
  const instance = new WebAssembly.Instance(module);
  const result = instance.exports.fibonacci(42);
  console.log(result);
})();

new WebAssembly.Module(buffer)를 사용하여 응답 버퍼를 모듈로 변환하는 방법에 유의하세요. 동기식 API이므로 완료될 때까지 기본 스레드를 차단합니다. Chrome에서는 4KB보다 큰 버퍼의 WebAssembly.Module를 사용하지 못하도록 사용 중지합니다. 크기 제한을 우회하려면 await WebAssembly.compile(buffer)를 대신 사용하면 됩니다.

(async () => {
  const response = await fetch('fibonacci.wasm');
  const buffer = await response.arrayBuffer();
  const module = await WebAssembly.compile(buffer);
  const instance = new WebAssembly.Instance(module);
  const result = instance.exports.fibonacci(42);
  console.log(result);
})();

await WebAssembly.compile(buffer)여전히 최적의 접근 방식은 아니지만, 그 방법은 잠시 후에 살펴보겠습니다.

수정된 스니펫의 거의 모든 작업은 이제 비동기식입니다. 이는 await 사용을 통해 명확하게 알 수 있습니다. 유일한 예외는 Chrome에서 동일한 4KB 버퍼 크기 제한이 적용되는 new WebAssembly.Instance(module)입니다. 일관성을 유지하고 기본 스레드를 자유롭게 유지하기 위해 비동기 WebAssembly.instantiate(module)를 사용할 수 있습니다.

(async () => {
  const response = await fetch('fibonacci.wasm');
  const buffer = await response.arrayBuffer();
  const module = await WebAssembly.compile(buffer);
  const instance = await WebAssembly.instantiate(module);
  const result = instance.exports.fibonacci(42);
  console.log(result);
})();

앞서 언급한 compile 최적화로 돌아가 보겠습니다. 스트리밍 컴파일을 사용하면 모듈 바이트가 다운로드되는 동안 브라우저가 이미 WebAssembly 모듈 컴파일을 시작할 수 있습니다. 다운로드와 컴파일이 동시에 실행되므로 특히 대용량 페이로드의 경우 속도가 빨라집니다.

다운로드 시간이 WebAssembly 모듈의 컴파일 시간보다 길면 WebAssembly.compileStreaming()은 마지막 바이트가 다운로드된 직후에 컴파일을 완료합니다.

이 최적화를 사용 설정하려면 WebAssembly.compile 대신 WebAssembly.compileStreaming를 사용하세요. 또한 이 변경사항을 통해 중간 배열 버퍼를 삭제할 수 있습니다. 이제 await fetch(url)에서 반환된 Response 인스턴스를 직접 전달할 수 있기 때문입니다.

(async () => {
  const response = await fetch('fibonacci.wasm');
  const module = await WebAssembly.compileStreaming(response);
  const instance = await WebAssembly.instantiate(module);
  const result = instance.exports.fibonacci(42);
  console.log(result);
})();

WebAssembly.compileStreaming API는 Response 인스턴스로 확인되는 프로미스도 허용합니다. 코드의 다른 위치에 response가 필요하지 않으면 결과를 명시적으로 await하지 않고 fetch에서 반환된 프로미스를 직접 전달할 수 있습니다.

(async () => {
  const fetchPromise = fetch('fibonacci.wasm');
  const module = await WebAssembly.compileStreaming(fetchPromise);
  const instance = await WebAssembly.instantiate(module);
  const result = instance.exports.fibonacci(42);
  console.log(result);
})();

다른 곳에서도 fetch 결과가 필요하지 않은 경우 직접 전달할 수도 있습니다.

(async () => {
  const module = await WebAssembly.compileStreaming(
    fetch('fibonacci.wasm'));
  const instance = await WebAssembly.instantiate(module);
  const result = instance.exports.fibonacci(42);
  console.log(result);
})();

개인적으로 별도의 줄에 두는 것이 더 읽기 쉽다고 생각합니다.

응답을 모듈로 컴파일한 다음 즉시 인스턴스화하는 방법을 확인해 보세요. WebAssembly.instantiate는 한 번에 컴파일하고 인스턴스화할 수 있습니다. WebAssembly.instantiateStreaming API는 스트리밍 방식으로 이를 실행합니다.

(async () => {
  const fetchPromise = fetch('fibonacci.wasm');
  const { module, instance } = await WebAssembly.instantiateStreaming(fetchPromise);
  // To create a new instance later:
  const otherInstance = await WebAssembly.instantiate(module);
  const result = instance.exports.fibonacci(42);
  console.log(result);
})();

인스턴스가 하나만 필요한 경우 module 객체를 계속 유지할 필요가 없으므로 코드를 더 단순화할 수 있습니다.

// This is our recommended way of loading WebAssembly.
(async () => {
  const fetchPromise = fetch('fibonacci.wasm');
  const { instance } = await WebAssembly.instantiateStreaming(fetchPromise);
  const result = instance.exports.fibonacci(42);
  console.log(result);
})();

적용된 최적화는 다음과 같이 요약할 수 있습니다.

  • 비동기 API를 사용하여 기본 스레드 차단 방지
  • 스트리밍 API를 사용하여 WebAssembly 모듈을 더 빠르게 컴파일하고 인스턴스화
  • 필요 없는 코드 작성 금지

WebAssembly를 즐겁게 사용하세요.