Node.js와 C++의 퀵정렬 속도 비교 (+메모리 할당 개선)

__DALL·E 2022-08-18 22.07.04 - Minions drawn by H.R. Giger.jpg
"Minions drawn by H.R. Giger" from DALL-E 2

재귀 없는 퀵정렬을 JS와 C++로 구현하면서 수행속도를 측정하고자 하였다.

 

C++은 정수형 타입이더라도 1바이트, 2바이트, 4바이트, 8바이트로 개발자가 선택하여 사용해야 하는 반면, JS는 정수형과 실수형 타입 모두 number로 사용되고 있다.

 

다만, JS의 경우 ArrayBuffer를 통해 원하는 바이트 크기의 버퍼를 생성할 수 있고, Int32Array() 함수로 해당 버퍼를 래핑하여 배열 원소의 크기를 4바이트로 사용할 수 있다.

 

[기본 배열 사용시]

  • JS에서 1억개 할당 시 기본 배열은 최대 6.8GB의 메모리 공간을 차지하였다.
  • 정렬 시 시간은 13.452초 소요되었다.
  • 메모리 할당 시간은 36.656초 소요되었다.

[고정 크기 배열 사용시]

  • JS에서 1억개 할당 시 고정 크기 배열은 400MB를 사용하였다. 정확히 1억개의 4바이트를 할당하였다.
  • 정렬 시 시간은 12.507로 기본 배열보다 근소하게 빨랐다.
  • 메모리 할당 시간은 2.344초가 소요되었다. 시간이 기본 배열의 6% 정도밖에 되지 않았다.

테스트에서, C++에서는 4바이트 크기인 int 형으로 배열을 만들어 사용하였고, JS에서는 Int32Array 배열을 만들어 사용하였다.

 

데이터 개수를 2천만, 4천만, 6천만, 8천만, 1억으로 증가하여 수행시간을 측정하였다.

 

시간 : 초 C++ (메모리 할당) C++ (퀵정렬) JS (메모리 할당) JS (퀵정렬)
2000만 0.702 2.01 0.531 2.439
4000만 1.426 4.255 0.956 5.11
6000만 2.145 6.545 1.478 7.714
8000만 2.868 8.881 1.901 10.606
1억 3.58 11.284 2.416 13.483

etc-image-1

 

특이하게도, 메모리 할당하는 시간은 JS가 더 적게 소요되었다.

 

퀵정렬의 경우 C++가 전체적으로 JS보다 약 20% 빠른 처리성능을 보여주었다.

 

이 결과를 보고, JS와 같은 인터프리트 언어라고 해서 반드시 C++보다 많이 느린 것은 아니라고 생각하게 되었다. 이 정도의 차이는 실제 사용시 크게 체감되지 않을 정도로 볼 수 있을 것 같다.

 

코드는 다음과 같다.

 

[JavaScript]

const USE_FIX_ARR = true;

/** INFO: 파라미터 만큼 배열을 생성하고 랜덤값 입력 */
function generateRandomArray(numData){
    let arr;
    if(USE_FIX_ARR){
        let arrBuffer = new ArrayBuffer(4 * numData);
        arr = new Int32Array(arrBuffer);
    } else {
        arr = new Array(numData).fill(0);
    }
    for(let i=0;i<numData;i++){
        arr[i] = Math.round(Math.random() * numData);
    }
    return arr;
}

