웹 어셈블리 찍먹 !

2024. 5. 23. 01:37· frontend
목차
  1. 웹 어셈블리란 ?
  2. 웹 어셈블리, Javascript 처리과정 비교
  3. Rust로 웹 어셈블리를 만들어보자
  4. Rust 설치
  5. Rust로 소스코드 작성
  6. 웹 어셈블리로 컴파일
  7. 웹 어셈블리 적용
  8. 웹 어셈블리 VS Javascript
반응형

오늘은 웹 어셈블리에 대해서 글을 써보려고 합니다.

간단히 웹 어셈블리가 뭔지 알아보고 Rust를 사용해서 간단한 웹 어셈블리를 만들고 기존 Javascript와 어떤 점이 다른지 확인을 해보겠습니다.

웹 어셈블리란 ?

WebAssembly는 최신 웹 브라우저에서 실행할 수 있는 새로운 유형의 코드이며, 새로운 기능과 성능 면에서 큰 이점을 제공합니다. 직접 코드를 작성하는 것이 아니라 C, C++, RUST 등의 소스 언어를 효과적으로 컴파일하도록 고안되었습니다.
MDN 공식 문서

쉽게 말해 C, C++, Rust 등 빠른 언어를 컴파일하여 브라우저에서 실행시킬 수 있는 바이너리 형식으로 만드는 기술을 의미합니다.

웹 어셈블리, Javascript 처리과정 비교

웹 어셈블리와 Javascript의 처리 과정을 비교해 보겠습니다.

구문 해석

  • javascript : 추상 구문 트리(AST)로 변환되어 이후 바이트 코드로 변환됩니다.
  • 웹 어셈블리 : 이미 바이트 코드라서 변환이 필요 없고 decode를 하여 검증만 합니다.

컴파일 및 최적화

  • javascript : JIT(Just in time) 컴파일러를 사용하여 컴파일을 합니다.
  • 웹 어셈블리 : 컴파일러를 사용하여 많은 최적화가 진행된 상태라 최적화 과정이 적습니다.

재 최적화

  • javascript : 기존의 최적화를 버리고 다시 최적화하는 경우가 있습니다.
  • 웹 어셈블리 : 재 최적화가 발생하지 않습니다.

실행

  • javascript : 컴파일러의 최적화 방식으로 최적화할 수 있지만 컴파일러의 내부동작을 알아야하고 브라우저마다 다른 최적화 방식으로 접근해야하기 때문에 쉽지 않습니다.
  • 웹 어셈블리 : 컴파일러를 위해 최적화된만큼 빠릅니다.

가비지 컬렉션

  • javascript : 가비지 컬렉터가 자동으로 메모리 관리를 해줍니다.
  • 웹 어셈블리 : 메모리를 수동으로 관리합니다.

Rust로 웹 어셈블리를 만들어보자

저도 처음 다뤄보는 웹 어셈블리만큼 많이 낯설고 어려운 개념들인데요. 처음부터 깊게 다루기에는 무리가 있어 간단하게 만들어보면서 찍먹 해보겠습니다.
어떤 언어로 만들어볼까 고민하다가 전부터 배우고 싶었단 Rust를 사용해서 만들어보겠습니다. (참고로 Rust도 할 줄 몰라 ChatGPT의 도움을 받겠습니다 ㅎ)

Rust 설치

우선 Rust를 설치해 보겠습니다. 공식문서에 친절하게 잘나와있네요. 이 글은 Mac OS기준으로 작성되었습니다.

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

이제 Rust가 설치되었을 겁니다. 잘 설치되었는지 확인해 볼까요 ?

cargo --version

잘 설치된 모습입니다.

Rust로 소스코드 작성

행렬의 곱을 계산해 주는 함수를 구현해 보겠습니다.

행렬에 대해서 몰라도 괜찮습니다. 그냥 복잡한 계산이라고 생각하시면 편합니다 !

use wasm_bindgen::prelude::*;
use rayon::prelude::*;

#[wasm_bindgen]
pub fn matrix_multiply(a: &[f64], b: &[f64], n: usize) -> Vec<f64> {
    let mut result = vec![0.0; n * n];

    result.par_chunks_mut(n).enumerate().for_each(|(i, row)| {
        for k in 0..n {
            let a_ik = a[i * n + k];
            for j in 0..n {
                row[j] += a_ik * b[k * n + j];
            }
        }
    });

    result
}

전지전능하신 chatGPT의 도움을 받아 작성했습니다.
Rust를 잘 몰라 다시 chatGPT에게 코드 분석을 요구하니

이런 식으로 응답이 옵니다.


위 코드는 Rust와 WebAssembly를 이용하여 행렬 곱셈을 수행하는 함수로, 병렬 처리를 통해 성능을 최적화하고 있습니다. 주요 구성 요소와 코드의 작동 방식을 단계별로 설명하겠습니다.

코드 분석

