Siner's Blog


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)에서는 사용하지 않는것이 무조건 좋습니다.