본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout

내비게이션

  • Tech
  • Projects
  • Books
  • About
  • Tags

카테고리

  • AI / ML
  • 웹 개발
  • 프로그래밍
  • 개발 도구

연결

  • GitHub
  • Email
  • RSS
© 2026 Kreath Archive. All rights reserved.Built with Next.js + MDX
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 7장: 브라우저 고성능 앱 — Wasm의 원래 영역
2026년 4월 3일·프로그래밍·

7장: 브라우저 고성능 앱 — Wasm의 원래 영역

JavaScript와 Wasm의 상호 호출, Web API 연동, 이미지/비디오 처리, AI 추론, 게임 엔진 등 브라우저에서 WebAssembly로 고성능 애플리케이션을 구축하는 방법을 다룹니다.

14분1,074자9개 섹션
webassemblyrust
공유
webassembly7 / 10
12345678910
이전6장: Go, Python, 기타 언어에서 Wasm다음8장: 서버사이드 Wasm — Spin, Fermyon, SpinKube

학습 목표

  • JavaScript와 Wasm 간의 상호 호출 패턴을 이해합니다
  • wasm-bindgen과 web-sys를 사용하여 Web API에 접근합니다
  • 이미지 처리, AI 추론 등 실전 사용 사례를 구현합니다
  • 브라우저 Wasm의 번들 크기 최적화 전략을 수립합니다

JavaScript와 Wasm의 관계

WebAssembly가 JavaScript를 대체하는 기술이라는 오해가 있지만, 실제로는 보완 관계에 가깝습니다. JavaScript는 DOM 조작, 이벤트 처리, UI 로직에서 여전히 최적이며, Wasm은 연산 집약적인 작업에서 진가를 발휘합니다.

기본 상호 호출

브라우저에서 Wasm 모듈을 로드하고 함수를 호출하는 기본 패턴은 다음과 같습니다.

basic-interop.js
javascript
// Wasm 모듈 로드 (스트리밍 컴파일)
const { instance } = await WebAssembly.instantiateStreaming(
  fetch('processor.wasm'),
  {
    env: {
      // Wasm에서 호출할 수 있는 JavaScript 함수
      consoleLog: (ptr, len) => {
        const bytes = new Uint8Array(instance.exports.memory.buffer, ptr, len);
        const text = new TextDecoder().decode(bytes);
        console.log(text);
      }
    }
  }
);
 
// Wasm 함수 호출
const result = instance.exports.process(42);

이 저수준 API는 정수와 메모리 포인터만 주고받을 수 있습니다. 문자열이나 객체를 전달하려면 직접 메모리를 관리해야 하므로 번거롭습니다.

wasm-bindgen — 고수준 바인딩

wasm-bindgen은 Rust와 JavaScript 사이의 고수준 바인딩을 자동 생성하는 도구입니다. 문자열, 객체, 클로저 등을 투명하게 주고받을 수 있습니다.

src/lib.rs
rust
use wasm_bindgen::prelude::*;
 
// JavaScript 함수를 Rust에서 호출
#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);
    
    #[wasm_bindgen(js_namespace = performance)]
    fn now() -> f64;
}
 
// Rust 함수를 JavaScript에서 호출
#[wasm_bindgen]
pub fn parse_markdown(input: &str) -> String {
    let start = now();
    
    // Markdown 파싱 (예시)
    let result = input
        .lines()
        .map(|line| {
            if line.starts_with("# ") {
                format!("<h1>{}</h1>", &line[2..])
            } else if line.starts_with("## ") {
                format!("<h2>{}</h2>", &line[3..])
            } else if line.is_empty() {
                String::new()
            } else {
                format!("<p>{}</p>", line)
            }
        })
        .collect::<Vec<_>>()
        .join("\n");
    
    let elapsed = now() - start;
    log(&format!("Parsed in {:.2}ms", elapsed));
    
    result
}
app.js
javascript
import init, { parse_markdown } from './pkg/markdown_parser.js';
 
async function main() {
    // Wasm 초기화
    await init();
    
    // Rust 함수를 마치 JavaScript 함수처럼 호출
    const html = parse_markdown("# Hello\n\nThis is **WebAssembly**.");
    document.getElementById('content').innerHTML = html;
}
 
main();

web-sys — Web API 접근

web-sys 크레이트는 모든 Web API에 대한 Rust 바인딩을 제공합니다. DOM 조작, Canvas, Fetch API, WebSocket 등을 Rust에서 직접 사용할 수 있습니다.

