웹어셈블리와 함께 Rust의 성능을 웹으로 가져오기
Takashi Yamamoto
Infrastructure Engineer · Leapcell

소개
웹 플랫폼은 정적 문서에서 풍부하고 상호작용적인 애플리케이션으로 극적으로 발전했습니다. 사용자들은 응답성과 연산 능력에 대한 기대치가 높아짐에 따라, 다재다능한 JavaScript는 때때로 CPU 집약적인 작업, 복잡한 시뮬레이션 또는 고성능 그래픽의 요구 사항을 충족하는 데 어려움을 겪습니다. 이때 Rust와 WebAssembly(WASM)의 강력한 조합이 등장합니다. 속도, 메모리 안전성, 동시성으로 유명한 Rust는 브라우저에서 가능한 것의 경계를 넓히는 데 이상적인 후보가 됩니다. Rust 코드를 WebAssembly로 컴파일함으로써 네이티브에 가까운 성능을 발휘하고 Rust의 강력한 타입 시스템과 두려움 없는 동시성을 웹 애플리케이션 내에서 직접 활용하여 까다로운 웹 기반 경험을 위한 새로운 지평을 열 수 있습니다.
Rust와 WebAssembly 시너지 이해하기
실제 적용으로 들어가기 전에 몇 가지 핵심 개념을 명확히 합시다.
Rust: Rust는 안전성, 성능, 동시성에 중점을 둔 시스템 프로그래밍 언어입니다. 소유권 시스템을 통해 가비지 컬렉션 없이 메모리 안전성을 달성하며, 이는 컴파일 타임 메모리 보장을 보장합니다. 성능에 중점을 두어 게임 엔진, 운영 체제, 그리고 점점 더 웹 애플리케이션 구성 요소와 같이 속도가 중요한 작업에 적합합니다.
WebAssembly (WASM): WebAssembly는 스택 기반 가상 머신을 위한 바이너리 명령어 형식입니다. C, C++, Rust와 같은 고수준 언어를 위한 이식 가능한 컴파일 대상으로서, 클라이언트 및 서버 애플리케이션을 위한 웹 배포를 가능하게 합니다. WASM은 빠르고 안전하며 효율적이어서 웹 애플리케이션에 거의 네이티브에 가까운 성능을 제공합니다. 호스트 시스템과 격리된 샌드박스 환경에서 실행되어 보안을 보장합니다.
원리: 아이디어는 간단합니다. 성능에 중요한 코드를 Rust로 작성하고, WebAssembly로 컴파일한 다음, 브라우저의 JavaScript 환경 내에서 해당 WASM 모듈을 로드하고 실행하는 것입니다. JavaScript는 DOM과의 상호 작용을 처리하고 WASM 모듈의 실행을 조율하는 "접착 코드" 역할을 합니다.
작동 방식: 단계별 개요
WebAssembly를 통해 Rust를 브라우저로 가져오는 과정은 일반적으로 다음 단계를 포함합니다.
- Rust 코드 작성: 애플리케이션 로직, 알고리즘 또는 계산 집약적인 부분을 Rust로 개발합니다.
- WASM으로 컴파일: Rust의
wasm-pack
도구(또는wasm32-unknown-unknown
타겟과 함께 직접cargo
)를 사용하여 Rust 코드를.wasm
바이너리 및 JavaScript "접착" 파일로 컴파일합니다. 접착 파일은 컴파일된 WASM 모듈을 로드하고 상호 작용하는 데 필요한 JavaScript 바인딩을 제공합니다. - 브라우저에서 로드: 웹 애플리케이션의 JavaScript 코드에서 생성된 접착 코드를 사용하여
.wasm
모듈을 로드합니다. 이렇게 하면 Rust 함수를 JavaScript 함수로 사용할 수 있습니다. - JavaScript와 상호 작용: JavaScript 코드에서 내보낸 Rust 함수를 호출하여 데이터를 주고받습니다.
실례: 만델브로트 집합 생성기
이것을 간단한 예제, 즉 만델브로트 집합 계산을 통해 설명해 보겠습니다. 이는 Rust의 성능을 크게 향상시키는 CPU 집약적인 작업입니다.
먼저 새 Rust 라이브러리 프로젝트를 만듭니다.
cargo new --lib mandelbrot-wasm cd mandelbrot-wasm
Rust와 JavaScript 간의 상호 작용을 용이하게 하기 위해 Cargo.toml
에 wasm-bindgen
을 추가합니다.
[lib] crate-type = ["cdylib"] [dependencies] wasm-bindgen = "0.2"
이제 src/lib.rs
에서 기본 만델브로트 계산 함수를 구현해 보겠습니다. 이 함수는 이미지 크기와 최대 반복 횟수를 인수로 받아 색상 배열(Vec<u8>
)을 반환합니다.
use wasm_bindgen::prelude::*; #[wasm_bindgen] pub fn generate_mandelbrot(width: u32, height: u32, max_iterations: u32) -> Vec<u8> { let mut pixels = vec![0; (width * height * 4) as usize]; // RGBA let zoom_factor = 2.0 / width as f64; for y in 0..height { for x in 0..width { let cr = (x as f64 * zoom_factor) - 1.5; let ci = (y as f64 * zoom_factor) - 1.0; let mut zr = 0.0; let mut zi = 0.0; let mut iterations = 0; while zr * zr + zi * zi < 4.0 && iterations < max_iterations { let temp = zr * zr - zi * zi + cr; zi = 2.0 * zr * zi + ci; zr = temp; iterations += 1; } let index = ((y * width + x) * 4) as usize; if iterations == max_iterations { pixels[index] = 0; // R pixels[index + 1] = 0; // G pixels[index + 2] = 0; // B pixels[index + 3] = 255; // A } else { let color_val = (iterations as f64 / max_iterations as f64 * 255.0) as u8; pixels[index] = color_val; pixels[index + 1] = color_val / 2; pixels[index + 2] = 255 - color_val; pixels[index + 3] = 255; } } } pixels }
이제 wasm-pack
을 사용하여 이 Rust 코드를 WebAssembly로 컴파일합니다.
cargo install wasm-pack wasm-pack build --target web
이 명령은 pkg
디렉토리를 생성하며, 여기에는 mandelbrot_wasm_bg.wasm
(WASM 모듈)과 mandelbrot_wasm.js
(JavaScript 접착 코드 및 기타 메타데이터)가 포함됩니다.
다음으로 이 WASM 모듈을 로드하고 사용하기 위한 index.html
및 index.js
를 만듭니다.
index.html
:
<!DOCTYPE html> <html> <head> <title>Rust Mandelbrot WASM</title> <style> body { font-family: sans-serif; text-align: center; } canvas { border: 1px solid black; margin-top: 20px; } </style> </head> <body> <h1>Mandelbrot Set in Rust & WebAssembly</h1> <canvas id="mandelbrotCanvas" width="800" height="600"></canvas> <script type="module" src="./index.js"></script> </body> </html>
index.js
:
import { generate_mandelbrot } from './pkg/mandelbrot_wasm.js'; async function run() { const canvas = document.getElementById('mandelbrotCanvas'); const ctx = canvas.getContext('2d'); const width = canvas.width; const height = canvas.height; const maxIterations = 500; console.time('Mandelbrot generation in Rust WASM'); const pixelData = generate_mandelbrot(width, height, maxIterations); console.timeEnd('Mandelbrot generation in Rust WASM'); const imageData = new ImageData( new Uint8ClampedArray(pixelData), width, height ); ctx.putImageData(imageData, 0, 0); } run();
이것을 실행하려면 간단한 HTTP 서버(예: npx http-server
또는 Python의 http.server
)가 필요합니다. 브라우저에서 index.html
을 엽니다. 만델브로트 집합이 렌더링되는 것을 볼 수 있으며, 콘솔에는 Rust WASM 함수에 소요된 시간이 표시되어 효율성을 입증합니다.
애플리케이션 시나리오
Rust + WASM 조합은 수많은 웹 애플리케이션 시나리오에 매우 강력합니다.
- 고성능 컴퓨팅: 과학 시뮬레이션, 데이터 처리, 암호화, 비디오/오디오 코딩/디코딩.
- 게임 개발: 기존 C/C++ 게임 엔진 포팅 또는 새롭고 고성능의 웹 기반 게임 개발.
- 이미지 및 동영상 편집: 브라우저 내에서 직접 복잡한 필터, 실시간 효과.
- 증강 현실/가상 현실: 서버 측 처리만으로 의존하기보다는 AR/VR 경험을 위한 집중적인 계산 수행.
- 코덱 및 파서: 특정 데이터 형식에 대한 사용자 정의의 효율적인 코덱 또는 파서 구현.
- 라이브러리 및 프레임워크: 모든 JavaScript 프레임워크에서 소비할 수 있는 WASM 모듈로 성능에 중요한 구성 요소 제공.
결론
Rust와 WebAssembly는 개발자가 JavaScript의 성능 한계를 극복하고 브라우저에서 진정으로 네이티브에 가까운 경험을 제공할 수 있도록 지원하는 강력한 쌍을 형성합니다. Rust의 안전성과 속도를 활용하여 이식 가능하고 효율적인 WASM 모듈로 컴파일함으로써, 더 빠르고 안정적이며 점점 더 복잡한 연산 작업을 처리할 수 있는 웹 애플리케이션을 구축할 수 있습니다. 이 시너지는 웹 개발에 있어 중요한 도약이며, 브라우저 환경 내에서 직접 달성 가능한 것의 경계를 넓힙니다.