본문 바로가기
Dev/Rust

Rust와 C++의 기본적인 속도 비교 1

by ArcticBear 2024. 5. 11.
반응형

 

 

 Rust의 속도 장점

코딩을 하면서 항상 느끼는 것이지만, “어떻게 빠르고 오류 없는 프로그램을 개발할 수 있을까?”는 개발자들의 가장 큰 고민 중 하나라고 생각합니다. 개발을 진행하면서 오류는 항상 발생하기 때문에, 오류를 100% 없애기보다는 최대한 줄이는 방향으로 나아가는 것이 개발 시간을 단축하는 방법이라고 믿습니다. Java나 C# 같은 언어로 처음 개발을 시작하다가 즉각적인 레이턴시 요구사항으로 인해 개발을 진행하다 보면, C++이 필연적으로 필요한 경우가 발생합니다. C++은 작성 후 컴파일하면 기계어로 번역되어 프로그램이 실행되기 때문에 매우 빠른 성능을 보여주지만, 프로그램의 흐름을 제대로 파악하지 않고 사용한다면 많은 오류를 발생시킬 수 있습니다.

대표적으로 개발자들이 겪는 C++ 컴파일 및 디버깅 오류로는 다음과 같은 문제들이 있습니다:

 

1. 개발자에게 자주 발생하는 C++ Compile/Debugging 오류

  • 메모리 오류 : C++에서는 메모리 누수, dangling pointers 및 메모리 해제 후에 포인터를 사용하는 등의 일반적인 메모리 관리 오류가 발생할 수 있다.
  • 스레딩 오류 : C++에서는 경쟁 조건, 데드락 및 스레드 안전성 문제와 같은 다중 스레딩 오류가 발생할 수 있다.
  • 안전하지 않은 형 변환 및 포인터 조작 : C++에서는 포인터 캐스팅 및 원시 포인터 조작과 같은 위험한 형 변환을 수행할 수 있으며, 이는 메모리 오류로 이어질 수 있다.
  • Null 포인터 및 널 참조 : C++에서는 null 포인터 및 널 참조로 인한 오류가 발생할 수 있다.
  • 안전하지 않은 동작 : C++에서는 배열 경계 초과, 포인터 산술 오버플로우 및 다른 안전하지 않은 작업으로 인한 예기치 않은 동작이 발생할 수 있다.

 

이러한 문제가 자주 발생하다 보니 C++ 개발자들은 앞서 언급한 여러 문제들에 대해 끊임없이 신경 써야 하며, 그 결과 개발 리소스의 상당 부분이 소모되기 마련입니다. 이러한 문제점을 해결하기 위해 모질라 재단은 2010년 7월경 Rust라는 새로운 프로그래밍 언어를 발표하였습니다.

Rust 언어는 위에서 언급한 문제들을 다음과 같이 해결하였습니다.

 

2. Rust의 문제 해결법

  • 메모리 오류 : Rust는 컴파일 시간에 메모리 안전성을 강제하므로 이러한 종류의 오류를 방지한다.
  • 스레딩 오류 : Rust는 안전한 다중 스레딩을 지원하기 위한 기능을 제공하여 이러한 종류의 오류를 방지한다.
  • 안전하지 않은 형 변환 및 포인터 조작 : Rust는 안전하지 않은 형 변환을 허용하지만, 이를 안전하게 수행할 수 있는 방법을 제공한다.
  • Null 포인터 및 널 참조 : Rust는 Option 및 Result와 같은 열거형 타입을 사용하여 null 포인터를 피하고, 널 참조 오류를 방지한다.
  • 안전하지 않은 동작 : Rust는 안전하지 않은 작업을 엄격하게 제어하여 이러한 종류의 오류를 방지한다.

 

Rust는 여러 특장점 덕분에 차세대 언어로 개발자들 사이에서 점차 주목받고 있습니다.
그러나 1983년 덴마크에서 Bjarne Stroustrup에 의해 발표된 C++는 오랜 기간 동안 사용되며 방대한 레퍼런스와 라이브러리를 축적해왔기에,
2024년 현재까지 Rust가 C++의 입지를 완전히 대체하지 못하고 있는 것이 현실입니다.

개인적으로는 미래에 Rust가 주류 언어로 자리잡아 보다 안전하고 효율적인 개발 환경을 제공할 수 있기를 기대합니다.

 

 테스트 PC 사양

  • CPU : AMD 6800H
  • RAM : Micron DDR5 4800Mhz 16GB X 2
  • 저장장치 : Samsung NVME SSD PM9A1 1TB

 

 Rust와 C++의 속도 비교 결과

 

1. 100만개의 리스트에 100만개의 랜덤 데이터 넣기 

Rust와 C++의 100만개의 리스트에 100만개의 랜덤 데이터 넣는 속도 비교

  • Rust : 63.29초
  • C++ : 170.32초

 

2. 100만개의 데이터 병합 정렬 (Merge Sort)

Rust와 C++의 100만개의 데이터 병합 정렬 속도

  • Rust : 463.89ms
  • C++ : 978.23ms  

 