1. 의존성 및 모듈 가져오기
use wasm_bindgen::prelude::*;
use rayon::prelude::*;
  • wasm_bindgen::prelude::*:
    • Rust 함수를 WebAssembly로 노출하기 위해 wasm_bindgen 매크로를 사용합니다.
  • rayon::prelude::*:
    • Rust에서 병렬 처리를 쉽게 구현할 수 있도록 도와주는 rayon 라이브러리의 프렐류드를 가져옵니다.
2. 함수 정의
#[wasm_bindgen]
pub fn matrix_multiply(a: &[f64], b: &[f64], n: usize) -> Vec<f64> {
  • #[wasm_bindgen]:
    • 이 매크로는 해당 함수를 WebAssembly로 노출하도록 합니다.
  • pub fn matrix_multiply(a: &[f64], b: &[f64], n: usize) -> Vec<f64>:
    • 함수 시그니처입니다. a와 b는 입력 행렬이고, n은 행렬의 크기입니다. 반환값은 결과 행렬입니다.
3. 결과 행렬 초기화
let mut result = vec![0.0; n * n];
  • result:
    • 결과 행렬을 저장할 벡터입니다. n * n 크기의 0.0으로 초기화된 벡터를 생성합니다.
4. 병렬 처리 루프
result.par_chunks_mut(n).enumerate().for_each(|(i, row)| {
    for k in 0..n {
        let a_ik = a[i * n + k];
        for j in 0..n {
            row[j] += a_ik * b[k * n + j];
        }
    }
});
  • result.par_chunks_mut(n):
    • rayon의 par_chunks_mut 메서드를 사용하여 결과 벡터를 병렬로 처리할 수 있도록 합니다. 각 청크는 n개의 요소로 나뉩니다.
  • .enumerate().for_each(|(i, row)| {...}):
    • 청크를 열거하고 각각에 대해 병렬 처리를 수행합니다. i는 행 인덱스이고, row는 현재 처리 중인 결과 행렬의 행입니다.
  • for k in 0..n:
    • 내부 루프는 행렬 곱셈의 열 인덱스를 순회합니다.
  • let a_ik = a[i * n + k];:
    • 입력 행렬 a의 현재 행과 열 요소를 가져옵니다.
  • for j in 0..n:
    • 결과 행렬의 열 인덱스를 순회합니다.
  • row[j] += a_ik * b[k * n + j];:
    • 행렬 곱셈의 누적 결과를 계산하여 결과 행렬의 해당 요소에 저장합니다.
5. 결과 반환
result
  • 계산된 결과 행렬을 반환합니다.

결론

이 코드는 rayon을 이용한 병렬 처리를 통해 행렬 곱셈을 수행합니다. 병렬 처리를 통해 성능을 최적화하고, Rust와 WebAssembly를 활용하여 웹 환경에서 효율적인 행렬 연산을 수행할 수 있도록 설계되었습니다. 주요 최적화는 par_chunks_mut를 사용하여 결과 벡터를 병렬로 처리하는 부분에 있습니다. 이로 인해 행렬의 각 행을 병렬로 계산하여 성능을 향상시킵니다.


이제 이 함수를 웹 어셈블리로 컴파일을 해보겠습니다.

웹 어셈블리로 컴파일

먼저 이 명령어로

 cargo install wasm-pack

Rust를 wasm으로 변환시켜주는 wasm-pack을 설치를 해줍니다.

설치를 완료하고 이 명령어를 입력하면

wasm-pack build --target web

터미널에 이런 로그가 뜨면서

pkg
├── .gitignore
├── README.md
├── package.json
├── webassembly.d.ts
├── webassembly.js
├── webassembly_bg.wasm
└── webassembly_bg.wasm.d.ts

이런 구조의 파일들이 생겨나는 모습을 볼 수 있습니다. 이제 적용시켜보겠습니다.

웹 어셈블리 적용

vite로 간단한 React 프로젝트를 만들었습니다. 그리고 위의 빌드 결과물 중에 webassembly_bg.wasm, webassembly.js, webassembly.d.ts이것들만 빼서 React 프로젝트에 넣어줬습니다.

import { useEffect } from "react";
import "./App.css";
import init, { matrix_multiply } from "./webassembly/webassembly";

const n = 2000; // 행렬 크기
const a = new Float64Array(n * n).fill(1.0); // 예제 행렬 데이터
const b = new Float64Array(n * n).fill(1.0); // 예제 행렬 데이터

function App() {
  useEffect(() => {
    init(); // WebAssembly 모듈 초기화
  }, []);

  return (
    <div>
      <h1>WebAssembly vs JavaScript Matrix Multiplication</h1>
      <button
        onClick={() => {
        const result = matrix_multiply(a, b, n);
        console.log(result)
        }}
      >
        WebAssembly Check
      </button>
    </div>
  );
}

export default App;

이런 식으로 사용을 해서 버튼을 클릭하여 로그를 확인해 보면

로그가 잘 찍히는 것을 확인할 수 있습니다.

웹 어셈블리 VS Javascript

이제 Javascript로도 같은 로직을 구현해 웹 어셈블리랑 속도 차이를 비교해 보겠습니다.

우선 js로직도 만들어봐야겠죠 ? Rust로 짠 코드와 같이 행렬을 곱해주는 함수를 만들어 보겠습니다.

export function matrixMultiply(
  a: Float64Array,
  b: Float64Array,
  n: number
): Float64Array {
  const result = new Float64Array(n * n);

  for (let i = 0; i < n; i++) {
    for (let j = 0; j < n; j++) {
      for (let k = 0; k < n; k++) {
        result[i * n + j] += a[i * n + k] * b[k * n + j];
      }
    }
  }

  return result;
}

이제 이 함수도 실행시켜서 Rust로 만든 웹 어셈블리랑 같은 결과가 나오는지 확인해 보겠습니다.

결과가 잘 나오는 모습이네요 ! 이제 이 2개의 함수의 속도를 측정해서 비교해 보겠습니다.

import { useEffect } from "react";
import "./App.css";
import { matrixMultiply } from "./matrixMultiply";
import init, { matrix_multiply } from "./webassembly/webassembly";

const n = 2000; // 행렬 크기
const a = new Float64Array(n * n).fill(1.0); // 예제 행렬 데이터
const b = new Float64Array(n * n).fill(1.0); // 예제 행렬 데이터

function App() {
  useEffect(() => {
    init(); // WebAssembly 모듈 초기화
  }, []);

  return (
    <div>
      <h1>WebAssembly vs JavaScript Matrix Multiplication</h1>
      <button
        onClick={() => {
          console.time("wasm matrix multiply");
          matrix_multiply(a, b, n);
          console.timeEnd("wasm matrix multiply");
        }}
      >
        WebAssembly Check
      </button>
      <button
        onClick={() => {
          console.time("js matrix multiply");
          matrixMultiply(a, b, n);
          console.timeEnd("js matrix multiply");
        }}
      >
        Javascript Check
      </button>
    </div>
  );
}

export default App;

이런 식으로 코드를 구성하고 console.time, console.timeEnd를 사용해서 시간을 측정해 보겠습니다.

오... 상당한 차이가 보이네요.

뭔가 Rust에 대해서 좀 더 공부를 하고 잘 사용하면 정말 강력한 기능이 생길 거 같은데요. 오늘은 찍먹 느낌이었고 좀 더 공부해서 어떻게 잘 활용할지 연구를 해봐야겠습니다...!

이 글에서 사용했던 코드들입니다 :)

  • Rust : https://github.com/hautest/rust-webassembly
  • React : https://github.com/hautest/react-webassembly

