- Authors

- Name
- Youngju Kim
- @fjvbn20031
- WebAssembly란 무엇인가?
- 1. WAT 텍스트 포맷과 바이트코드 구조
- 2. WASI: WebAssembly System Interface
- 3. Wasm 생태계: Rust, AssemblyScript, Emscripten
- 4. 브라우저 AI: ONNX Runtime Web, WebNN, WebGPU
- 5. 엣지 AI 배포: Cloudflare Workers, Fastly, AWS Lambda@Edge
- 6. IoT & 임베디드 Wasm
- 7. 성능 벤치마킹: Wasm vs Native
- 8. 실전 사례
- 퀴즈
- 결론
WebAssembly란 무엇인가?
WebAssembly(Wasm)는 2019년 W3C 공식 웹 표준으로 채택된 바이너리 명령어 포맷입니다. 브라우저, 서버, IoT 디바이스 어디서나 근-네이티브(near-native) 속도로 실행되며, JavaScript의 유일한 브라우저 실행 언어 지위를 사실상 종식시켰습니다.
Wasm의 핵심 설계 원칙은 다음 네 가지입니다.
- 안전성(Safety): 샌드박스 메모리 모델로 호스트 환경을 보호합니다.
- 이식성(Portability): CPU 아키텍처에 무관하게 동일하게 동작합니다.
- 속도(Speed): JIT/AOT 컴파일로 JavaScript 대비 수 배 빠른 연산 처리를 달성합니다.
- 개방성(Open): 특정 언어나 플랫폼에 종속되지 않습니다.
1. WAT 텍스트 포맷과 바이트코드 구조
WebAssembly는 .wasm 바이너리 포맷과 사람이 읽을 수 있는 .wat(WebAssembly Text Format) 두 가지 표현을 가집니다.
WAT 예시: 두 정수의 합
(module
(func $add (param $a i32) (param $b i32) (result i32)
local.get $a
local.get $b
i32.add)
(export "add" (func $add)))
이 코드가 .wasm으로 컴파일되면 매직 넘버 \0asm(0x00 0x61 0x73 0x6D)으로 시작하는 바이너리가 됩니다.
바이트코드 구조
Wasm 모듈은 섹션(section) 단위로 구성됩니다.
| 섹션 ID | 이름 | 설명 |
|---|---|---|
| 1 | Type | 함수 시그니처 정의 |
| 3 | Function | 함수 인덱스 |
| 7 | Export | 외부로 노출할 심볼 |
| 10 | Code | 실제 함수 바디 |
선형 메모리(Linear Memory)
Wasm은 단일 연속 바이트 배열인 선형 메모리를 사용합니다. 64KB 페이지 단위로 할당되며, JavaScript에서 WebAssembly.Memory 객체로 직접 접근할 수 있습니다.
const memory = new WebAssembly.Memory({ initial: 1, maximum: 10 })
const buffer = new Uint8Array(memory.buffer)
// 오프셋 0부터 직접 읽기/쓰기
buffer[0] = 42
포인터 연산 자체는 안전하게 샌드박스 내부에 격리되므로, 호스트 메모리에는 접근할 수 없습니다.
2. WASI: WebAssembly System Interface
WASI는 Wasm 모듈이 파일 시스템, 네트워크, 환경 변수 등 OS 기능에 접근하기 위한 표준 시스템 인터페이스입니다. "Write once, run anywhere"를 진정으로 실현하기 위해 Solomon Hykes(Docker 창시자)가 필요성을 공개적으로 역설하면서 주목받았습니다.
(import "wasi_snapshot_preview1" "fd_write"
(func $fd_write (param i32 i32 i32 i32) (result i32)))
WASI가 제공하는 주요 추상화는 다음과 같습니다.
- 파일 시스템:
fd_read,fd_write,path_open - 시계:
clock_time_get - 환경 변수:
environ_get - 네트워크(WASI 0.2):
wasi:sockets인터페이스
WASI 0.2(Component Model)는 2024년 정식 출시되어 고수준 인터페이스 정의 언어인 WIT(Wasm Interface Types)를 도입했습니다.
3. Wasm 생태계: Rust, AssemblyScript, Emscripten
Rust → WebAssembly (wasm-pack)
Rust는 현재 Wasm 생태계에서 가장 성숙한 언어입니다. wasm-pack을 사용하면 npm 패키지로 즉시 배포 가능한 Wasm 모듈을 생성할 수 있습니다.
// src/lib.rs
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u32 {
match n {
0 => 0,
1 => 1,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}
#[wasm_bindgen]
pub fn matrix_multiply(a: &[f32], b: &[f32], n: usize) -> Vec<f32> {
let mut result = vec![0.0f32; n * n];
for i in 0..n {
for j in 0..n {
for k in 0..n {
result[i * n + j] += a[i * n + k] * b[k * n + j];
}
}
}
result
}
빌드 및 배포:
# wasm-pack 설치
cargo install wasm-pack
# 브라우저 타겟으로 빌드
wasm-pack build --target web
# Node.js 타겟으로 빌드
wasm-pack build --target nodejs
생성된 pkg/ 디렉토리에는 .wasm 바이너리, JavaScript 글루 코드, TypeScript 타입 정의가 포함됩니다.
JavaScript에서 Wasm 모듈 호출
import init, { fibonacci, matrix_multiply } from './pkg/my_module.js'
async function main() {
// Wasm 모듈 초기화
await init()
// Fibonacci 계산
console.log(fibonacci(40)) // 102334155
// 행렬 곱셈 (4x4)
const a = new Float32Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])
const b = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1])
const result = matrix_multiply(a, b, 4)
console.log(result)
}
main()
AssemblyScript
TypeScript 문법으로 Wasm을 작성할 수 있는 언어입니다.
// assembly/index.ts
export function add(a: i32, b: i32): i32 {
return a + b
}
export function sumArray(ptr: usize, len: i32): i64 {
let sum: i64 = 0
for (let i = 0; i < len; i++) {
sum += load<i32>(ptr + i * 4)
}
return sum
}
Emscripten (C/C++)
C/C++ 코드베이스를 Wasm으로 포팅할 때 사용합니다. Figma와 Google Earth가 이 방식을 사용합니다.
# C 파일을 Wasm으로 컴파일
emcc compute.c -O3 -o compute.js \
-s WASM=1 \
-s EXPORTED_FUNCTIONS='["_process_image"]' \
-s EXPORTED_RUNTIME_METHODS='["ccall","cwrap"]'
4. 브라우저 AI: ONNX Runtime Web, WebNN, WebGPU
ONNX Runtime Web으로 브라우저 AI 추론
ONNX Runtime Web은 브라우저에서 ONNX 모델을 직접 실행합니다. 백엔드로 WebAssembly(CPU), WebGL, WebGPU를 지원합니다.
import * as ort from 'onnxruntime-web'
async function runInference() {
// WebGPU 백엔드 우선, 폴백으로 Wasm 사용
const session = await ort.InferenceSession.create('/models/bert-base.onnx', {
executionProviders: ['webgpu', 'wasm'],
graphOptimizationLevel: 'all',
})
// 입력 텐서 생성
const inputIds = new BigInt64Array([101n, 2023n, 2003n, 102n])
const attentionMask = new BigInt64Array([1n, 1n, 1n, 1n])
const feeds = {
input_ids: new ort.Tensor('int64', inputIds, [1, 4]),
attention_mask: new ort.Tensor('int64', attentionMask, [1, 4]),
}
const results = await session.run(feeds)
console.log('Logits:', results.logits.data)
}
WebGPU Compute Shader로 행렬 연산
WebGPU는 GPU의 병렬 연산 능력을 웹에서 직접 활용할 수 있게 해줍니다. WebGL 대비 ML 추론에 훨씬 적합한 이유는 **컴퓨트 쉐이더(Compute Shader)**를 지원하기 때문입니다.
async function webgpuMatmul(matA, matB, M, N, K) {
const adapter = await navigator.gpu.requestAdapter()
const device = await adapter.requestDevice()
const shaderCode = `
@group(0) @binding(0) var<storage, read> matA: array<f32>;
@group(0) @binding(1) var<storage, read> matB: array<f32>;
@group(0) @binding(2) var<storage, read_write> result: array<f32>;
@compute @workgroup_size(8, 8)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
let row = gid.x;
let col = gid.y;
var sum = 0.0;
for (var k = 0u; k < ${K}u; k++) {
sum += matA[row * ${K}u + k] * matB[k * ${N}u + col];
}
result[row * ${N}u + col] = sum;
}
`
const shaderModule = device.createShaderModule({ code: shaderCode })
// ... 버퍼 생성, 파이프라인 설정, 디스패치
}
WebNN API
WebNN(Web Neural Network API)은 브라우저가 OS의 하드웨어 가속(NPU, GPU, DSP)을 직접 활용할 수 있도록 하는 W3C 표준 API입니다.
const context = await navigator.ml.createContext({ deviceType: 'gpu' })
const builder = new MLGraphBuilder(context)
const input = builder.input('input', { type: 'float32', dimensions: [1, 3, 224, 224] })
const weights = builder.constant(/* ... */)
const conv = builder.conv2d(input, weights, { padding: [1, 1, 1, 1] })
const relu = builder.relu(conv)
const graph = await builder.build({ output: relu })
const results = await context.compute(graph, inputs, outputs)
5. 엣지 AI 배포: Cloudflare Workers, Fastly, AWS Lambda@Edge
Cloudflare Workers AI
Cloudflare Workers는 V8 isolate 기반으로 전 세계 300개 이상의 PoP(Point of Presence)에서 실행됩니다. AI 바인딩을 통해 추론을 엣지에서 직접 수행합니다.
// Cloudflare Worker with AI binding
export default {
async fetch(request, env) {
const body = await request.json()
const userMessage = body.message
// AI 바인딩으로 LLM 추론
const response = await env.AI.run('@cf/meta/llama-3.1-8b-instruct', {
messages: [
{
role: 'system',
content: 'You are a helpful assistant.',
},
{
role: 'user',
content: userMessage,
},
],
max_tokens: 512,
})
return new Response(JSON.stringify({ reply: response.response }), {
headers: { 'Content-Type': 'application/json' },
})
},
}
wrangler.toml 설정:
name = "edge-ai-worker"
main = "src/index.js"
compatibility_date = "2024-09-23"
[ai]
binding = "AI"
Fastly Compute (Rust 기반 Wasm)
use fastly::{Error, Request, Response};
#[fastly::main]
fn main(req: Request) -> Result<Response, Error> {
let body = req.into_body_str();
// Wasm 내에서 직접 처리
let processed = process_with_wasm(&body);
Ok(Response::from_body(processed))
}
fn process_with_wasm(input: &str) -> String {
// 엣지에서 실행되는 비즈니스 로직
format!("Processed at edge: {}", input.to_uppercase())
}
AWS Lambda@Edge vs Cloudflare Workers 비교
| 특성 | Cloudflare Workers | AWS Lambda@Edge |
|---|---|---|
| 실행 모델 | V8 Isolate | 컨테이너 기반 |
| 콜드 스타트 | ~0ms | 100ms~수초 |
| 메모리 한도 | 128MB | 128MB~10GB |
| 실행 시간 | 최대 30초(유료) | 최대 30초 |
| 전역 PoP | 300+ | CloudFront 엣지 |
| 언어 지원 | JS/TS, Wasm | Node.js, Python, Java 등 |
Cloudflare Workers가 콜드 스타트가 빠른 핵심 이유는 프로세스가 아닌 V8 isolate를 재사용하기 때문입니다. 각 Worker는 별도의 OS 프로세스 대신 동일 프로세스 내 격리된 JavaScript 실행 컨텍스트에서 동작합니다.
6. IoT & 임베디드 Wasm
WasmEdge
WasmEdge는 CNCF(Cloud Native Computing Foundation) 샌드박스 프로젝트로, IoT 및 엣지 디바이스용 경량 Wasm 런타임입니다.
# WasmEdge 설치
curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash
# Python 스크립트를 Wasm으로 실행
wasmedge --dir .:. python_wasm.wasm script.py
WasmEdge로 Python 스크립트 실행:
# script.py - WasmEdge 위에서 동작
import sys
import json
def process_sensor_data(data):
temperature = data.get('temperature', 0)
humidity = data.get('humidity', 0)
# 이상 감지 로직
if temperature > 80 or humidity > 90:
return {'alert': True, 'reason': 'threshold_exceeded'}
return {'alert': False, 'status': 'normal'}
data = json.loads(sys.argv[1])
result = process_sensor_data(data)
print(json.dumps(result))
WAMR (WebAssembly Micro Runtime)
WAMR은 Bytecode Alliance가 개발한 초경량 Wasm 런타임으로 수 KB RAM에서도 동작합니다.
최소 메모리 요구사항:
- Interpreter 모드: ~85KB ROM + ~64KB RAM
- AOT 모드: ~60KB ROM + ~64KB RAM
Fermyon Spin
Spin은 Wasm 기반 마이크로서비스 프레임워크입니다.
# spin.toml
spin_manifest_version = 2
[application]
name = "iot-processor"
version = "0.1.0"
[[trigger.http]]
route = "/sensor"
component = "sensor-handler"
[component.sensor-handler]
source = "target/wasm32-wasi/release/sensor_handler.wasm"
[component.sensor-handler.build]
command = "cargo build --target wasm32-wasi --release"
7. 성능 벤치마킹: Wasm vs Native
SIMD in WebAssembly
Wasm SIMD(Single Instruction Multiple Data)는 128비트 벡터 연산을 지원하여 ML 워크로드를 크게 가속합니다.
// Rust에서 Wasm SIMD 활용
#[cfg(target_arch = "wasm32")]
use std::arch::wasm32::*;
pub fn dot_product_simd(a: &[f32], b: &[f32]) -> f32 {
let mut sum = f32x4_splat(0.0);
let chunks = a.len() / 4;
for i in 0..chunks {
let va = v128_load(a[i*4..].as_ptr() as *const v128);
let vb = v128_load(b[i*4..].as_ptr() as *const v128);
sum = f32x4_add(sum, f32x4_mul(va, vb));
}
// 수평 합산
let arr: [f32; 4] = unsafe { std::mem::transmute(sum) };
arr.iter().sum()
}
멀티스레딩: SharedArrayBuffer
Wasm 스레딩은 SharedArrayBuffer와 Atomics API를 활용합니다.
// 공유 메모리로 Wasm 멀티스레딩
const sharedMemory = new WebAssembly.Memory({
initial: 16,
maximum: 256,
shared: true, // SharedArrayBuffer 활성화
})
// Worker에 공유 메모리 전달
const worker = new Worker('wasm-worker.js')
worker.postMessage({ memory: sharedMemory })
벤치마크 결과 (참고치)
| 작업 | JavaScript | Wasm (단일) | Wasm + SIMD | Native C |
|---|---|---|---|---|
| 행렬 곱셈 (1024x1024) | 850ms | 210ms | 55ms | 40ms |
| SHA-256 해싱 (1MB) | 120ms | 35ms | 22ms | 18ms |
| 이미지 리사이즈 (4K) | 340ms | 95ms | 28ms | 20ms |
Wasm + SIMD는 네이티브 C와 10~40% 차이 이내로 수렴합니다.
8. 실전 사례
Figma
Figma의 렌더링 엔진 전체가 C++로 작성되어 Emscripten을 통해 Wasm으로 컴파일됩니다. 덕분에 복잡한 벡터 그래픽 연산을 브라우저에서 60fps로 처리합니다.
Google Earth
Google Earth for Web도 C++ 엔진을 Wasm으로 포팅했습니다. 수 GB에 달하는 3D 지형 렌더링 로직을 브라우저에서 실행합니다.
Pyodide: Python in Browser
Pyodide는 CPython 인터프리터 전체를 Wasm으로 컴파일하여 브라우저에서 Python을 실행합니다.
<script src="https://cdn.jsdelivr.net/pyodide/v0.27.0/full/pyodide.js"></script>
<script>
async function runPython() {
const pyodide = await loadPyodide()
// numpy, pandas를 브라우저에서 설치 및 사용
await pyodide.loadPackage(['numpy', 'pandas'])
const result = pyodide.runPython(`
import numpy as np
import pandas as pd
# 브라우저에서 numpy 연산
arr = np.random.randn(1000, 1000)
eigenvalues = np.linalg.eigvals(arr[:10, :10])
float(np.abs(eigenvalues).max())
`)
console.log('최대 고유값:', result)
}
runPython()
</script>
퀴즈
Q1. WebAssembly가 JavaScript보다 숫자 연산에서 빠른 성능을 내는 근본적 이유는?
정답: 정적 타입 시스템과 AOT/JIT 컴파일의 예측 가능성
설명: JavaScript는 동적 타입 언어로 런타임에 타입 추론, 인라인 캐싱, 히든 클래스 변환 등 수많은 최적화를 거쳐야 합니다. 반면 Wasm은 컴파일 시점에 모든 타입이 확정되어 있어 JIT 엔진이 즉시 최적화된 기계어를 생성할 수 있습니다. 또한 Wasm 바이트코드는 파싱 오버헤드가 매우 낮고, SIMD/멀티스레딩 명령어를 명시적으로 사용할 수 있습니다.
Q2. WASI(WebAssembly System Interface)가 필요한 이유와 제공하는 추상화는?
정답: OS 종속성 없이 시스템 리소스에 접근하기 위한 표준 인터페이스
설명: 브라우저 밖에서 실행되는 Wasm 모듈은 파일 시스템, 네트워크, 환경 변수 등에 접근해야 하지만 Wasm 자체는 샌드박스로 격리되어 있습니다. WASI는 POSIX 유사 시스템 콜을 Wasm 인터페이스로 표준화하여, 동일한 .wasm 바이너리가 Linux, Windows, macOS, 임베디드 시스템 등 어디서나 동일하게 동작하도록 합니다. Docker 컨테이너 없이 Wasm 하나로 어디서나 실행 가능한 "컨테이너의 미래"로 불립니다.
Q3. WebGPU가 WebGL보다 ML 추론에 더 적합한 이유는?
정답: 컴퓨트 쉐이더 지원과 명시적 GPU 메모리 관리
설명: WebGL은 그래픽 렌더링 파이프라인에 특화되어 있어 범용 병렬 연산(GPGPU)이 제한적입니다. ML 추론의 핵심인 행렬 곱셈, 합성곱 등을 수행하려면 프래그먼트 쉐이더를 편법으로 활용해야 했습니다. WebGPU는 컴퓨트 쉐이더를 1급 기능으로 지원하고, 스토리지 버퍼를 통한 유연한 메모리 접근, 더 나은 비동기 실행 모델을 제공합니다. 실제로 ONNX Runtime Web의 WebGPU 백엔드는 WebGL 대비 2~5배 빠른 추론 속도를 보입니다.
Q4. Cloudflare Workers의 V8 isolate 기반 실행이 Lambda보다 콜드 스타트가 빠른 이유는?
정답: 프로세스/컨테이너 초기화 없이 V8 isolate 컨텍스트만 생성
설명: AWS Lambda는 요청마다 새 컨테이너(또는 실행 환경)를 프로비저닝하고, OS 부팅, 런타임 초기화 등을 거쳐야 합니다. 이 과정이 수백 ms에서 수초까지 걸립니다. Cloudflare Workers는 동일한 V8 프로세스 내에서 메모리 격리된 isolate를 순식간에 생성합니다. isolate 생성은 ~1ms 수준으로, 기존 프로세스의 JIT-컴파일된 코드를 재사용할 수 있어 실질적으로 콜드 스타트가 거의 0에 수렴합니다.
Q5. Pyodide가 Python을 브라우저에서 실행하기 위해 WebAssembly를 사용하는 방식은?
정답: CPython 인터프리터 전체를 Emscripten으로 Wasm으로 컴파일
설명: Pyodide는 CPython 3.x 소스 코드(C로 작성)를 Emscripten 툴체인으로 WebAssembly 바이너리로 컴파일합니다. 브라우저가 이 Wasm 바이너리를 로드하면 완전한 Python 인터프리터가 브라우저 탭 안에서 실행됩니다. numpy, scipy 등 C 확장 모듈도 동일하게 Wasm으로 컴파일되어 제공됩니다. JavaScript와의 양방향 바인딩(PyProxy, JsProxy)을 통해 Python 객체를 JS에서, JS 객체를 Python에서 직접 조작할 수 있습니다. JupyterLite와 같은 완전한 브라우저 기반 Jupyter 환경이 이 기술로 구현됩니다.
결론
WebAssembly는 단순한 "빠른 JavaScript 대체재"를 넘어 범용 런타임 플랫폼으로 진화하고 있습니다. WASI Component Model의 성숙, WebGPU의 확산, 엣지 플랫폼의 Wasm 채택 가속화로 인해 2026년 현재 Wasm은 다음과 같은 영역에서 표준 기술로 자리잡고 있습니다.
- 브라우저 AI 추론 (ONNX Runtime Web + WebGPU)
- 서버리스 엣지 함수 (Cloudflare Workers, Fastly)
- IoT 및 임베디드 (WasmEdge, WAMR)
- 플러그인 시스템 (Extism, waPC)
Rust로 시작하는 Wasm 개발, wasm-pack으로의 빌드, Cloudflare Workers 배포까지 이 가이드가 여러분의 엣지 컴퓨팅 여정의 나침반이 되길 바랍니다.