WebAssembly 완전 가이드 | 웹에서 네이티브 성능 구현하기
이 글의 핵심
JavaScript보다 10배 빠른 WebAssembly. C/C++/Rust 코드를 브라우저에서 실행하며, 게임·영상 처리·암호화 등 고성능이 필요한 작업에 최적입니다. JavaScript와 완벽히 상호 운용되는 차세대 웹 표준입니다.
이 글의 핵심
WebAssembly(WASM)는 웹 브라우저에서 네이티브에 가까운 성능을 제공하는 바이너리 포맷입니다. C/C++/Rust로 작성한 코드를 JavaScript보다 10배 빠르게 실행하며, 게임·영상 처리·암호화 등 고성능 작업에 최적화되어 있습니다.
목차
WebAssembly란?
WebAssembly는 2017년 W3C에서 표준화한 저수준 바이너리 포맷입니다.
🚀 핵심 특징
1. 네이티브급 성능
JavaScript:
- 인터프리터 or JIT 컴파일
- 가비지 컬렉션
- 동적 타입
WebAssembly:
- AOT 컴파일된 바이너리
- 수동 메모리 관리
- 정적 타입
→ 10-100배 빠름
2. 멀티 언어 지원
- C/C++: Emscripten
- Rust: wasm-bindgen
- Go: TinyGo
- AssemblyScript: TypeScript와 유사
3. 안전한 샌드박스
- 브라우저 보안 모델 내에서 실행
- 메모리 격리
- 명시적 권한 필요
4. JavaScript와 상호 운용
// JavaScript에서 WASM 호출
const result = wasmModule.add(10, 20);
// WASM에서 JavaScript 호출
wasmModule.alertUser("Hello from WASM!");
WebAssembly 시작하기
1️⃣ Rust로 WASM 개발 (권장)
Rust 설치
# Rust 설치
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# wasm32 타겟 추가
rustup target add wasm32-unknown-unknown
# wasm-pack 설치
cargo install wasm-pack
프로젝트 생성
# Rust WASM 프로젝트 생성
cargo new --lib hello-wasm
cd hello-wasm
Cargo.toml 설정
[package]
name = "hello-wasm"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
Rust 코드 작성
// src/lib.rs
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
#[wasm_bindgen]
pub struct Calculator {
value: f64,
}
#[wasm_bindgen]
impl Calculator {
#[wasm_bindgen(constructor)]
pub fn new() -> Calculator {
Calculator { value: 0.0 }
}
pub fn add(&mut self, x: f64) {
self.value += x;
}
pub fn get_value(&self) -> f64 {
self.value
}
}
빌드
# WASM으로 빌드
wasm-pack build --target web
# 결과물:
# pkg/
# ├── hello_wasm.js
# ├── hello_wasm_bg.wasm
# └── hello_wasm.d.ts
HTML에서 사용
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello WASM</title>
</head>
<body>
<h1>WebAssembly Example</h1>
<button id="btn">Run WASM</button>
<div id="result"></div>
<script type="module">
import init, { add, greet, Calculator } from './pkg/hello_wasm.js';
async function run() {
// WASM 초기화
await init();
document.getElementById('btn').addEventListener('click', () => {
// 함수 호출
const sum = add(10, 20);
const greeting = greet('Alice');
// 클래스 사용
const calc = new Calculator();
calc.add(5);
calc.add(10);
const value = calc.get_value();
document.getElementById('result').innerHTML = `
Sum: ${sum}<br>
Greeting: ${greeting}<br>
Calculator: ${value}
`;
});
}
run();
</script>
</body>
</html>
C/C++로 WASM 개발
Emscripten 설치
# Emscripten 설치
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh
C 코드 작성
// hello.c
#include <emscripten.h>
#include <stdio.h>
// JavaScript에서 호출 가능
EMSCRIPTEN_KEEPALIVE
int add(int a, int b) {
return a + b;
}
EMSCRIPTEN_KEEPALIVE
double fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
int main() {
printf("Hello from WebAssembly!\n");
return 0;
}
컴파일
# WASM으로 컴파일
emcc hello.c -o hello.html \
-s WASM=1 \
-s EXPORTED_FUNCTIONS='["_add","_fibonacci"]' \
-s EXPORTED_RUNTIME_METHODS='["cwrap"]'
# 결과물:
# hello.html
# hello.js
# hello.wasm
JavaScript에서 사용
<script src="hello.js"></script>
<script>
Module.onRuntimeInitialized = () => {
// C 함수 래핑
const add = Module.cwrap('add', 'number', ['number', 'number']);
const fibonacci = Module.cwrap('fibonacci', 'number', ['number']);
console.log(add(10, 20)); // 30
console.log(fibonacci(10)); // 55
};
</script>
실전 예제: 이미지 필터
Rust로 이미지 처리
// src/lib.rs
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn grayscale(data: &mut [u8], width: u32, height: u32) {
for i in (0..data.len()).step_by(4) {
let r = data[i] as f32;
let g = data[i + 1] as f32;
let b = data[i + 2] as f32;
// 그레이스케일 변환
let gray = (0.299 * r + 0.587 * g + 0.114 * b) as u8;
data[i] = gray;
data[i + 1] = gray;
data[i + 2] = gray;
}
}
#[wasm_bindgen]
pub fn blur(data: &mut [u8], width: u32, height: u32) {
let w = width as usize;
let h = height as usize;
let mut output = vec![0u8; data.len()];
// 간단한 박스 블러 (3x3)
for y in 1..h-1 {
for x in 1..w-1 {
let idx = (y * w + x) * 4;
for c in 0..3 {
let mut sum = 0u32;
for dy in -1i32..=1 {
for dx in -1i32..=1 {
let ny = (y as i32 + dy) as usize;
let nx = (x as i32 + dx) as usize;
let i = (ny * w + nx) * 4 + c;
sum += data[i] as u32;
}
}
output[idx + c] = (sum / 9) as u8;
}
output[idx + 3] = data[idx + 3]; // 알파 채널 유지
}
}
data.copy_from_slice(&output);
}
JavaScript에서 사용
<!DOCTYPE html>
<html>
<head>
<title>Image Filter</title>
</head>
<body>
<input type="file" id="fileInput" accept="image/*">
<canvas id="canvas"></canvas>
<br>
<button id="grayscale">Grayscale</button>
<button id="blur">Blur</button>
<script type="module">
import init, { grayscale, blur } from './pkg/image_filter.js';
let imageData;
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// WASM 초기화
await init();
// 이미지 로드
document.getElementById('fileInput').addEventListener('change', (e) => {
const file = e.target.files[0];
const img = new Image();
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
imageData = ctx.getImageData(0, 0, img.width, img.height);
};
img.src = URL.createObjectURL(file);
});
// 그레이스케일 적용
document.getElementById('grayscale').addEventListener('click', () => {
if (!imageData) return;
const data = imageData.data;
const start = performance.now();
// WASM 호출
grayscale(data, canvas.width, canvas.height);
const end = performance.now();
console.log(`Grayscale: ${end - start}ms`);
ctx.putImageData(imageData, 0, 0);
});
// 블러 적용
document.getElementById('blur').addEventListener('click', () => {
if (!imageData) return;
const data = imageData.data;
const start = performance.now();
// WASM 호출
blur(data, canvas.width, canvas.height);
const end = performance.now();
console.log(`Blur: ${end - start}ms`);
ctx.putImageData(imageData, 0, 0);
});
</script>
</body>
</html>
성능 벤치마크
JavaScript vs WASM
// JavaScript 버전
function fibonacciJS(n) {
if (n <= 1) return n;
return fibonacciJS(n - 1) + fibonacciJS(n - 2);
}
// 벤치마크
console.time('JS');
console.log(fibonacciJS(40)); // 102334155
console.timeEnd('JS');
// JS: 1200ms
console.time('WASM');
console.log(fibonacciWasm(40)); // 102334155
console.timeEnd('WASM');
// WASM: 120ms (10배 빠름)
WASM과 JavaScript 상호 운용
JavaScript 함수 호출 (Rust)
// src/lib.rs
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
// JavaScript 함수 선언
fn alert(s: &str);
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
#[wasm_bindgen]
pub fn greet_with_alert(name: &str) {
let message = format!("Hello, {}!", name);
log(&message);
alert(&message);
}
JavaScript 객체 전달
use wasm_bindgen::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct User {
name: String,
age: u32,
}
#[wasm_bindgen]
pub fn process_user(val: JsValue) -> JsValue {
// JavaScript 객체 → Rust 구조체
let user: User = serde_wasm_bindgen::from_value(val).unwrap();
// 처리
let processed = User {
name: user.name.to_uppercase(),
age: user.age + 1,
};
// Rust 구조체 → JavaScript 객체
serde_wasm_bindgen::to_value(&processed).unwrap()
}
// JavaScript
import { process_user } from './pkg/myapp.js';
const user = { name: 'alice', age: 25 };
const result = process_user(user);
console.log(result); // { name: 'ALICE', age: 26 }
WASM의 실제 사용 사례
1. Figma (디자인 도구)
- C++로 작성된 렌더링 엔진을 WASM으로 포팅
- 브라우저에서 네이티브급 성능
2. Google Earth
- C++로 작성된 3D 엔진을 WASM으로 이식
- 플러그인 없이 브라우저에서 실행
3. Photoshop Web
- 이미지 처리 엔진을 WASM으로 최적화
- 복잡한 필터를 실시간 처리
4. Unity 게임
- Unity 게임을 WASM으로 내보내기
- 웹 브라우저에서 3D 게임 실행
5. FFmpeg.wasm
- 비디오 인코딩/디코딩을 브라우저에서
- 서버 없이 클라이언트에서 처리
WASM의 한계
❌ WASM이 적합하지 않은 경우
- DOM 조작: JavaScript가 훨씬 빠름
- 간단한 UI 로직: 오버헤드가 더 큼
- 네트워크 I/O: 병목이 네트워크이므로 이점 없음
- 작은 계산: WASM 로딩 시간이 더 오래 걸림
✅ WASM이 유용한 경우
- 계산 집약적 작업: 암호화, 압축, 수학 연산
- 이미지/영상 처리: 필터, 변환, 렌더링
- 게임 엔진: 물리 엔진, 충돌 감지
- 레거시 코드 포팅: C/C++ 라이브러리를 웹에서 사용
핵심 정리
✅ WebAssembly의 장점
- 네이티브급 성능: JavaScript보다 10-100배 빠름
- 멀티 언어 지원: C/C++/Rust/Go 등
- 안전한 실행: 브라우저 샌드박스 내에서
- JavaScript 상호 운용: 기존 코드와 통합 가능
- 표준화: 모든 주요 브라우저 지원
🚀 다음 단계
- MDN WebAssembly에서 심화 학습
- wasm-bindgen Book에서 Rust WASM 마스터
- Emscripten Docs에서 C/C++ WASM 배우기
시작하기:
cargo new --lib my-wasm명령으로 5분 만에 첫 WebAssembly 프로젝트를 시작하고, 네이티브급 성능을 경험하세요! 🚀