3. 100만 자리의 피보나치 수열 계산

Rust와 C++의 100만 자리의 피보나치 수열 계산

  • Rust : 67.85초
  • C++ : 20.32초

 

 Rust 소스

 

1. 100만개의 리스트에 100만개의 랜덤 데이터 넣기

use rand::Rng;
use std::time::{Instant};

// 랜덤 데이터를 생성하여 벡터에 저장하는 함수
fn generate_random_data(size: usize) -> Vec<u32> {
    let mut data = Vec::with_capacity(size);
    let mut rng = rand::thread_rng();

    for _ in 0..size {
        // 0부터 99999 사이의 랜덤 값 생성하여 벡터에 추가
        data.push(rng.gen_range(0..100000));
    }

    data
}

// 100만개의 리스트에 대해 100만번의 작업을 수행하는 함수
fn process_list(data_list: &[u32]) {
    for &data in data_list {
        // 여기서 작업을 수행
        // 예시로 각 요소를 출력하는 작업을 수행
        println!("{}", data);
    }
}

fn main() {
    // 시작 시간 측정
    let start_time = Instant::now();

    // 100만개의 랜덤 데이터 생성
    let data_list = generate_random_data(1_000_000);

    // 데이터 처리
    process_list(&data_list);

    // 종료 시간 측정 및 실행 시간 출력
    let end_time = Instant::now();
    let duration = end_time - start_time;
    println!("테스트 시간: {:?}", duration);
}

(rand = 0.9.0-alpha.1)

 

2. 100만개의 데이터 병합 정렬 (Merge Sort)

use std::time::Instant;

fn merge(arr: &mut [i32], temp: &mut [i32], left: usize, mid: usize, right: usize) {
    let mut i = left;
    let mut j = mid + 1;
    let mut k = left;

    while i <= mid && j <= right {
        if arr[i] <= arr[j] {
            temp[k] = arr[i];
            i += 1;
        } else {
            temp[k] = arr[j];
            j += 1;
        }
        k += 1;
    }

    while i <= mid {
        temp[k] = arr[i];
        i += 1;
        k += 1;
    }

    while j <= right {
        temp[k] = arr[j];
        j += 1;
        k += 1;
    }

    for idx in left..=right {
        arr[idx] = temp[idx];
    }
}

fn merge_sort(arr: &mut [i32], temp: &mut [i32], left: usize, right: usize) {
    if left < right {
        let mid = left + (right - left) / 2;

        merge_sort(arr, temp, left, mid);
        merge_sort(arr, temp, mid + 1, right);

        merge(arr, temp, left, mid, right);
    }
}

fn main() {
    let mut arr = vec![0; 1000000];
    let mut temp = vec![0; 1000000];

    // 배열 초기화 (랜덤 또는 원하는 값으로 초기화)

    let start_time = Instant::now();
    let arr_len = arr.len();
    merge_sort(&mut arr, &mut temp, 0, arr_len - 1);
    let duration = start_time.elapsed();

    println!("병합 정렬 실행 시간: {:?}", duration);
}

 

3. 100만개의 리스트에 100만개의 랜덤 데이터 넣기

use std::time::Instant;
use num_bigint::BigInt;
use num_traits::{Zero, One};

fn fibonacci(n: usize) -> BigInt {
    let mut a = BigInt::zero();
    let mut b = BigInt::one();

    for _ in 0..n {
        let tmp = a.clone();
        a = b.clone();
        b += tmp;
    }

    a
}

fn main() {
    let n = 1000000; // 계산하고자 하는 피보나치 수열의 인덱스

    let start_time = Instant::now();
    let result = fibonacci(n);
    let end_time = start_time.elapsed();

    println!("피보나치 수열 계산에 걸린 시간: {:?}", end_time);
}

 

 

 C++ 소스

 

1. 100만개의 리스트에 100만개의 랜덤 데이터 넣기

#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>
#include <chrono>

// 랜덤 데이터를 생성하여 벡터에 저장하는 함수
std::vector<int> generateRandomData(int size) {
    std::vector<int> data;
    // 랜덤 시드 설정
    std::srand(std::time(nullptr));

    for (int i = 0; i < size; ++i) {
        // 0부터 99999 사이의 랜덤 값 생성하여 벡터에 추가
        data.push_back(std::rand() % 100000);
    }

    return data;
}

// 100만개의 리스트에 대해 100만번의 작업을 수행하는 함수
void processList(std::vector<int>& dataList) {
    for (int i = 0; i < dataList.size(); ++i) {
        // 여기서 작업을 수행
        // 예시로 각 요소를 출력하는 작업을 수행
        std::cout << dataList[i] << std::endl;
    }
}

int main() {
    // 시작 시간 측정
    auto start = std::chrono::steady_clock::now();

    // 100만개의 랜덤 데이터 생성
    std::vector<int> dataList = generateRandomData(1000000);

    // 데이터 처리
    processList(dataList);

    // 종료 시간 측정 및 실행 시간 출력
    auto end = std::chrono::steady_clock::now();
    std::chrono::duration<double> elapsedSeconds = end - start;
    std::cout << "테스트 시간: " << elapsedSeconds.count() << " seconds" << std::endl;

    return 0;
}

 

 