function quickSort(arr, start, end){
    let stack = [];
    let low = start, high = end;
    let pivot, tmp;
    let i, j;
    stack.push(high);
    stack.push(low);
    while(stack.length !== 0){
        low = stack.pop();
        high = stack.pop();
        let curLength = high - low + 1;
        if(low >= high){
            //INFO: 한개밖에 없으면 continue
            continue;
        }

        if(curLength > 2) {
            //INFO: 2개 이상인 경우
            let tmpMid = Math.round((low+high) >> 1);
            //INFO: low, mid, high 중 중간값을 정해야 함
            pivot = arr[low] > arr[high] ? (arr[low] > arr[tmpMid] ? (arr[tmpMid] > arr[high] ? tmpMid : high) : low) : (arr[high] > arr[tmpMid] ? (arr[tmpMid] > arr[low] ? tmpMid : low) : high);

            {//SWAP
                tmp = arr[high];
                arr[high] = arr[pivot];
                arr[pivot] = tmp;
            }

            pivot = high;
        } else {
            //INFO: 2개 밖에 없는 경우
            if(arr[low] > arr[high]){
                //SWAP
                tmp = arr[low];
                arr[low] = arr[high];
                arr[high] = tmp;
            }
            continue;
        }

        //INFO: 피봇
        pivot = arr[high];
        i = low - 1;
        j = high;
        while(true){
            while(arr[++i] < pivot);
            while(arr[--j] > pivot);
            if(i >= j) break;
            tmp = arr[i];
            arr[i] = arr[j];
            arr[j] = tmp;
        }
        tmp = arr[i];
        arr[i] = arr[high];
        arr[high] = tmp;

        stack.push(high);
        stack.push(i+1);
        stack.push(i-1);
        stack.push(low);
    }
}

function main(){
    let numData = 100000000;
    let dataSizeGap = 5000000;

    for(let j=0;j<1;j++){
        for(let i=0;i<3;i++){
            console.time('mem');
            let arr = generateRandomArray(numData);
            console.timeEnd('mem');
            console.log('Start Quick Sort; data size : ' + numData);
            console.time('quick_sort');
            quickSort(arr, 0, numData - 1);
            console.timeEnd('quick_sort');
            //console.log(arr);
        }
        numData += dataSizeGap;
    }
}
main();

[C++]

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

#define N 100000000
#define R 100000
#define STKSIZE N

//unsigned int arr[N];
int* arr = nullptr;

int top = 0;
int* stack = nullptr;
//int stack[STKSIZE];

void init_stack() {
    top = 0;
}

void push(int i) {
    if (top < STKSIZE)
        stack[top++] = i;
    else {
        printf("stack full\n");
        exit(0);
        return;
    }
}

int pop() {
    if (top != 0)
        return stack[--top];
    else
        return 0;
}

int isStackEmpty() {
    if (top != 0)
        return 0;
    else
        return 1;
}

void quickSort() {
    int pivot, tmp;
    int i, j;
    int low, high;
    init_stack();
    low = 0;
    high = N - 1;
    push(high);
    push(low);
    while (!isStackEmpty()) {
        low = pop();
        high = pop();
        if (low >= high) {
            continue;
        }
        int curLength = high - low + 1;
        int pivot;

        if (curLength > 2) {
            int tmpMid = (low + high) / 2;
            pivot = arr[low] > arr[high] ? (arr[low] > arr[tmpMid] ? (arr[tmpMid] > arr[high] ? tmpMid : high) : low) : (arr[high] > arr[tmpMid] ? (arr[tmpMid] > arr[low] ? tmpMid : low) : high);
            {
                tmp = arr[high];
                arr[high] = arr[pivot];
                arr[pivot] = tmp;
            }
            pivot = high;
        }
        else {
            if (arr[low] > arr[high]) {
                tmp = arr[low];
                arr[low] = arr[high];
                arr[high] = tmp;
            }
            continue;
        }

        //INFO: 데이터가 최소 두개
        pivot = arr[high];
        i = low - 1;
        j = high;
        while (1) {
            while (arr[++i] < pivot);
            while (arr[--j] > pivot);
            if (i >= j)
                break;
            tmp = arr[i];
            arr[i] = arr[j];
            arr[j] = tmp;
        }

        tmp = arr[i];
        arr[i] = arr[high];
        arr[high] = tmp;

        push(high);
        push(i + 1);
        push(i - 1);
        push(low);
    }
}

void main() {
    srand(time(NULL));

    stack = new int[STKSIZE];

    clock_t start, end;

    for (int i = 0; i < 3; i++) {
        top = 0;
        start = clock();
        arr = new int[N];
        for (int i = 0; i < N; i++) {
            arr[i] = (rand() * (rand() + 1)) % N;
        }
        end = clock();
        printf("mem time : %lf\n", (double)(end - start) / CLOCKS_PER_SEC);

        start = clock();
        quickSort();
        end = clock();
        printf("quicksort time : %lf\n", (double)(end - start) / CLOCKS_PER_SEC);

        delete[] arr;
    }

    delete[] stack;
}