web

[React 최적화] 실습 4. 이미지 갤러리 페이지(3) - 병목함수 memoization 적용

Jaaaay 2022. 8. 8. 13:04

병목 함수에 memoization 적용

병목함수 memoiztion 적용은 pure function일 때만 가능하다.

const cache = {};

export function getAverageColorOfImage(imgElement) {
  if (cache.hasOwnProperty(imgElement)) {
    console.log('cache has the key', cache);
    return cache[imgElement];
  }
  console.log('cache dont have the key', cache);
  const canvas = document.createElement('canvas');
  const context = canvas.getContext && canvas.getContext('2d');
  const averageColor = {
    r: 0,
    g: 0,
    b: 0,
  };

  if (!context) {
    return averageColor;
  }

  const width = (canvas.width =
    imgElement.naturalWidth || imgElement.offsetWidth || imgElement.width);
  const height = (canvas.height =
    imgElement.naturalHeight || imgElement.offsetHeight || imgElement.height);

  context.drawImage(imgElement, 0, 0);

  const imageData = context.getImageData(0, 0, width, height).data;
  const length = imageData.length;

  for (let i = 0; i < length; i += 4) {
    averageColor.r += imageData[i];
    averageColor.g += imageData[i + 1];
    averageColor.b += imageData[i + 2];
  }

  const count = length / 4;
  averageColor.r = ~~(averageColor.r / count); // ~~ => convert to int
  averageColor.g = ~~(averageColor.g / count);
  averageColor.b = ~~(averageColor.b / count);

  cache[imgElement] = averageColor;

  return averageColor;
}

모듈 바깥에 cache 객체를 두고 캐시가 있을 경우 기록을 return, 그렇지 않을 경우 마지막에 캐시를 저장해둔다.

  • 다른 이미지에 대해서도 캐시가 저장됐다고 뜨는 문제

object HTMLImageElement 값으로 input이 들어가서 같은 키로 인식됐다.

src url을 키로 활용하자.

const cache = {};

export function getAverageColorOfImage(imgElement) {
  if (cache.hasOwnProperty(imgElement.src)) {
    console.log('cache has the key', imgElement.src);
    return cache[imgElement.src];
  }
  console.log('cache dont have the key', imgElement.src);

...

  cache[imgElement.src] = averageColor;

  return averageColor;
}

정상 작동을 확인할 수 있다.

팩토리 패턴 적용

캐시를 사용해야하는 대상이 많을 경우 일일이 위와 같은 방식으로 객체를 따로 설정하기 번거롭고 관리가 용이하지 못하다.

팩토리 패턴으로 설계해보자.

// memoize.js
function memoize(fn) {
  const cache = {};

  return function (...args) {
    if(args.length !== 1){
        return fn(...args);
    }

    if (cache.hasOwnProperty(args)) {
      return cache[args];
    }

    const result = fn(...args);
    cache[args] = result;

    return result;
  };
}

위와 같은 예시의 함수로 기존 함수를 wrapping 해주면 캐시를 이용하는 함수로 쉽게 만들어줄 수 있다.

memoization의 기회비용

input 값이 매번 달라지는 함수 등은 이런 memoization을 사용할 경우 오히려 메모리의 낭비만 일으킬 수 있다.

같은 input이 자주 쓰이거나, 함수의 작동 자체가 복잡한 경우 이용하면 좋다.


정리

Layout Shift 피하기

→ 이미지 지연(lazy) 로딩

→ useSelector 렌더링 문제 해결

→ Redux Reselect를 통한 렌더링 최적화

→ 병목 함수에 memoization 적용