2. 100만개의 데이터 병합 정렬 (Merge Sort)

#include <iostream>
#include <vector>
#include <algorithm>
#include <chrono>
#include <random>

void merge(std::vector<int>& arr, std::vector<int>& temp, int left, int mid, int right) {
    int i = left;
    int j = mid + 1;
    int k = left;

    while (i <= mid && j <= right) {
        if (arr[i] <= arr[j]) {
            temp[k] = arr[i];
            i++;
        } else {
            temp[k] = arr[j];
            j++;
        }
        k++;
    }

    while (i <= mid) {
        temp[k] = arr[i];
        i++;
        k++;
    }

    while (j <= right) {
        temp[k] = arr[j];
        j++;
        k++;
    }

    for (int idx = left; idx <= right; idx++) {
        arr[idx] = temp[idx];
    }
}

void mergeSort(std::vector<int>& arr, std::vector<int>& temp, int left, int right) {
    if (left < right) {
        int mid = left + (right - left) / 2;

        mergeSort(arr, temp, left, mid);
        mergeSort(arr, temp, mid + 1, right);

        merge(arr, temp, left, mid, right);
    }
}

int main() {
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> dis(0, 1000000);

    std::vector<int> arr(1000000);
    std::vector<int> temp(1000000);

    // 배열 초기화 (랜덤 또는 원하는 값으로 초기화)
    for (int i = 0; i < 1000000; ++i) {
        arr[i] = dis(gen);
    }

    auto start_time = std::chrono::high_resolution_clock::now();
    mergeSort(arr, temp, 0, arr.size() - 1);
    auto end_time = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);

    std::cout << "병합 정렬 실행 시간: " << duration.count() << " milliseconds" << std::endl;

    return 0;
}

 

 

3. 100만개의 리스트에 100만개의 랜덤 데이터 넣기

#include <iostream>
#include <chrono>
#include <boost/multiprecision/cpp_int.hpp>

namespace mp = boost::multiprecision;
using bigint = mp::cpp_int;
using namespace std::chrono;

bigint fibonacci(int n) {
    bigint a = 0, b = 1;

    for (int i = 0; i < n; ++i) {
        bigint tmp = a;
        a = b;
        b += tmp;
    }

    return a;
}

int main() {
    int n = 1000000; // 계산하고자 하는 피보나치 수열의 인덱스

    auto start_time = high_resolution_clock::now();
    bigint result = fibonacci(n);
    auto end_time = high_resolution_clock::now();

    std::cout << "피보나치 수열 계산에 걸린 시간: " << duration_cast<milliseconds>(end_time - start_time).count() << " ms" << std::endl;

    return 0;
}

 

 

 

 

 마지막으로

실험 결과를 살펴보면, Rust와 C++ 모두 특정 조건하에서 뛰어난 성능을 보이는 사례가 관측되고 있습니다.
상황에 따라 Rust가 더 빠른 결과를 나타내기도 하고, 반대로 C++이 우위를 점하는 경우도 있는데,
이는 단순히 언어 자체의 차이뿐만 아니라, 각 언어의 컴파일러 최적화, 런타임 환경, 그리고 코드 작성 스타일 등 복합적인 요인이 작용한 결과로 판단됩니다.

 

앞으로 시간적 여유가 있을 때, 이러한 성능 차이의 원인을 다각도로 분석해보고자 합니다.
예를 들어, 동일한 알고리즘과 데이터 구조를 Rust와 C++로 구현한 후,
각 컴파일러의 최적화 옵션, 메모리 할당 전략, 그리고 런타임 오버헤드 등을 비교하는 정밀한 실험을 진행하려고 합니다.
또한, 실제 애플리케이션 개발 현장에서 발생하는 다양한 변수들을 고려하여, 어느 조건에서 어떤 언어가 더 유리한지에 대한 실증적 자료를 확보하는 것이 중요하다고 생각됩니다.

본 분석은 단순한 성능 비교를 넘어, 두 언어의 설계 철학과 개발 도구, 그리고 생태계 전반에 걸친 차이점을 이해하는 데 기여할 것으로 기대됩니다.
이를 통해 개발자들이 프로젝트의 특성과 요구 사항에 따라 최적의 언어 선택을 할 수 있도록, 보다 근거 중심의 가이드라인을 마련하는 데 도움이 되고자 합니다.

혹시 글의 내용 중에 잘못된 부분이나 부정확한 정보가 있다면 언제든지 피드백을 주시기 바랍니다.
여러분의 소중한 의견을 반영하여 보다 완성도 높은 결과를 공유할 수 있도록 지속적으로 수정·보완해 나가겠습니다.

반응형

'Dev > Rust' 카테고리의 다른 글

Rust vs Go 차이점  (0) 2025.03.23
Rust와 C/C++의 차이점에 대한 심층 분석  (1) 2025.03.17