참고자료

  • https://tech.kakao.com/posts/438
  • https://tecoble.techcourse.co.kr/post/2021-11-24-web-assembly/
  • https://developer.mozilla.org/ko/docs/WebAssembly/Concepts#%EC%9E%90%EC%8B%A0%EC%9D%98_%EC%96%B4%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98%EC%97%90%EC%84%9C_webassembly%EB%A5%BC_%EC%96%B4%EB%96%BB%EA%B2%8C_%EC%8B%9C%EC%9E%91%ED%95%B4%EC%95%BC%ED%95%98%EB%82%98%EC%9A%94
반응형

'frontend' 카테고리의 다른 글

브라우저의 동작 원리  (0) 2024.06.11
(React 19 + Next js 15 + Panda Css) 1. 프로젝트 세팅  (1) 2024.06.11
싱글스레드 언어인 JS에서는 Promise가 어떻게 동작할까 ?  (0) 2024.05.15
No.1 Css 라이브러리 "Panda Css"  (0) 2024.05.06
🚀 "우당탕탕 도서관" 프론트엔드 개발자 글쓰기 커뮤니티 2기 모집 안내 🚀  (0) 2024.05.04
  1. 웹 어셈블리란 ?
  2. 웹 어셈블리, Javascript 처리과정 비교
  3. Rust로 웹 어셈블리를 만들어보자
  4. Rust 설치
  5. Rust로 소스코드 작성
  6. 웹 어셈블리로 컴파일
  7. 웹 어셈블리 적용
  8. 웹 어셈블리 VS Javascript
'frontend' 카테고리의 다른 글
  • 브라우저의 동작 원리
  • (React 19 + Next js 15 + Panda Css) 1. 프로젝트 세팅
  • 싱글스레드 언어인 JS에서는 Promise가 어떻게 동작할까 ?
  • No.1 Css 라이브러리 "Panda Css"
tmdgns
tmdgns
개발, 주식, 게임을 좋아합니다.
반응형
tmdgns
부자가 되고 싶은 개발자
tmdgns
전체
오늘
어제
  • 분류 전체보기 (23)
    • frontend (16)
    • 수익형 웹 (toWeb) (1)
    • Typehero Challenge (2)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • Rust
  • 브라우저
  • 웹어셈블리
  • 프론트엔드
  • 브라우저 동작원리

최근 댓글

최근 글

hELLO · Designed By 정상우.v4.2.2
tmdgns
웹 어셈블리 찍먹 !
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.