canvas-example.rs
rust
use wasm_bindgen::prelude::*;
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
 
#[wasm_bindgen]
pub struct Renderer {
    ctx: CanvasRenderingContext2d,
    width: u32,
    height: u32,
}
 
#[wasm_bindgen]
impl Renderer {
    #[wasm_bindgen(constructor)]
    pub fn new(canvas_id: &str) -> Result<Renderer, JsValue> {
        let document = web_sys::window()
            .unwrap()
            .document()
            .unwrap();
        
        let canvas = document
            .get_element_by_id(canvas_id)
            .unwrap()
            .dyn_into::<HtmlCanvasElement>()?;
        
        let ctx = canvas
            .get_context("2d")?
            .unwrap()
            .dyn_into::<CanvasRenderingContext2d>()?;
        
        let width = canvas.width();
        let height = canvas.height();
        
        Ok(Renderer { ctx, width, height })
    }
    
    pub fn draw_mandelbrot(&self, max_iter: u32) {
        let image_data = self.ctx
            .get_image_data(0.0, 0.0, self.width as f64, self.height as f64)
            .unwrap();
        
        let mut data = image_data.data().0;
        
        for y in 0..self.height {
            for x in 0..self.width {
                // 만델브로 집합 계산
                let cx = (x as f64 / self.width as f64) * 3.5 - 2.5;
                let cy = (y as f64 / self.height as f64) * 2.0 - 1.0;
                
                let mut zx = 0.0_f64;
                let mut zy = 0.0_f64;
                let mut iter = 0u32;
                
                while zx * zx + zy * zy < 4.0 && iter < max_iter {
                    let tmp = zx * zx - zy * zy + cx;
                    zy = 2.0 * zx * zy + cy;
                    zx = tmp;
                    iter += 1;
                }
                
                let idx = ((y * self.width + x) * 4) as usize;
                if iter == max_iter {
                    data[idx] = 0;
                    data[idx + 1] = 0;
                    data[idx + 2] = 0;
                } else {
                    let t = iter as f64 / max_iter as f64;
                    data[idx] = (9.0 * (1.0 - t) * t * t * t * 255.0) as u8;
                    data[idx + 1] = (15.0 * (1.0 - t) * (1.0 - t) * t * t * 255.0) as u8;
                    data[idx + 2] = (8.5 * (1.0 - t) * (1.0 - t) * (1.0 - t) * t * 255.0) as u8;
                }
                data[idx + 3] = 255; // alpha
            }
        }
        
        let clamped = wasm_bindgen::Clamped(data);
        let new_image = web_sys::ImageData::new_with_u8_clamped_array_and_sh(
            clamped, self.width, self.height
        ).unwrap();
        
        self.ctx.put_image_data(&new_image, 0.0, 0.0).unwrap();
    }
}
Tip

Canvas 기반 렌더링에서 Wasm은 JavaScript 대비 2~5배 빠른 성능을 보입니다. 특히 픽셀 단위의 반복 연산(프랙탈, 이미지 필터, 시뮬레이션)에서 차이가 두드러집니다. 다만 DOM 조작은 여전히 JavaScript 쪽에서 하는 것이 효율적입니다.

실전 사용 사례

이미지 처리

브라우저에서의 실시간 이미지 처리는 Wasm의 대표적인 강점 영역입니다.

image-processing.rs
rust
use wasm_bindgen::prelude::*;
 
#[wasm_bindgen]
pub fn apply_sepia(pixels: &mut [u8]) {
    // RGBA 픽셀 배열을 직접 처리
    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;
        
        chunk[0] = ((0.393 * r + 0.769 * g + 0.189 * b).min(255.0)) as u8;
        chunk[1] = ((0.349 * r + 0.686 * g + 0.168 * b).min(255.0)) as u8;
        chunk[2] = ((0.272 * r + 0.534 * g + 0.131 * b).min(255.0)) as u8;
        // alpha 유지
    }
}
 
#[wasm_bindgen]
pub fn resize_image(
    src: &[u8],
    src_width: u32,
    src_height: u32,
    dst_width: u32,
    dst_height: u32,
) -> Vec<u8> {
    let mut dst = vec![0u8; (dst_width * dst_height * 4) as usize];
    
    let x_ratio = src_width as f64 / dst_width as f64;
    let y_ratio = src_height as f64 / dst_height as f64;
    
    for y in 0..dst_height {
        for x in 0..dst_width {
            let src_x = (x as f64 * x_ratio) as u32;
            let src_y = (y as f64 * y_ratio) as u32;
            
            let src_idx = ((src_y * src_width + src_x) * 4) as usize;
            let dst_idx = ((y * dst_width + x) * 4) as usize;
            
            dst[dst_idx..dst_idx + 4].copy_from_slice(&src[src_idx..src_idx + 4]);
        }
    }
    
    dst
}

