Siner's Blog

about me


node와 ts-node의 CPU,RAM 사용량 비교

ts-node를 production에서 사용하면 안되는 이유

2022/02/19


node와 ts-node의 차이가 어디에서 오는지 알아보기 위해 조사한 과정을 기록으로 남기고자 합니다.

특정 코드의 동작시간과 메모리 사용량을 각각 비교해보았습니다.

테스트 코드

테스트하고자 하는 코드는 소수의 개수를 반환하는 간단한 코드입니다.

function isPrime(num: number): boolean {
  if (num < 2) return false;
  if (num === 2) return true;
  for (let i = 2; i < num; i++) {
    if (num % i === 0) return false;
  }
  return true;
}

function test(): void {
  const t0 = performance.now();
  const iterNum = 100000;
  const primes: number[] = [];

  for (let i = 2; i < iterNum; i++) {
    const progressRate: number = (i / iterNum) * 100;
    if (isPrime(i)) primes.push(i);
  }

  const t1 = performance.now();
}
전체 벤치마크 코드 (열어서 보기)
interface MemoryUsage {
  rss: number;
  heapTotal: number;
  heapUsed: number;
  external: number;
  arrayBuffers: number;
}

function isPrime(num: number): boolean {
  if (num < 2) return false;
  if (num === 2) return true;
  for (let i = 2; i < num; i++) {
    if (num % i === 0) return false;
  }
  return true;
}

function printUsage(metrics: { progress: number; usage: MemoryUsage }[], key: string) {
  console.log(key);
  let prev: MemoryUsage = metrics[0].usage;
  metrics.forEach((curr) => {
    const { progress, usage } = curr;
    console.log(`[${progress.toString().padStart(3, '0')}%]${usage[key]}(${usage[key] - prev[key] >= 0 ? '+' : ''}${usage[key] - prev[key]})`);
    prev = curr.usage;
  });
  console.log('\n');
}

function test(): void {
  const t0 = performance.now();

  let progress: number = 0;
  const iterNum = 100000;
  const primes: number[] = [];
  const metrics: { progress: number, usage: MemoryUsage }[] = [];
  for (let i = 2; i < iterNum; i++) {
    const progressRate: number = (i / iterNum) * 100;
    if (progress <= progressRate) {
      metrics.push({ progress, usage: process.memoryUsage() });
      progress += 10;
    }
    if (isPrime(i)) primes.push(i);
  }
  metrics.push({ progress, usage: process.memoryUsage() });
  const t1 = performance.now();

  console.log('=============================');
  console.log(`소수는 ${primes.length}개 입니다.`);
  console.log((t1 - t0) + 'ms 걸렸습니다.');
  console.log('=============================');

  printUsage(metrics, 'rss');
  printUsage(metrics, 'heapTotal');
  printUsage(metrics, 'heapUsed');
  printUsage(metrics, 'external');
  printUsage(metrics, 'arrayBuffers');
}

test();

결과

좌측(또는 위)이 node, 우측(또는 아래)이 ts-node입니다.

시간

node와 ts-node 모두 출력된 결과에선 별 차이가 없었고, 반복문을 10만에서 100만회로 늘려서 다시 시도해보아도 0.3%정도의 오차율만 보였습니다.

ts-node의 경우 ts-node 패키지를 실행하는 시간으로 인해 실제로는 수 초 이상의 시간이 더 소요되었습니다. 하지만 이러한 컴파일 타임은 서버와 같이 장시간 동작하는 프로세스에서는 의미없는 차이라고 느껴집니다.

스크린샷 2022-02-19 오후 4 22 24 스크린샷 2022-02-19 오후 4 23 46

Resident Set Size (RSS)

RSS의 경우 7~8배 정도의 차이가 있었습니다. 또한 node의 rss는 코드가 진행됨에따라 변화했지만, ts-node는 변화가 거의 없었습니다. node는 최소한의 메모리만을 사용하도록 최적화가 되어있는 반면, ts-node는 한번에 쫙 땡겨서 쓴다는 느낌을 받았습니다.

스크린샷 2022-02-19 오후 4 22 38 스크린샷 2022-02-19 오후 4 24 00

heapTotal

heapTotal의 경우 20에서 최대 26배까지 차이가 있었습니다. ts-node의 경우 typescript를 해석하기 위해 컴파일러를 메모리에 로드하는데, 이 때문에 메모리의 차이가 크게 발생하는 것으로 보입니다.

// ./node_modules/ts-node/index.ts
function loadCompiler(name: string | undefined, relativeToPath: string) {
  const projectLocalResolveHelper =
    createProjectLocalResolveHelper(relativeToPath);
  const compiler = projectLocalResolveHelper(name || 'typescript', true);
  const ts: TSCommon = attemptRequireWithV8CompileCache(require, compiler);
  return { compiler, ts, projectLocalResolveHelper };
}
스크린샷 2022-02-19 오후 4 22 52 스크린샷 2022-02-19 오후 4 24 10

heapUsed

heapUsed의 경우 24~27배정도의 차이가 있었습니다. 눈에 띄었던 점은, node의 경우 30% 구간을 통과할때 heapUsed가 60만에서 70만정도 감소했지만, ts-node의 경우엔 계속해서 증가하기만 하는 차이를 보였습니다. 이는 반복해서 테스트를 돌려도 같은 결과가 나타났습니다.

스크린샷 2022-02-19 오후 4 23 03 스크린샷 2022-02-19 오후 4 24 20

external

external은 V8에서 관리하는 객체로써, JavaScript에 바인딩된 C++ 객체의 메모리 사용량을 나타냅니다. external또한 5배정도의 차이가 있었습니다.

스크린샷 2022-02-19 오후 4 23 15 스크린샷 2022-02-19 오후 4 24 36

arrayBuffers

arrayBuffers는 약 4배정도의 차이를 보였습니다.

스크린샷 2022-02-19 오후 4 23 24 스크린샷 2022-02-19 오후 4 24 45

결론

ts-node는 js파일을 생성하지 않는 대신, typescript 컴파일러를 메모리에 올려서 동작시켜야 하기 때문에 heap 메모리의 낭비가 있으므로, 자원이 한정적인 real-world(production)에서는 사용하지 않는것이 무조건 좋습니다.



쿠버네티스 정리 1. 컴포넌트 종류

쿠버네티스가 각 문제들을 해결하는 방법

JAVA 자료구조 정리

java data structure cheat sheet