컴파일러, 메모리 패딩 및 저장공간

컴파일러와 메모리 구조

컴파일러란?

컴파일러는 C 코드를 컴퓨터가 이해할 수 있는 기계어로 변환해주는 주체입니다. 이 과정에서 C 코드는 실행 파일로 만들어지며, CPU가 이를 읽고 연산하여 작동할 수 있습니다.

컴파일러는 표준이 정해져 있지 않으며, 각 컴파일러의 개발자에 따라 다르게 구현될 수 있습니다. 따라서, C 코드를 입력하면 실행 가능한 파일을 출력하는 것이 컴파일러의 기본적인 역할입니다.

주요 컴파일러

C와 C++에서 사용되는 대표적인 컴파일러는 다음과 같습니다:

  • MSVC: Microsoft Visual Studio에 내장되어 있습니다.
  • GCC: 주로 리눅스에서 사용되며, 학습용으로 많이 활용됩니다.
  • Clang: 표준을 가장 잘 준수하는 편입니다.

각 컴파일러마다 특색이 있으며, 예를 들어 MSVC는 비표준 기능이 많고, Clang은 경고를 엄격하게 제공합니다.

컴파일러의 한계

프로젝트가 커지면 컴파일러를 변경하는 것이 큰 허들이 될 수 있습니다. 예를 들어, MSVC는 C99의 VLA(Variable Length Array)를 지원하지 않기 때문에, 이와 같은 문법을 사용할 경우 컴파일 에러가 발생할 수 있습니다.

메모리 구조와 패딩

C 구조체는 가장 큰 데이터형을 기준으로 정렬됩니다. 예를 들어, CPU가 2바이트 데이터에 접근하기 위해서는 주소가 2바이트 단위로 정렬되어 있어야 성능이 최적화됩니다. 이 과정에서 빈 공간이 생기는 것을 패딩이라고 합니다.

struct b {
    int b;
    char c;
    // char empty[3]; // 패딩을 위한 공간
};

struct f { // sizeof 8
    char c; // 0번째 오프셋
    char d; // 1번째 오프셋
    int b;  // 2번째 오프셋
};

struct g { // sizeof 12
    char d; // 0번째 오프셋
    int b;  // 1번째 오프셋
    char c; // 2번째 오프셋
};

SIMD와 메모리 접근

SIMD(Single Instruction, Multiple Data)는 한 번의 명령으로 여러 개의 계산을 수행하는 기술입니다. 이 명령어는 메모리가 16바이트 정렬되어 있어야 하며, 정렬이 되어 있지 않은 메모리를 입력으로 사용할 경우 크래시가 발생할 수 있습니다.

Array (Vector) vs List

Array

  • 정의: 한정된 갯수의 값이 확실하게 알 수 있을 때 사용합니다.
  • 특징: 데이터가 메모리상에 연속적으로 저장되어 있어 메모리 접근 속도가 빠릅니다.
  • 메모리 할당: 주로 스택에 할당되며, 가끔 힙 할당을 사용합니다.
  • 중간 삭제: 중간 삭제가 느릴 수 있지만, swap and pop_back 기법을 사용하여 마지막 원소와 교환 후 삭제함으로써 성능을 개선할 수 있습니다.

List

  • 정의: 원소 접근 및 순회가 용이한 데이터 구조입니다.
  • 특징: 데이터가 메모리상에 흩어져 있어 응집도가 떨어지며, 각 노드는 앞뒤 포인터를 가지고 있어 메모리 효율이 떨어질 수 있습니다.
  • 메모리 할당: 항상 힙에 할당되며, 노드마다 16바이트의 메모리를 사용합니다.

성능 비교

  • Array는 List에 비해 대부분의 케이스에서 성능이 우수합니다.
  • 중간 삭제가 자주 발생하는 경우, 삭제 후 정렬을 통해 데이터의 응집도를 높이는 것이 좋습니다. 정렬은 반드시 크기순일 필요는 없으며, qsort와 같은 정렬 함수가 기본적으로 제공됩니다.

