hmk run dev

wasm과 webworker를 도입해보다...! 본문

Front-End

wasm과 webworker를 도입해보다...!

hmk run dev 2023. 4. 16. 00:03


마이그레이션 의사 결정 결과

  • aws lambda를 이용해 backEnd에서 색상을 추출하는 방식을 FE에서 모두 처리
    • 서버비용 절감
    • Rust를 사용한 WebAssembly 도입 및 사내 시스템화

기존 방식

  • filedId & sliceId를 넘겨 backend에서 컬러를 추출해 보여주는 방식(sliceApi)

적용 방식

  • Rust & wasm-pack을 이용해 wasm 모듈 생성
  • wasm 모듈이 적용된 webworker를 생성
  • 해당 webworker에 메세지를 주고 받으며 데이터 교환

도입이유

이미지에서 색상 데이터를 추출하여 추천 색상을 찾는 것은 기존에도 가능했지만, 이 과정에서 많은 계산 작업이 필요하고, 이는 웹 페이지의 성능에 영향을 미칠 수 있습니다.

WebWorker는 백그라운드 스레드에서 작업을 수행하므로 웹 페이지의 성능을 저하시키지 않으면서 계산 작업을 수행할 수 있습니다.

또한, WebAssembly는 웹 브라우저에서 실행 가능한 빠른 이진 코드로 변환됩니다.

따라서, JavaScript보다 훨씬 빠른 계산 속도를 제공할 수 있습니다.

이러한 이유로 WebWorker와 WebAssembly를 사용하여 이미지에서 색상 데이터를 추출하는 것이 유용한 기술로 판단되어 이를 도입하게 되었습니다.

도입효과

WebWorker와 WebAssembly를 도입함으로써, 이미지에서 색상 데이터를 추출하는 과정에서 발생하는 계산 작업이 웹 페이지의 성능에 미치는 영향을 줄일 수 있었습니다. 이로 인해 웹 페이지가 더 빠르고 부드러운 사용자 경험을 제공할 수 있게 되었습니다.

또한, WebAssembly를 사용하여 계산 속도를 향상시켰기 때문에, 추천 색상을 찾는 시간이 단축되어, 사용자에게 보다 빠르게 결과를 제공할 수 있게 되었습니다. 마지막으로, WebWorker와 WebAssembly를 사용한 이미지 처리는 브라우저에서 수행되므로, 서버에서 이미지 처리를 수행하는 것보다 더 빠르고 경제적이라고 생각합니다.

개선효과

  • wasm - 40 ~ 150 ms
  • api = 400 ~ 571 ms



웹어셈블리란?

WebAssembly은 브라우저에서 실행되는 이진 형식의 프로그래밍 언어로, C, C++, Rust, Java 등의 언어를 웹에서 실행할 수 있도록 지원합니다.

 

웹어셈블리, 줄여서 Wasm은 다양한 프로그래밍 언어와 다양한 실행 환경 사이의 중간 계층입니다. 30개 이상의 다른 언어로 작성된 코드를 가져와서 .wasm 파일로 컴파일한 다음 브라우저, 서버 심지어 자동차에서도 해당 파일을 실행할 수 있습니다.

“웹어셈블리”라는 이름은 잘못 지어졌습니다.

초기에 웹상에서 코드를 빠르게 실행하기 위한 목적으로 설계되었지만, 현재는 브라우저 외에 다양한 환경에서 실행될 수 있습니다. 그리고 웹어셈블리는 어셈블리라고 볼 수 없습니다. 그보다 높은 수준의 바이트코드입니다.


WebAssembly을 사용하는 이유는 다음과 같습니다.

  1. 빠른 실행 속도: WebAssembly은 빠른 실행 속도를 제공합니다. 이는 JavaScript와 달리 JIT 컴파일을 필요로하지 않기 때문입니다.
  2. 크로스 플랫폼 지원: WebAssembly은 모든 주요 브라우저에서 실행됩니다. 이는 플랫폼과 브라우저 간의 호환성 문제를 해결할 수 있습니다.
  3. 안전한 실행 환경: WebAssembly은 메모리와 프로세스를 격리하고, 안전하고 제한된 환경에서 실행됩니다. 이는 보안상의 이유로 중요합니다.
  4. 대규모 응용 프로그램 지원: WebAssembly은 대규모 응용 프로그램을 실행할 수 있습니다. 이는 JavaScript로는 처리하기 어려운 대용량 데이터를 다루는 응용 프로그램에서 유용합니다.
  5. 쉬운 연동성: WebAssembly은 JavaScript와 쉽게 상호 작용할 수 있습니다. 이는 기존의 JavaScript 코드와 함께 사용할 수 있기 때문에 이전의 프로젝트에서 작성한 코드를 재사용할 수 있습니다.

