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.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를 즐겁게 사용하세요.