AI 추론 — ONNX Runtime Web

ONNX Runtime Web은 ONNX 모델을 브라우저에서 실행하는 라이브러리로, 내부적으로 WebAssembly를 사용합니다.

ai-inference.js
javascript
import * as ort from 'onnxruntime-web';
 
async function classifyImage(imageData) {
    // Wasm 백엔드 설정
    ort.env.wasm.wasmPaths = '/wasm/';
    
    // 모델 로드
    const session = await ort.InferenceSession.create(
        '/models/mobilenet-v2.onnx',
        { executionProviders: ['wasm'] }
    );
    
    // 입력 텐서 생성
    const tensor = new ort.Tensor('float32', imageData, [1, 3, 224, 224]);
    
    // 추론 실행
    const results = await session.run({ input: tensor });
    const output = results.output.data;
    
    // 결과 해석
    const topK = Array.from(output)
        .map((prob, idx) => ({ prob, idx }))
        .sort((a, b) => b.prob - a.prob)
        .slice(0, 5);
    
    return topK;
}
Info

WebNN(Web Neural Network API)은 브라우저에서 하드웨어 가속 AI 추론을 가능하게 하는 새로운 표준입니다. 2026년 현재 Chrome에서 실험적으로 지원되며, ONNX Runtime Web과 함께 사용하면 GPU 가속을 활용할 수 있습니다. WebNN이 불가능한 환경에서는 Wasm 백엔드로 자동 폴백됩니다.

게임 엔진

Unity와 Unreal Engine은 Wasm을 통한 웹 빌드를 지원합니다. 독립적인 웹 게임 엔진도 Wasm을 핵심 기술로 활용합니다.

game-loop.rs
rust
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
 
#[wasm_bindgen]
pub struct Game {
    player_x: f64,
    player_y: f64,
    velocity_x: f64,
    velocity_y: f64,
}
 
#[wasm_bindgen]
impl Game {
    #[wasm_bindgen(constructor)]
    pub fn new() -> Self {
        Self {
            player_x: 400.0,
            player_y: 300.0,
            velocity_x: 0.0,
            velocity_y: 0.0,
        }
    }
    
    // 매 프레임 호출
    pub fn update(&mut self, delta_time: f64) {
        self.player_x += self.velocity_x * delta_time;
        self.player_y += self.velocity_y * delta_time;
        
        // 경계 충돌 처리
        if self.player_x < 0.0 || self.player_x > 800.0 {
            self.velocity_x = -self.velocity_x;
        }
        if self.player_y < 0.0 || self.player_y > 600.0 {
            self.velocity_y = -self.velocity_y;
        }
    }
    
    pub fn set_velocity(&mut self, vx: f64, vy: f64) {
        self.velocity_x = vx;
        self.velocity_y = vy;
    }
    
    pub fn player_x(&self) -> f64 { self.player_x }
    pub fn player_y(&self) -> f64 { self.player_y }
}
game-loop.js
javascript
import init, { Game } from './pkg/game_engine.js';
 
async function main() {
    await init();
    const game = new Game();
    const canvas = document.getElementById('game-canvas');
    const ctx = canvas.getContext('2d');
    
    let lastTime = performance.now();
    
    function gameLoop(currentTime) {
        const deltaTime = (currentTime - lastTime) / 1000;
        lastTime = currentTime;
        
        // Wasm에서 게임 로직 업데이트
        game.update(deltaTime);
        
        // JavaScript에서 렌더링
        ctx.clearRect(0, 0, 800, 600);
        ctx.fillStyle = '#3498db';
        ctx.beginPath();
        ctx.arc(game.player_x(), game.player_y(), 20, 0, Math.PI * 2);
        ctx.fill();
        
        requestAnimationFrame(gameLoop);
    }
    
    requestAnimationFrame(gameLoop);
}
 
main();

번들 크기 최적화

브라우저에서 Wasm을 배포할 때 바이너리 크기는 사용자 경험에 직접적인 영향을 미칩니다.

코드 분할과 지연 로딩

lazy-loading.js
javascript
// 기본 기능은 JavaScript로
function quickPreview(text) {
    return text.substring(0, 200) + '...';
}
 