따라서 WebAssembly은 브라우저에서 더 나은 성능을 제공하고, 크로스 플랫폼 지원을 제공하며, 안전한 실행 환경을 제공합니다.

이를 통해 더 나은 웹 응용 프로그램을 개발할 수 있습니다.


Rust를 선택한 이유

Rust는 웹 어셈블리 (WebAssembly)를 개발하기에 적합한 언어 중 하나입니다.

이는 다음과 같은 이유로 인해 Rust를 사용해 WebAssembly를 구현하는 것에 대한 장점이 있습니다.

  • 안전성: Rust는 컴파일 시간 안전성을 보장하는 메모리 안전성 기능을 제공합니다. 이는 웹 어셈블리에서 보안상의 위협을 줄이고, 신뢰성 높은 코드를 작성할 수 있도록 합니다.
    • 안정성에 관한 자료(공식문서 발췌)
      1. Rust 코드의 96%가 출시 전에 컴파일러에서 컴파일되지만, 런타임 오류가 없습니다.
      2. Rust 코드에 대한 보안 취약점 보고서는 2016년 이후 급격히 감소하고 있습니다.
      3. Rust 컴파일러는 취약한 포인터 사용, 버퍼 오버플로 및 다른 보안 문제를 방지하기 위한 다양한 보안 검사를 수행합니다.
      4. Rust는 모든 변수가 선언되어 초기화되어 있어, 변수 사용 전 초기화하지 않는 버그를 방지할 수 있습니다.
      5. Rust는 개발자가 메모리 할당 및 해제를 직접 관리하지 않아도 되도록 안전한 메모리 관리 기능을 제공합니다. 이는 일반적인 메모리 오류를 방지할 수 있습니다.
  • 성능: Rust는 C++과 유사한 성능을 제공하며, 높은 수준의 병렬성을 지원합니다. 이는 웹 어셈블리에서 빠른 실행 속도를 보장하고, 대용량 데이터를 처리하는 데 유용합니다.
  • 성능에 관한 자료(공식문서 발췌)
    1. Rust는 C, C++, Java 등과 같은 언어와 비교하여 매우 낮은 오버헤드로 동작합니다.
    2. Rust는 메모리 누수를 방지하기 위한 안전한 메모리 관리 기능을 제공하면서, 고성능의 실행 시간 성능을 보장합니다.
    3. Rust의 속도는 일반적으로 다른 언어보다 빠르거나 비슷합니다. Rust 코드는 특히 높은 수준의 병렬성을 갖추면서도 빠릅니다.
    4. Rust는 다중 CPU 코어에서 높은 확장성을 제공합니다. Rust는 코드를 안전하게 병렬화하고, CPU 코어의 수에 따라 선형적으로 성능을 높일 수 있습니다.
    5. Rust는 다른 언어에 비해 메모리 사용량이 적습니다. Rust는 스택 기반 메모리 할당을 사용하고, 힙 할당을 최소화함으로써 메모리 사용량을 줄일 수 있습니다.
  • 이식성: Rust는 많은 플랫폼을 지원합니다. 이는 웹 어셈블리에서 크로스 플랫폼 지원을 제공하고, 다른 환경으로 이식성을 높일 수 있도록 합니다.
  • 이식성에 관한 자료(공식문서 발췌)
    1. Rust는 크로스 컴파일링을 지원합니다. 이를 통해 Windows, Mac, Linux 등 다양한 플랫폼에서 실행 가능한 이식성 높은 바이너리를 만들 수 있습니다.
    2. Rust는 다양한 아키텍처를 지원합니다. 이는 ARM, x86, x86-64 등 다양한 아키텍처에서 Rust를 실행할 수 있도록 지원하는 것을 의미합니다.
    3. Rust는 다양한 운영체제를 지원합니다. 이는 Windows, macOS, Linux, FreeBSD, Android 등의 운영체제에서 Rust를 실행할 수 있도록 지원하는 것을 의미합니다.
    4. Rust는 다양한 개발 환경을 지원합니다. 이는 Visual Studio Code, Vim, Emacs 등 다양한 개발 도구에서 Rust를 사용할 수 있도록 지원하는 것을 의미합니다.
    5. Rust는 C/C++와의 상호 운용성을 제공합니다. Rust는 C/C++ 라이브러리와 쉽게 상호 작용할 수 있도록 지원합니다.
  • 코드 유지 관리: Rust는 모듈화, 패키징, 테스트 등의 기능을 제공합니다. 이는 웹 어셈블리에서 코드 유지 보수를 용이하게 하고, 팀 작업을 지원합니다.
  • 코드유지관리에 관한 자료(공식문서 발췌)
    1. Rust는 크로스 컴파일링을 지원합니다. 이를 통해 Windows, Mac, Linux 등 다양한 플랫폼에서 실행 가능한 이식성 높은 바이너리를 만들 수 있습니다.
    2. Rust는 다양한 아키텍처를 지원합니다. 이는 ARM, x86, x86-64 등 다양한 아키텍처에서 Rust를 실행할 수 있도록 지원하는 것을 의미합니다.
    3. Rust는 다양한 운영체제를 지원합니다. 이는 Windows, macOS, Linux, FreeBSD, Android 등의 운영체제에서 Rust를 실행할 수 있도록 지원하는 것을 의미합니다.
    4. Rust는 다양한 개발 환경을 지원합니다. 이는 Visual Studio Code, Vim, Emacs 등 다양한 개발 도구에서 Rust를 사용할 수 있도록 지원하는 것을 의미합니다.
    5. Rust는 C/C++와의 상호 운용성을 제공합니다. Rust는 C/C++ 라이브러리와 쉽게 상호 작용할 수 있도록 지원합니다.
  • JavaScript와 상호 운용성: Rust는 JavaScript와의 상호 운용성을 지원합니다. 이는 웹 어셈블리와 JavaScript 코드를 쉽게 연동할 수 있도록 합니다.
  • javascript와 상호 운용성에 관한 자료(공식문서 발췌)
    1. Rust는 WebAssembly를 지원합니다. WebAssembly는 JavaScript와 함께 실행될 수 있는 바이트 코드 형식입니다. Rust는 WebAssembly를 지원함으로써 JavaScript와 상호 운용성을 제공합니다.
    2. Rust는 wasm-bindgen을 제공합니다. wasm-bindgen은 Rust와 JavaScript 간에 인터페이스를 제공하여 두 언어간에 상호 작용이 가능하도록 합니다.
    3. Rust는 Node.js와 상호 운용성을 지원합니다. Rust는 Node.js와 상호 운용성을 위한 모듈을 제공합니다.
    4. Rust는 JavaScript에 대한 다양한 라이브러리를 지원합니다. Rust는 다양한 JavaScript 라이브러리를 지원하며, 이를 통해 JavaScript와의 상호 운용성을 높일 수 있습니다.

