node.js 의 http/https 서버에서 요청된 body buffer를 req.on('data') 에서 이어 붙일 때 문자열 += 연산으로 하는것과 buffer 배열에 append 한 뒤 마지막에 문자열 하나로 만드는 것 중 어느게 더 효율적인지 테스트 해 봄.

const { performance } = require('perf_hooks');
 
if (!global.gc) {
    console.error("Run the script with '--expose-gc' option: node --expose-gc x.js");
    process.exit(1);
}
 
// 설정값
const NUM_CHUNKS = 10000;  // 생성할 버퍼 개수
const CHUNK_SIZE = 1024*10;    // 각 버퍼 크기 (바이트)
const ITERATIONS = 100;       // 반복 실행 횟수
 
// 랜덤 데이터 생성 함수
function createRandomBuffers(numChunks, chunkSize) {
    const buffers = [];
    for (let i = 0; i < numChunks; i++) {
        buffers.push(Buffer.alloc(chunkSize, 'a')); // 'a'로 채운 버퍼
    }
    return buffers;
}
 
// 메모리 및 시간 측정 함수 (반복 실행 및 통계 계산)
function benchmark(fn, buffers, methodName) {
    let times = [];
    let memoryUsages = [];
 
    // 초기 GC 실행 (메모리 측정 안정화)
    global.gc();
    let baselineMem = process.memoryUsage().heapUsed;
 
    for (let i = 0; i < ITERATIONS; i++) {
        global.gc(); // GC 실행하여 메모리 정확도 높임
        const startMem = process.memoryUsage().heapUsed;
        const startTime = performance.now();
 
        let result = fn(buffers); // 벤치마크 실행
 
        const endTime = performance.now();
        global.gc(); // 최종 GC 실행 (메모리 측정)
 
        const endMem = process.memoryUsage().heapUsed;
 
        // 문자열 결과를 해제하여 영향 제거
        result = null;
        global.gc();
 
        const memoryUsed = Math.max(0, (endMem - startMem) / 1024 / 1024);
        const timeTaken = endTime - startTime;
 
        times.push(timeTaken);
        memoryUsages.push(memoryUsed);
 
        //console.log(`[${methodName}] Iteration ${i + 1}: ${timeTaken.toFixed(2)}ms, ${memoryUsed.toFixed(2)}MB`);
    }
 
    // 통계 계산 함수
    function calculateStats(values) {
        const mean = values.reduce((a, b) => a + b, 0) / values.length;
        const stdDev = Math.sqrt(
            values.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b, 0) / values.length
        );
        return {
            mean: mean.toFixed(2),
            stdDev: stdDev.toFixed(2),
            min: Math.min(...values).toFixed(2),
            max: Math.max(...values).toFixed(2),
        };
    }
 
    // 최종 결과 출력
    console.log(`\n=== ${methodName} Results ===`);
    console.log(`Execution Time (ms) → Mean: ${calculateStats(times).mean}, StdDev: ${calculateStats(times).stdDev}, Min: ${calculateStats(times).min}, Max: ${calculateStats(times).max}`);
    console.log(`Memory Usage (MB) → Mean: ${calculateStats(memoryUsages).mean}, StdDev: ${calculateStats(memoryUsages).stdDev}, Min: ${calculateStats(memoryUsages).min}, Max: ${calculateStats(memoryUsages).max}`);
    console.log('================================\n');
}
 
// 문자열 방식 (s += chunk.toString())
function benchmarkStringConcat(buffers) {
    let s = '';
    for (let buf of buffers) {
        s += buf.toString();
    }
    return s;
}
 
// 버퍼 방식 (Buffer.concat())
function benchmarkBufferConcat(buffers) {
    return Buffer.concat(buffers).toString();
}
 
// 실행
const buffers = createRandomBuffers(NUM_CHUNKS, CHUNK_SIZE);
benchmark(benchmarkBufferConcat, buffers, 'Buffer.concat()');
benchmark(benchmarkStringConcat, buffers, 'String Concatenation');

 

 

실행결과는 아래와 같음.

# 명시적 gc를 위해 --expose-gc 옵션
node --expose-gc benchmark.js
 
# 결과
=== Buffer.concat() Results ===
Execution Time (ms) → Mean: 27.19, StdDev: 3.17, Min: 24.88, Max: 55.72
Memory Usage (MB) → Mean: 97.65, StdDev: 0.07, Min: 96.98, Max: 97.67
================================
 
 
=== String Concatenation Results ===
Execution Time (ms) → Mean: 30.79, StdDev: 1.99, Min: 28.96, Max: 49.10
Memory Usage (MB) → Mean: 98.11, StdDev: 0.01, Min: 98.02, Max: 98.12
================================

 

 

미미한 차이긴 하나 Buffer.concat()이 약간 더 효율적임.

 

 

Posted by bloodguy
,