WebAssembly(WASM)는 브라우저에서 C++를 실행하기 위한 기술로 시작되었습니다. 2026년 현재, 브라우저, 서버, 엣지 네트워크, 임베디드 디바이스 등 모든 환경에서 실행되며, 웹에서 가장 높은 성능을 요구하는 애플리케이션들을 구동하고 있습니다. Figma의 렌더링 엔진, 웹 버전 Adobe Photoshop, Google Meet의 영상 처리, Cloudflare의 엣지 컴퓨팅 플랫폼 모두 WebAssembly 위에서 동작합니다.
Chrome Platform Status에 따르면, 2026년 초 기준으로 WASM은 Chrome 전체 페이지 로드의 약 5.5%를 차지하며, 전년도의 4.5%에서 증가했습니다. WASM 3.0이 W3C 표준이 되고 WASI가 1.0을 향해 성숙해지면서, 생태계는 전환점에 도달했습니다.
이 가이드는 WebAssembly로 개발을 시작하기 위해 알아야 할 모든 것을 다룹니다.
WebAssembly란 무엇인가?
WebAssembly는 컴파일 타겟으로 설계된 바이너리 명령어 형식입니다. 고수준 언어(Rust, C, C++, Go, Kotlin)로 코드를 작성하고, .wasm으로 컴파일한 후, WASM 런타임이 있는 모든 환경 - 브라우저, Node.js, Cloudflare Workers, Wasmtime, Wasmer - 에서 실행할 수 있습니다.
작동 원리
WASM은 스택 기반 가상 머신입니다. 함수는 피연산자 스택에서 값을 푸시하고 팝합니다. 호스트 런타임(Chrome의 V8, Firefox의 SpiderMonkey)이 WASM 바이트코드를 네이티브 머신 코드로 JIT 컴파일하기 때문에 네이티브에 가까운 성능을 발휘합니다.
주요 특성:
- 샌드박스 실행: WASM 모듈은 호스트가 명시적으로 허용한 리소스에만 접근할 수 있습니다. 허가 없이는 파일 시스템, 네트워크, OS에 접근할 수 없습니다. 이는 네이티브 코드와 근본적으로 다른 점입니다.
- 선형 메모리: WASM과 호스트 간에 공유되는 단일 연속
ArrayBuffer입니다. 복잡한 데이터(문자열, 구조체)는 메모리에 쓰고 포인터를 공유하는 방식으로 전달됩니다. - 제한된 타입: WASM은 네이티브로
i32,i64,f32,f64네 가지 타입만 지원합니다. 그 외(문자열, 배열, 객체)는 선형 메모리 또는 Component Model을 통한 인코딩이 필요합니다. - 이식성: 동일한
.wasm바이너리가 WASM 런타임이 있는 모든 플랫폼에서 재컴파일 없이 실행됩니다.
WASM vs JavaScript
WASM은 JavaScript를 대체하는 것이 아닙니다. JavaScript를 보완하는 것입니다.
| 항목 | JavaScript | WebAssembly |
|---|---|---|
| 파싱 | 런타임에 파싱 + 컴파일 | 사전 컴파일된 바이너리, 디코드만 필요 |
| 실행 속도 | JIT 최적화, 변동 있음 | 네이티브에 가까움, 일관적 |
| 시작 | 작은 스크립트에서 빠름 | 빠른 디코드, 예측 가능 |
| DOM 접근 | 직접 | 간접 (JS 글루 코드 경유) |
| 최적 용도 | UI, DOM 조작, 이벤트 처리 | CPU 집약적 연산 |
| 가비지 컬렉션 | 내장 | WasmGC(새로운 기능) 또는 수동 |
UI와 DOM 작업에는 JavaScript를 사용하세요. 이미지 처리, 비디오 인코딩, 물리 시뮬레이션, 암호화, 데이터 파싱 같은 무거운 연산에는 WASM을 사용하세요.
WASM 3.0: 새로운 기능
WebAssembly 3.0은 2025년 9월에 W3C 표준이 되었으며, 수년간 개발되어 온 아홉 가지 기능을 표준화했습니다.
| 기능 | 가능하게 하는 것 |
|---|---|
| WasmGC | WASM의 네이티브 가비지 컬렉션. 관리 언어(Java, Kotlin, Dart)가 자체 GC 런타임을 포함하지 않고 WASM으로 컴파일 가능. Chrome 119+, Firefox 120+, Safari 18.2+ 지원. |
| Exception Handling | WASM의 네이티브 try/catch. 이전에는 예외 처리에 JavaScript로의 비용이 큰 라운드트립이 필요했음. |
| Tail Calls | 스택 오버플로 없는 효율적인 재귀 구현. 함수형 언어에 필수적. |
| Relaxed SIMD | 병렬 데이터 처리를 위한 128비트 벡터 명령어. 하드웨어별 최적화 가능. |
| Memory64 | 4GB 선형 메모리 제한 돌파. 대규모 데이터 처리에 필수. |
| Multi-memory | 하나의 모듈에 여러 독립 메모리 영역. |
가장 영향력이 큰 것은 WasmGC입니다. 이전에는 Java나 Kotlin을 WASM으로 컴파일하면 가비지 컬렉터 전체를 바이너리에 포함해야 해서 파일 크기가 비대해졌습니다. 이제 브라우저 자체의 GC가 JavaScript와 마찬가지로 WASM 모듈의 메모리 관리를 담당합니다.
WASI: 브라우저를 넘어선 WebAssembly
브라우저에서의 WASM은 강력하지만, WASM을 유니버설 런타임으로 만드는 것은 **WASI(WebAssembly System Interface)**입니다. WASI는 시스템 리소스 - 파일, 네트워킹, 클럭, 난수 - 에 대한 표준화된 인터페이스를 제공하여 WASM 모듈을 브라우저 외부에서 실행할 수 있게 합니다.
WASI Preview 2(현재 안정 릴리스)는 다음 인터페이스를 정의합니다:
wasi:filesystem- 케이퍼빌리티 핸들(기존 파일 디스크립터가 아닌)을 통한 파일 작업wasi:sockets- TCP/UDP 네트워킹wasi:http- HTTP 요청/응답 처리wasi:clocks- 월 클럭, 모노토닉 클럭wasi:random- 암호학적 난수wasi:cli- 명령줄 인수, 환경 변수, 표준 입출력
핵심 원칙은 케이퍼빌리티 기반 보안입니다. WASM 모듈은 호스트가 특정 디렉토리에 대한 핸들을 명시적으로 부여하지 않는 한 파일 시스템에 접근할 수 없습니다. 이로 인해 WASI는 네이티브 실행 파일 실행보다 근본적으로 더 안전합니다.
WASI 1.0으로의 여정
WASI 0.3.0(async/동시성 프리미티브 추가)은 2026년에 예정되어 있으며, 이후 WASI 1.0이 뒤따릅니다. 주요 추가 사항은 제로 카피 스트리밍 I/O를 갖춘 언어 통합 비동기 처리입니다.
Component Model
코어 WASM 모듈은 숫자만 교환할 수 있습니다. Component Model은 WASM 위에 풍부한 타입 시스템과 조합 가능한 레이어를 추가하여 이 제한을 해결합니다.
WIT (WebAssembly Interface Types)
WIT는 컴포넌트가 풍부한 타입 - 문자열, 레코드, 리스트, 배리언트, 열거형 - 으로 임포트와 익스포트를 선언할 수 있는 인터페이스 정의 언어입니다. i32와 f64만이 아닙니다.
// calculator.wit package myorg:calculator@1.0.0; interface operations { record calculation { expression: string, result: f64, timestamp: u64, } add: func(a: f64, b: f64) -> f64; multiply: func(a: f64, b: f64) -> f64; history: func() -> list<calculation>; } world calculator { export operations; }
wit-bindgen 같은 도구 체인이 WIT 파일에서 언어별 바인딩을 생성합니다. Rust 컴포넌트와 Python 컴포넌트가 상대방의 구현 언어를 모르면서도 WIT 계약을 통해 문자열, 레코드, 리스트를 교환할 수 있습니다.
Rust로 첫 WASM 모듈 만들기
Rust는 가장 성숙한 WASM 도구를 갖추고 있습니다. 실용적인 예제를 만들어 보겠습니다: 브라우저에서 실행되는 이미지 처리 모듈입니다.
설정
# Install the WASM target for Rust rustup target add wasm32-unknown-unknown # Install wasm-pack (builds Rust to WASM + generates JS bindings) cargo install wasm-pack # Create a new library project cargo new --lib image-processor cd image-processor
Cargo.toml 설정
[package] name = "image-processor" version = "0.1.0" edition = "2021" [lib] crate-type = ["cdylib"] [dependencies] wasm-bindgen = "0.2"
Rust 코드 작성
// src/lib.rs use wasm_bindgen::prelude::*; /// Convert an image buffer to grayscale. /// Input: RGBA pixel data as a flat u8 array (4 bytes per pixel). /// Output: Modified in place for zero-copy performance. #[wasm_bindgen] pub fn grayscale(pixels: &mut [u8]) { for chunk in pixels.chunks_exact_mut(4) { let r = chunk[0] as f32; let g = chunk[1] as f32; let b = chunk[2] as f32; // ITU-R BT.709 luminance coefficients let gray = (0.2126 * r + 0.7152 * g + 0.0722 * b) as u8; chunk[0] = gray; chunk[1] = gray; chunk[2] = gray; // chunk[3] is alpha, leave unchanged } } /// Adjust brightness of an image. /// factor > 1.0 brightens, < 1.0 darkens. #[wasm_bindgen] pub fn adjust_brightness(pixels: &mut [u8], factor: f32) { for chunk in pixels.chunks_exact_mut(4) { chunk[0] = ((chunk[0] as f32 * factor).min(255.0)) as u8; chunk[1] = ((chunk[1] as f32 * factor).min(255.0)) as u8; chunk[2] = ((chunk[2] as f32 * factor).min(255.0)) as u8; } } /// Invert all colors in the image. #[wasm_bindgen] pub fn invert(pixels: &mut [u8]) { for chunk in pixels.chunks_exact_mut(4) { chunk[0] = 255 - chunk[0]; chunk[1] = 255 - chunk[1]; chunk[2] = 255 - chunk[2]; } } /// Calculate the average brightness of an image (0-255). #[wasm_bindgen] pub fn average_brightness(pixels: &[u8]) -> f32 { let mut total: f64 = 0.0; let pixel_count = pixels.len() / 4; for chunk in pixels.chunks_exact(4) { let luminance = 0.2126 * chunk[0] as f64 + 0.7152 * chunk[1] as f64 + 0.0722 * chunk[2] as f64; total += luminance; } (total / pixel_count as f64) as f32 }
빌드
wasm-pack build --target web
이렇게 하면 pkg/ 디렉토리가 생성되며 다음을 포함합니다:
image_processor_bg.wasm- 컴파일된 WASM 바이너리image_processor.js- TypeScript 타입 정의가 포함된 JavaScript 글루 코드package.json- npm에 바로 배포 가능
JavaScript에서 사용
<!DOCTYPE html> <html> <head><title>WASM Image Processor</title></head> <body> <canvas id="canvas" width="800" height="600"></canvas> <button onclick="applyGrayscale()">Grayscale</button> <button onclick="applyBrightness()">Brighten</button> <button onclick="applyInvert()">Invert</button> <script type="module"> import init, { grayscale, adjust_brightness, invert } from "./pkg/image_processor.js"; let ctx; let imageData; async function setup() { await init(); const canvas = document.getElementById("canvas"); ctx = canvas.getContext("2d"); // Load an image onto the canvas const img = new Image(); img.onload = () => { ctx.drawImage(img, 0, 0); imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); }; img.src = "photo.jpg"; } window.applyGrayscale = () => { grayscale(imageData.data); ctx.putImageData(imageData, 0, 0); }; window.applyBrightness = () => { adjust_brightness(imageData.data, 1.3); ctx.putImageData(imageData, 0, 0); }; window.applyInvert = () => { invert(imageData.data); ctx.putImageData(imageData, 0, 0); }; setup(); </script> </body> </html>
핵심 포인트: imageData.data는 ArrayBuffer에 의해 뒷받침되는 Uint8ClampedArray입니다. WASM에 전달될 때 같은 메모리를 공유합니다 - 복사가 발생하지 않습니다. Rust 함수가 픽셀을 제자리에서 수정하면, JavaScript 측에서 즉시 변경 사항을 볼 수 있습니다.
저수준: 수동 WASM 인스턴스화
wasm-bindgen을 사용하고 싶지 않다면, WASM 모듈을 직접 인스턴스화할 수 있습니다:
const response = await fetch("calculator.wasm"); const { instance } = await WebAssembly.instantiateStreaming(response, { env: { // Functions the WASM module can call log_result: (value) => console.log("Result:", value), }, }); // Call exported functions const { add, multiply } = instance.exports; console.log(add(5, 3)); // 8 console.log(multiply(4, 7)); // 28
이 방법은 최소한의 오버헤드가 필요하고 풍부한 타입 상호 운용이 필요 없을 때 유용합니다.
성능: WASM vs JavaScript
실제 벤치마크에서 CPU 집약적 작업에 대한 상당한 속도 향상이 나타납니다:
| 작업 | JavaScript | WASM | 속도 향상 |
|---|---|---|---|
| 4K 이미지 처리 | 180ms | 8ms (SIMD 사용) | 22배 |
| 이미지 리사이즈 (4K) | 250ms | 45ms | 5.5배 |
| 물리 시뮬레이션 (1만 엔티티) | 프레임 드롭 | 부드러운 60fps | 약 10배 |
| JSON 파싱 (대용량 페이로드) | 12ms | 3ms | 4배 |
| 암호학적 해싱 | 45ms | 6ms | 7.5배 |
WASM은 네이티브 코드 속도의 약 95%로 실행됩니다. 가장 큰 이점은 다음에서 나옵니다:
- 예측 가능한 성능 (JIT 워밍업 없음, GC 일시 정지 없음)
- 병렬 데이터 처리를 위한 SIMD 명령어
- 가비지 컬렉터 간섭 없는 직접 메모리 접근
WASM이 더 빠르지 않은 경우: DOM 조작, 작은 연산, I/O 바운드 작업. JavaScript는 이러한 작업에 이미 최적화되어 있습니다.
프로덕션 사례
Figma: 실시간 벡터 렌더링
Figma의 핵심 렌더링 엔진은 C++를 WASM으로 컴파일한 것입니다. 모든 도형, 그라디언트, 이펙트가 WASM에서 계산되어 Canvas 요소에 그려집니다. 덕분에 Figma는 브라우저에서 수천 개의 레이어가 있는 복잡한 디자인을 60fps로 처리할 수 있습니다 - 순수 JavaScript로는 불가능한 성능입니다.
웹 버전 Adobe Photoshop
Adobe는 Rust를 사용하여 Photoshop의 주요 필터와 도구를 WASM으로 포팅했습니다. 벤치마크에 따르면 WASM SIMD를 사용한 4K 이미지 처리는 22ms이며, JavaScript의 180ms와 비교하면 8배 향상입니다 - 이로 인해 실시간 필터 미리보기가 가능해졌습니다.
Cloudflare Workers
Cloudflare는 330개 이상의 엣지 로케이션에 걸친 V8 아이솔레이트에서 WASM 모듈을 실행합니다. 콜드 스타트는 1-5ms입니다(컨테이너 기반 서버리스의 100-500ms와 비교). 2026년 2월에는 WASM을 사용하여 엣지 네트워크 전체에 Llama-3-8b 추론을 배포했습니다.
Google Meet
Google Meet의 배경 블러와 가상 배경은 실시간 비디오 처리를 위해 SIMD가 적용된 WASM을 사용합니다. WASM 모듈은 각 비디오 프레임을 30fps의 부드러운 비디오를 유지할 수 있을 만큼 빠르게 처리합니다.
브라우저 지원 (2026년)
| 기능 | Chrome | Firefox | Safari | Edge |
|---|---|---|---|---|
| 코어 WASM | 완전 | 완전 | 완전 | 완전 |
| Threads | 지원 | 지원 | 지원 | 지원 |
| SIMD (128비트) | 지원 | 지원 | 지원 | 지원 |
| WasmGC | 119+ | 120+ | 18.2+ | 지원 |
| Exception Handling | 지원 | 지원 | 지원 | 지원 |
| Memory64 | 지원 | 지원 | 부분적 | 지원 |
모든 주요 브라우저가 WASM을 완전히 지원합니다. 새로운 기능들(WasmGC, Exception Handling)은 폭넓게 사용 가능한 상태에 도달했습니다.
도구 레퍼런스
| 도구 | 용도 | 설치 |
|---|---|---|
| wasm-pack | Rust를 WASM으로 빌드, npm 패키지 생성 | cargo install wasm-pack |
| wasm-bindgen | Rust/JS 상호 운용 바인딩 (wasm-pack이 사용) | Cargo.toml의 의존성 |
| wasm-opt | 바이너리 크기 최적화 (50% 이상 감소) | Binaryen의 일부: brew install binaryen |
| wit-bindgen | WIT 파일에서 바인딩 생성 | cargo install wit-bindgen-cli |
| Wasmtime | 서버 측 WASM 런타임 (WASI 레퍼런스 구현) | brew install wasmtime |
| Wasmer | WASI를 지원하는 대안 WASM 런타임 | curl https://get.wasmer.io -sSfL | sh |
| wasm-feature-detect | 런타임 브라우저 기능 감지 | npm install wasm-feature-detect |
바이너리 크기 최적화
WASM 바이너리는 크기가 클 수 있습니다. 줄이는 방법은 다음과 같습니다:
# Cargo.toml [profile.release] opt-level = "z" # Optimize for size lto = true # Link-time optimization codegen-units = 1 # Better optimization, slower compile strip = true # Strip debug symbols
# Build in release mode wasm-pack build --release --target web # Further optimize with wasm-opt wasm-opt -Oz pkg/image_processor_bg.wasm -o pkg/image_processor_bg.wasm
일반적인 Rust WASM 모듈은 이러한 최적화를 적용하면 500KB에서 50KB 미만으로 줄어듭니다.
시작 로드맵
결론
WebAssembly는 더 이상 실험적인 기술이 아닙니다. 웹에서 가장 높은 성능을 요구하는 애플리케이션에서 사용되는 프로덕션 기술입니다. 네이티브에 가까운 성능, 샌드박스 보안, 범용 이식성 - 이 세 가지를 모두 제공하는 컴파일 타겟은 다른 곳에 없습니다.
애플리케이션 전체를 WASM으로 다시 작성할 필요는 없습니다. 하나의 CPU 집약적 함수 - 이미지 필터, 데이터 파서, 물리 계산 - 를 WASM으로 컴파일하고 JavaScript에서 호출해 보세요. 차이를 측정하세요. 그런 다음 WASM이 다른 어디에서 도움이 될 수 있는지 결정하세요.
도구는 성숙했고, 브라우저 지원은 보편적이며, 생태계는 성장하고 있습니다. Rust를 쓰고 있다면, 브라우저까지 단 하나의 명령어만 있으면 됩니다.
시작 체크리스트:
- Rust와 wasm-pack 설치 완료
- 첫 WASM 모듈을 빌드하고 브라우저에서 실행
- JavaScript 상호 운용 작동 (JS에서 WASM 호출)
- 크기 최적화가 적용된 릴리스 빌드
- 순수 JavaScript 구현과의 성능 벤치마크
- 서버 측 사용을 위한 Wasmtime으로 WASI 탐색