결론

Array와 List는 각각의 장단점이 있으며, 사용 목적에 따라 적절한 데이터 구조를 선택하는 것이 중요합니다. 데이터 응집도와 메모리 효율성을 고려하여 최적의 선택을 하세요.

저장공간

SSD와 HDD

  • SSD (Solid State Drive): 빠른 데이터 접근 속도를 제공하는 저장 장치입니다.
  • HDD (Hard Disk Drive): 기계적인 부품을 사용하여 데이터를 저장하는 전통적인 저장 장치입니다.
  • 보조기억장치: PC가 꺼져도 데이터가 남아있는 장치입니다.

메모리

  • RAM (Random Access Memory): PC가 꺼지면 데이터가 사라지는 휘발성 메모리입니다.

캐시 메모리

  • 캐시 메모리: CPU가 가지고 있는 고속 메모리로, 데이터 접근 속도를 높이는 역할을 합니다.

메모리 접근 속도

  • 속도 순서: 캐시 메모리 »»»» 메모리 »»»» 보조기억장치
  • 캐시 메모리를 접근하는 것이 가장 효율적입니다.

프로세스 생성 시 메모리 할당

프로세스가 생성될 때 모든 공간은 메모리에 할당됩니다. CPU가 메모리에 접근할 때, 가장 먼저 캐시 메모리에 이 데이터가 있는지 확인합니다.

  • 캐시 미스: 데이터가 캐시 메모리에 없으면, CPU가 포인터로 메모리에 접근하여 읽은 값을 캐시 메모리에 저장합니다.
  • 캐시 히트: 데이터가 캐시 메모리에 있으면, 캐시 메모리에 접근하여 데이터를 가져옵니다. 이때, 데이터를 뭉태기로 가져오며, 기본적으로 캐시 라인은 64바이트로 가정합니다. 즉, 캐시가 메모리를 긁어갈 때 64바이트씩 가져간다고 볼 수 있습니다.

캐시 메모리는 실제 메모리보다 훨씬 작기 때문에, CPU 입장에서 메모리 접근이 광범위한 것은 성능에 손해가 될 수 있습니다.

int main() {
    // 2D 배열을 선언합니다.
    int arr[1000][1000];

    // 첫 번째 접근 방식: 행 우선 순회
    // 이 방식은 메모리에서 연속적으로 저장된 데이터를 효율적으로 접근합니다.
    for (int i = 0; i < 1000; i++) {
        for (int j = 0; j < 1000; j++) {
            arr[i][j] = 10; // 각 요소에 10을 할당합니다.
        }
    }

    // 두 번째 접근 방식: 열 우선 순회
    // 이 방식은 메모리 접근 패턴이 비효율적입니다.
    for (int j = 0; j < 1000; j++) {
        for (int i = 0; i < 1000; i++) {
            arr[i][j] = 10; // 각 요소에 10을 할당합니다.
        }
    }

    return 0;
}

설명

  • 위 코드는 1000x1000 크기의 2D 배열을 선언하고, 두 가지 접근 방식을 사용하여 배열의 모든 요소에 10을 할당합니다.
  • 첫 번째 접근 방식은 행 우선 순회로, 메모리에서 연속적으로 저장된 데이터를 효율적으로 접근합니다. 이는 캐시 메모리의 활용을 극대화하여 성능을 향상시킵니다.
  • 두 번째 접근 방식은 열 우선 순회로, 메모리 접근 패턴이 비효율적입니다. 이 경우 캐시 미스가 발생할 가능성이 높아져 성능이 저하될 수 있습니다.

이 예제는 메모리 접근 방식이 성능에 미치는 영향을 이해하는 데 도움이 됩니다.

결론

이 문서는 컴파일러, 메모리 구조, 자료구조에 대한 기초적인 내용을 다루었습니다. 이러한 기초 지식은 시스템 프로그래밍, 컴퓨터 구조, 운영체제 등의 분야에서 중요한 역할을 합니다.

results matching ""

    No results matching ""