따라서, Rust는 안전성, 성능, 이식성, 코드 유지 보수성, JavaScript와의 상호 운용성 등의 이점으로 인해, 웹 어셈블리를 개발하는 데에 매우 적합한 언어입니다.


Wesm-bindgen

wasm-bindgen은 Rust와 JavaScript 간의 상호 운용성을 제공하는 라이브러리입니다.

Rust로 작성된 코드를 JavaScript와 상호 작용할 수 있도록 해주는 도구로,

Rust로 작성한 함수를 JavaScript에서 직접 호출할 수 있게 해줍니다. 또한, JavaScript에서 Rust로 작성된 함수를 호출할 수 있도록 해줍니다.

 

wasm-bindgen을 사용하면 Rust와 JavaScript 간에 데이터를 전달할 수 있습니다.

이를 위해 wasm-bindgen은 JavaScript와 Rust 간에 인터페이스를 생성합니다.

이 인터페이스를 통해 Rust와 JavaScript 간에 데이터 전달 및 함수 호출 등이 가능해집니다.

예를 들어, Rust로 작성한 함수를 JavaScript에서 호출하려면, 해당 함수에 #[wasm_bindgen] 어노테이션을 붙여주어야 합니다.

이를 통해 해당 함수가 JavaScript와 상호 작용할 수 있도록 할 수 있습니다.

 

wasm-bindgen은 이러한 어노테이션을 붙여줄 수 있도록 지원하며, Rust 코드를 웹 어플리케이션에서 사용하기 쉽게 만들어줍니다.

따라서, wasm-bindgen은 Rust와 JavaScript 간의 상호 운용성을 높일 수 있는 유용한 도구입니다. Rust로 작성한 코드를 JavaScript와 함께 사용하고자 할 때, wasm-bindgen을 사용하면 간편하게 상호 운용성을 구현할 수 있습니다.


