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 패키지를 실행하는 시간으로 인해 실제로는 수 초 이상의 시간이 더 소요되었습니다. 하지만 이러한 컴파일 타임은 서버와 같이 장시간 동작하는 프로세스에서는 의미없는 차이라고 느껴집니다.
Resident Set Size (RSS)
RSS의 경우 7~8배 정도의 차이가 있었습니다. 또한 node의 rss는 코드가 진행됨에따라 변화했지만, ts-node는 변화가 거의 없었습니다. node는 최소한의 메모리만을 사용하도록 최적화가 되어있는 반면, ts-node는 한번에 쫙 땡겨서 쓴다는 느낌을 받았습니다.
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 };
}
heapUsed
heapUsed의 경우 24~27배정도의 차이가 있었습니다. 눈에 띄었던 점은, node의 경우 30% 구간을 통과할때 heapUsed가 60만에서 70만정도 감소했지만, ts-node의 경우엔 계속해서 증가하기만 하는 차이를 보였습니다. 이는 반복해서 테스트를 돌려도 같은 결과가 나타났습니다.
external
external은 V8에서 관리하는 객체로써, JavaScript에 바인딩된 C++ 객체의 메모리 사용량을 나타냅니다. external또한 5배정도의 차이가 있었습니다.
arrayBuffers
arrayBuffers는 약 4배정도의 차이를 보였습니다.
결론
ts-node는 js파일을 생성하지 않는 대신, typescript 컴파일러를 메모리에 올려서 동작시켜야 하기 때문에 heap 메모리의 낭비가 있으므로, 자원이 한정적인 real-world(production)에서는 사용하지 않는것이 무조건 좋습니다.