// 무거운 처리는 Wasm을 지연 로딩
let wasmModule = null;
 
async function fullProcess(text) {
    if (!wasmModule) {
        // 사용자가 실제로 필요할 때만 로드
        wasmModule = await import('./pkg/heavy_processor.js');
        await wasmModule.default();
    }
    return wasmModule.process(text);
}

스트리밍 컴파일

streaming.js
javascript
// compileStreaming은 다운로드와 컴파일을 병렬로 수행
// 일반 compile 대비 로딩 시간이 크게 단축됨
const module = await WebAssembly.compileStreaming(
    fetch('processor.wasm')
);
 
// 모듈을 캐시하여 재사용
const cache = await caches.open('wasm-cache-v1');
await cache.put('processor', new Response(await fetch('processor.wasm')));
Warning

WebAssembly.compileStreaming을 사용하려면 서버가 .wasm 파일에 application/wasm MIME 타입을 설정해야 합니다. 잘못된 MIME 타입은 스트리밍 컴파일 실패의 가장 흔한 원인입니다.

성능 비교 지침

Wasm 도입을 결정할 때는 실제 벤치마크가 중요합니다.

작업 유형JS vs Wasm권장
DOM 조작JS가 빠름JavaScript
문자열 처리 (소량)비슷함JavaScript
숫자 연산 (대량)Wasm 2~5배 빠름WebAssembly
이미지 픽셀 처리Wasm 3~10배 빠름WebAssembly
암호화/해싱Wasm 5~20배 빠름WebAssembly
JSON 파싱비슷함 ~ JS가 빠름JavaScript

정리

이번 장에서는 브라우저에서의 WebAssembly 활용을 다양한 각도에서 살펴보았습니다.

  • wasm-bindgen은 Rust와 JavaScript 간의 고수준 바인딩을 자동화합니다
  • web-sys를 통해 Canvas, Fetch 등 모든 Web API에 Rust에서 직접 접근할 수 있습니다
  • 이미지 처리, AI 추론, 게임 엔진 등 연산 집약적 작업에서 Wasm이 진가를 발휘합니다
  • 지연 로딩과 스트리밍 컴파일로 초기 로딩 성능을 최적화할 수 있습니다

다음 장 미리보기

8장에서는 브라우저를 벗어나 서버사이드 Wasm을 다룹니다. Spin 프레임워크의 아키텍처, Fermyon Cloud와 Akamai의 통합, SpinKube를 활용한 Kubernetes 배포, 그리고 Docker와 Wasm의 비교를 살펴봅니다.

이 글이 도움이 되셨나요?

관련 주제 더 보기

#webassembly#rust

관련 글

프로그래밍

8장: 서버사이드 Wasm — Spin, Fermyon, SpinKube

Spin 프레임워크의 아키텍처, Fermyon Cloud와 Akamai 통합, SpinKube를 활용한 Kubernetes 배포, Docker와 Wasm의 비교를 통해 서버사이드 WebAssembly의 현재를 분석합니다.

2026년 4월 5일·16분
프로그래밍

9장: 엣지 컴퓨팅과 Wasm 배포

Cloudflare Workers, Fastly Compute, Akamai Edge의 Wasm 실행 환경을 비교하고, 엣지에서의 AI 추론, 콜드 스타트 성능 분석, 엣지 배포 전략을 다룹니다.

2026년 4월 5일·16분
프로그래밍

10장: 실전 프로젝트 — WebAssembly 애플리케이션 구축

Rust+Spin 서버리스 API 구축, 브라우저 Wasm 모듈 통합, 엣지 배포, 성능 벤치마킹, Wasm 도입 의사결정 가이드, 그리고 WebAssembly의 미래 전망을 다룹니다.

2026년 4월 5일·19분
이전 글6장: Go, Python, 기타 언어에서 Wasm
다음 글8장: 서버사이드 Wasm — Spin, Fermyon, SpinKube

댓글

목차

약 14분 남음
  • 학습 목표
  • JavaScript와 Wasm의 관계
    • 기본 상호 호출
  • wasm-bindgen — 고수준 바인딩
  • web-sys — Web API 접근
  • 실전 사용 사례
    • 이미지 처리
    • AI 추론 — ONNX Runtime Web
    • 게임 엔진
  • 번들 크기 최적화
    • 코드 분할과 지연 로딩
    • 스트리밍 컴파일
  • 성능 비교 지침
  • 정리
  • 다음 장 미리보기