WebAssembly의 개념

https://developer.mozilla.org/ko/docs/WebAssembly/Concepts

 

적용방법

  • wasm
    • cargo 설치(rust의 패키지 매니저)
    https://velog.io/@markyang92/rust
    • wasm-pack 설치(Rust의 npm 패키지를 빌드하는 도구)
    cargo install wasm-pack
    
    • Rust 패키지 생성
    cargo new --lib package_name
    
    • Rust 패키지의 구조
    +-- Cargo.toml
        +-- src
            +-- lib.rs
    
    • Rust 코드 작성 (lib.rs)
      • wasm-bindgen
        • Rust 코드 설명get_top_five_colors 함수는 image_data 바이트 배열을 입력으로 사용하고 이미지의 상위 5가지 색상을 나타내는 JavaScript 값 배열(특히 JsValue)을 반환합니다.그런 다음 코드는 image_data 배열의 모든 4개 요소 위에 루프합니다. 각 픽셀은 4개의 값(R, G, B, 알파)으로 표시되므로 이 단계에서는 각 픽셀에 대한 R, G, B 값을 추출합니다.그런 다음 코드는 color_counts HashMap의 입력 메서드를 사용하여 현재 색상에 대한 키가 있는지 확인합니다.키가 존재하는 경우 메소드는 키와 관련된 값에 대한 변경 가능한 참조를 반환합니다.다음으로, 코드는 해시맵을 Vec로 변환하고 각 색상의 카운트별로 내림차순으로 정렬합니다. 그런 다음 반복기에서 take 메서드를 사용하여 정렬된 Vec에서 상위 5가지 색상을 선택합니다. take 메서드는 원래 반복기의 처음 n개 항목만 생성하는 새 반복기를 생성합니다.
          format!형식 문자열 "{:02X}"은 값 r, g , b의 형식을 지정하는 자리 표시자입니다. 중괄호 {}은(는) 해당 위치의 문자열에 값을 삽입해야 함을 나타냅니다. :02X는 대소문자를 사용하여 값을 2자리 16진수로 포맷해야 함을 나타내는 형식 지정 명령입니다. X는 문자가 대문자여야 함을 지정합니다.예를 들어, r, g, b의 값이 각각 255, 0, 128이라면 format! 호출은 마젠타 색을 나타내는 문자열 #FF0080을 생성할 것입니다.
          JsValueJsValue는 숫자, 문자열, 부울, 객체, 배열, null 또는 미정의 등 다양한 자바스크립트 값을 보유할 수 있는 유연한 유형이다. JsValue는 또한 Rust 유형을 into_serde 및 into와 같은 자바스크립트 값으로 변환하는 메서드를 가지고 있다.JsValue를 Rust 함수에 전달할 때 Rust 코드는 JsValue를 사용하여 JavaScript 또는 TypeScript 값과 상호 작용할 수 있습니다. 예를 들어, Rust 코드는 JsValue의 메서드를 호출하여 속성에 액세스하거나 메서드를 호출할 수 있습니다.
          Vec & HashMapVec은 "벡터"의 줄임말로, 필요에 따라 성장하거나 축소할 수 있는 동적 배열이다.해시맵은 키를 값에 매핑하는 키 값 저장소입니다.키와 값은 서로 다른 유형일 수 있으며 요소를 효율적으로 조회하고 삽입할 수 있는 방식으로 저장됩니다.제공한 코드에서 HashMap은 이미지 데이터에서 각 색상의 빈도를 추적하기 위해 색상과 해당 카운트를 저장하는 데 사용됩니다. HashMap을 사용하면 이미지 데이터에서 발견되는 각 색상의 카운트를 효율적으로 증가시킬 수 있다.
          let mut sorted_vec = color_counts
              .iter()
              .map(|(k, v)| (k.as_str(), *v))
              .collect::<Vec<(&str, i32)>>();
          
          해시맵 color_counts는 문자열 키(&str)와 해당 키의 등장 횟수(i32)를 값으로 가지는 데이터 구조입니다. 이 코드에서는 이 해시맵을 iter() 메서드를 사용하여 이터레이터로 변환한 후, map() 메서드를 사용하여 키와 값 쌍을 튜플로 변환하고 as_str() 메서드를 사용하여 문자열로 변환합니다. 이렇게 변환된 튜플들은 새로운 벡터(Vec)에 수집(collect())됩니다.
        • 최종적으로 sorted_vec 변수에는 color_counts 해시맵의 키와 값이 쌍으로 저장된 문자열과 정수형의 벡터가 저장됩니다.
        • Vec는 문자열 표현 이외의 추가 정보를 각 색상과 연결할 필요가 없기 때문에 반환할 상위 5가지 색상을 저장하는 데 사용됩니다. Vec을 사용하면 카운트별로 색상을 쉽게 정렬하고 상위 5가지 색상을 추출할 수 있습니다. 그런 다음 이러한 각 색상을 JsValue로 변환하여 호출하는 JavaScript 또는 TypeScript 코드로 반환할 수 있습니다.

        • 삽입, 제거 및 지우기와 같은 방법을 사용하여 HashMap에서 요소를 추가하거나 제거할 수 있으며, 지정된 키와 연결된 값이 포함된 옵션을 반환하는 get 메서드를 사용하여 요소에 액세스할 수 있습니다.

        • 값 쌍을 저장하며, 각 쌍은 키와 해당 값으로 구성됩니다.

        • 동일한 유형의 요소를 연속 메모리에 저장하므로 인덱스를 사용하거나 요소를 반복하여 요소에 액세스하는 데 효율적입니다. 푸시, 팝, 삽입 및 제거와 같은 방법을 사용하여 Vec에서 요소를 추가하거나 제거할 수 있으며 인덱싱 연산자([])를 사용하여 요소에 액세스할 수 있습니다.
        • Vec과 HashMap은 모두 Rust에서 여러 값을 저장하는 컬렉션 유형이지만, 서로 다른 특성을 가지고 있으며 다른 용도로 사용됩니다.

        • 제공한 코드에서 JsValue ::from_str은 Rust 문자열에서 새 JsValue를 생성하는 메서드입니다. 색상의 문자열 표현을 get_top_five_colors 함수에서 호출하는 JavaScript 또는 TypeScript 코드로 반환할 수 있는 JsValue로 변환하는 데 사용됩니다.

        • JavaScript 또는 TypeScript에서 JsValue를 반환하는 Rust 함수를 호출하면 JsValue를 JavaScript 값처럼 사용할 수 있습니다. 예를 들어 JsValue에서 메서드를 호출하거나 해당 속성에 액세스하거나 다른 JavaScript 또는 TypeScript 함수에 인수로 전달할 수 있습니다.

        • JsValue는 Rust에서 자바스크립트 값을 나타내는 유형이다. Wasm-bindgen을 사용하여 Rust와 JavaScript 또는 TypeScript 간의 바인딩을 생성할 때 JsValue는 Rust에서 JavaScript 값을 나타내는 데 사용되는 유형입니다.
        • 따라서 형식! 호출에서 {:02X}은(는) r, g, b 값 각각에 대해 한 번씩 세 번 사용됩니다. 이것은 #RRGGBB 형식의 RGB 색 코드를 나타내는 길이 7의 문자열을 생성한다.
        • format!는 값으로 문자열의 형식을 지정하는 데 사용되는 Rust의 매크로입니다. 당신이 제공한 코드에서, 라인 형식!("#{:02X}{:02X}}, r, g, b)는 CSS 색 코드 형식으로 RGB 색을 나타내는 문자열을 만드는 데 사용됩니다.
        • 마지막으로, 코드는 top_five_colors라는 새로운 Vec를 생성하고 상위 5개의 각 색상을 Vec에 추가합니다. from_str 메서드를 사용하여 각 색을 자바스크립트 JsValue로 변환하고 Vec로 푸시합니다. 그런 다음 Vec이 함수의 결과로 반환됩니다.
        • 그런 다음 코드는 색상 카운트 값을 1씩 증가시킵니다.
        • 키가 없으면 값이 0인 새 항목이 생성됩니다.
        • 그런 다음 포맷! @매크로를 사용하여 RGB 값을 CSS 색상 형식의 문자열로 변환합니다. format! 매크로는 자바스크립트나 TypeScript의 문자열 보간과 유사하게 러스트의 문자열을 포맷하는 편리한 방법입니다.
        • 함수는 먼저 color_counts라는 빈 해시맵을 만듭니다. 해시맵은 자바스크립트의 객체나 타입스크립트의 맵과 유사하게 키를 값에 매핑하는 컬렉션입니다.
        • wasm_bindgen 매크로를 사용하여 JavaScript 바인딩을 생성하므로, Rust 코드를 JavaScript 또는 TypeScript에서 호출할 수 있습니다.
    #[wasm_bindgen]
    pub fn get_top_five_colors(image_data: &[u8]) -> Vec<JsValue> {
      let mut color_counts = HashMap::new(); // 비어있는 빈 hashMap 생성 -> 흡사 js의 object or ts의 Map
    
      // loop over every 4 elements in the image data to extract the RGB values
      for i in (0..image_data.len()).step_by(4) { // (R, G, B, 알파) 4개씩 loop
        let r = image_data[i];
        let g = image_data[i + 1];
        let b = image_data[i + 2];
    
        let color = format!("#{:02X}{:02X}{:02X}", r, g, b); // hex 코드로 변환
        let count = color_counts.entry(color).or_insert(0); // color_counts에 현재 색상이 없으면 insert
        *count += 1; // 있으면 count++
      }
    
      let mut sorted_vec = color_counts
        .iter()
        .map(|(k, v)| (k.as_str(), *v))
        .collect::<Vec<(&str, i32)>>();
    
      sorted_vec.sort_by(|a, b| b.1.cmp(&a.1)); // 내림차순으로 정렬
    
      let top_five = sorted_vec.iter().take(5);
    
      let mut top_five_colors = Vec::new();
      for (color, count) in top_five {
        top_five_colors.push(JsValue::from_str(color));
      }
      top_five_colors
    }
    
    • Cargo.toml - Rust 프로젝트의 빌드, 의존성 관리 및 패키지 매니저 역할 ( packag.json )
    [package]
    name = "wasm-color-pick"
    authors = ["ohoolabs <ohoolabs@naver.com>"]
    description = "project with wasm-pack"
    version = "1.0.0"
    edition = "2021"
    
    # See more keys and their definitions at <https://doc.rust-lang.org/cargo/reference/manifest.html>
    
    [lib]
    crate-type = ["cdylib"]
    
    [dependencies]
    js-sys = "0.3.61"
    wasm-bindgen = "0.2"
    wasm-bindgen-test = "0.3.20"
    image = "0.23.14"
    </ohoolabs@naver.com>
    • 빌드
      • 빌드 시 일어나는 일들
        1. Rust 코드를 WebAssembly로 컴파일 합니다.
        2. 그 WebAssembly 위에서 wasm-bindgen을 실행하여, WebAssembly를 npm이 이해할 수 있는 모듈로 감싸는 JavaScript 파일을 생성합니다.
        3. pkg 디렉터리를 만들고 JavaScript 파일과 WebAssembly 코드를 그 안으로 옮깁니다.
        4. Cargo.toml 을 읽고 동등한 package.json을 생성합니다.
    wasm-pack build --scope ohoolabs
    
    • npm에 패키지 배포 (pkg 디렉토리로 이동 후 실행 / cd pkg)
      • npm 패키지로 사용하는 이유
        • 공식문서 안내
        • 다른 사용자들이 Rust관련 패키지를 설치할 수고가 없음
        • 현재 상태에서 따로 webpack 설정이 필요없음 ( wasm-loader 불필요 )
    npm publish --access=public
    
  • 사용예시
import workerSingleton from "worker/WorkerSingleton";

public componentDidMount(): void {
  
  this.worker = workerSingleton;

  this.worker.onmessage = e => {
    const hsvObjList = (Array.from(e.data || []).map(hexColor => {
      const convertedHexColor = Convert(hexColor);
      const hsvObj = roundObjectValues(convertedHexColor.hsv().object());
      return hsvObj;
    }) as unknown) as IHSVColor[];

    this.props.setDominantColors(hsvObjList);
  };
}
  • worker
    • worker.js (./src/app/worker/worker.js)
    • webworker의 적정 갯수CPU 코어 개수: 보편적으로 CPU 코어 개수와 비슷한 수의 Worker를 사용하는 것이 적절합니다. 이는 각 Worker가 병렬로 실행될 수 있도록 하기 위함입니다.작업의 성격: 작업이 CPU를 많이 사용하는 작업인 경우, Worker의 개수를 줄이는 것이 좋습니다.메모리 사용량: 각 Worker는 독립적인 메모리 공간을 사용하므로, 시스템의 메모리 사용량을 고려하여 Worker의 개수를 결정하는 것이 좋습니다.브라우저의 Worker 제한: 브라우저마다 Worker 개수에 제한을 둘 수도 있습니다. 이 경우, 브라우저의 Worker 제한을 고려하여 Worker 개수를 결정하는 것이 좋습니다.
    • 일단은 wasm 모듈이 많지 않을 경우엔 1개로 제한해서 사용해요 괜찮을 것 같습니다.
    • 작업이 I/O 바운드인 경우, 많은 Worker를 사용해도 성능 향상 효과가 크지 않으므로, 필요한 만큼만 사용하는 것이 좋습니다.
    • Web Worker의 적정 개수는 상황에 따라 다르며, 몇 개의 Worker가 적절한지 결정하는 것은 어렵습니다. 하지만 몇 가지 기준을 제시해 드릴 수 있습니다.
    • 워커 갯수 관리하기
    • // CPU 코어 수 파악 const numCores = navigator.hardwareConcurrency || 1; // 웹 워커 생성 및 관리 객체 생성 const workers = []; for (let i = 0; i < numCores; i++) { workers.push(new Worker('worker.js')); } // 작업할 이미지 URL 배열 const imageUrls = ['<https://example.com/image1.png>', '<https://example.com/image2.png>', '<https://example.com/image3.png>']; // 웹 워커에 이미지 URL 배열 전달 및 작업 시작 const promises = workers.map((worker, index) => { const startIndex = index * (imageUrls.length / numCores); const endIndex = (index + 1) * (imageUrls.length / numCores); const urls = imageUrls.slice(startIndex, endIndex); return new Promise(resolve => { worker.onmessage = e => { // 웹 워커로부터 이미지 데이터 배열 수신 const images = e.data; // 작업 결과 처리 // ... // 작업이 끝난 웹 워커 종료 worker.terminate(); resolve(); }; worker.postMessage(urls); }); }); // 모든 작업 완료 후 실행할 작업 등록 Promise.all(promises).then(() => { // 모든 작업 완료 후 실행할 작업 처리 // ... });
      self.onmessage = async e => {
        if (e.data.type === "wasm") {
          try {
            const wasmModule = await import("@ohoolabs/hello-wasm");
            const result = wasmModule.add(1, 2);
            self.postMessage(result);
          } catch (error) {
            console.error(`web worker error: ${error}`);
          }
        }
      };
    
    
    • worker 등록 후 메세지 주고 받기
    public componentDidMount(): void { 
    	this.worker = new Worker('../worker/index.js', {type : "module"});
    
    	this.worker.onmessage = (e) => {
    	  console.log('worker onmessage:', e)
    	}
    }
    
  • worker를 싱글톤으로 사용하기 위한 WorkerSingleton.ts
    • WorkerSingleton.ts (./src/app/worker/WorkerSingleton.js)
import Worker from "worker-loader!./worker.js";

class WorkerSingleton {
  public onmessage: () => void;
  public onmessageerror: () => void;
  public postMessage: () => void;
  public terminate: () => void;
  public addEventListener: () => void;
  public removeEventListener: () => void;
  public dispatchEvent: (e: MouseEvent) => boolean;
  public onerror: () => void;
  private static instance: Worker;

  constructor() {
    if (!WorkerSingleton.instance && typeof window !== "undefined") {
      WorkerSingleton.instance = new Worker("worker-loader!./worker.ts");
    }
    return WorkerSingleton.instance;
  }
}

const workerSingleton = new WorkerSingleton();

export default workerSingleton;
  • worker-loader(npm install worker-loader --save-dev)
    • worker-loader란?**worker-loader**를 사용하면 웹팩에서 웹 워커 스크립트를 번들링할 수 있습니다. 일반적으로 웹 워커 스크립트를 만들 때는 별도의 파일로 분리하여 작성합니다. 하지만 웹팩에서는 일반적으로 웹팩 로더를 사용하여 모든 파일을 번들링합니다. 이로 인해 웹 워커 스크립트를 분리하여 작성할 수 없게 되는데, 이때 **worker-loader**를 사용하면 웹 워커 스크립트를 분리하여 작성할 수 있습니다.**worker-loader**는 다음과 같은 특징을 가지고 있습니다.
      • 웹 워커 스크립트를 번들링하여 파일을 분리할 수 있습니다.
      • ES6 모듈과 CommonJS 모듈을 지원합니다.
      • TypeScript와 함께 사용할 수 있습니다.
      • 웹 워커 스크립트를 빌드할 때, 코드를 압축할 수 있습니다.
      • Worker 인스턴스를 생성할 때, URL 문자열 대신 모듈 경로를 전달할 수 있습니다.
      **worker-loader**를 사용하면 웹 워커를 쉽게 사용할 수 있으며, 웹팩과 함께 사용하여 빌드 프로세스를 간소화할 수 있습니다.
    • **worker-loader**를 사용하면, Worker 인스턴스를 생성할 때 URL 문자열 대신 모듈 경로를 전달할 수 있습니다. **worker-loader**는 이 경로를 사용하여 웹 워커 스크립트를 번들링하고, 번들링된 스크립트를 사용하여 Worker 인스턴스를 생성합니다.
    • **worker-loader**는 웹 워커를 위한 웹팩 로더입니다. 웹 워커는 메인 스레드와 별도로 동작하는 스레드로, 메인 스레드와 독립적으로 실행되며 웹 애플리케이션의 성능을 향상시키는 데 사용됩니다.
//webpack 설정

{
  test: /\\.worker\\.js$/,
  use: { loader: 'worker-loader' }
},
public componentDidMount(): void {
    const curShape = Array.from(this.props.selectedShapes).shift();

    this.worker = workerSingleton;

    this.worker.onmessage = e => {
      const hsvObjList = (Array.from(e.data || []).map(hexColor => {
        const convertedHexColor = Convert(hexColor);
        const hsvObj = roundObjectValues(convertedHexColor.hsv().object());
        return hsvObj;
      }) as unknown) as IHSVColor[];

      this.props.setDominantColors(hsvObjList);
    };

    if (curShape instanceof Layer && this.props.showColorPicker) {
      this.loadColorList();
    }
  }

public componentDidMount(): void {
    const curShape = Array.from(this.props.selectedShapes).shift();

    this.worker = workerSingleton;

    this.worker.onmessage = e => {
      const hsvObjList = (Array.from(e.data || []).map(hexColor => {
        const convertedHexColor = Convert(hexColor);
        const hsvObj = roundObjectValues(convertedHexColor.hsv().object());
        return hsvObj;
      }) as unknown) as IHSVColor[];

      this.props.setDominantColors(hsvObjList);
    };

    if (curShape instanceof Layer && this.props.showColorPicker) {
      this.loadColorList();
    }
  }

 

그외에 적용을 기대할 수 있는 부분

  • 텍스트 렌더링: WebAssembly를 사용하여 텍스트 렌더링 성능을 개선할 수 있습니다. 이는 Rust와 같은 언어로 작성된 빠른 텍스트 렌더링 엔진을 사용하여 구현할 수 있습니다.https://www.npmjs.com/package/rustybuzz-wasm?activeTab=readme
  • Rust로 작성된 빠른 텍스트 렌더링 엔진인 rusttype을 WebAssembly로 변환하여 사용한 예시 코드입니다.
  • 이미지 처리: WebWorker를 사용하여 이미지 처리 작업을 백그라운드에서 처리할 수 있습니다. 예를 들어, 이미지 크기 조정, 회전, 필터 적용 등의 작업을 더 빠르게 처리할 수 있습니다.

WebWorker를 사용하여 이미지 리사이징을 수행하는 예시 코드입니다.

https://github.com/nukesor/picopt

  • 컬러 관리: WebAssembly를 사용하여 컬러 관리 성능을 개선할 수 있습니다. 예를 들어, 빠른 컬러 변환, 특정 색상 공간에서의 컬러 조정 등을 더 빠르게 처리할 수 있습니다.참고 링크: https://github.com/dakom/lanczos-wasm
  • Rust로 작성된 빠른 컬러 변환 라이브러리인 **lanczos**를 WebAssembly로 변환하여 사용한 예시 코드가 있습니다. 이 코드는 브라우저에서 빠르게 컬러 변환 작업을 수행할 수 있도록 도와줍니다.
  • 선 그리기: WebAssembly를 사용하여 선 그리기 성능을 개선할 수 있습니다. 이는 Rust와 같은 언어로 작성된 빠른 선 그리기 엔진을 사용하여 구현할 수 있습니다.https://github.com/rust-skia/rust-skia
  • Rust로 작성된 빠른 선 그리기 엔진인 rust-skia를 WebAssembly로 변환하여 사용한 예시 코드입니다.
  • 직교 좌표계 계산: WebAssembly를 사용하여 직교 좌표계 계산 성능을 개선할 수 있습니다. 예를 들어, 좌표 회전, 크기 조정 등의 작업을 더 빠르게 처리할 수 있습니다.https://github.com/richardanaya/line-integral-convolution-wasm
  • WebAssembly를 사용하여 빠르게 직교 좌표계 계산을 수행하는 예시 코드입니다.
Comments