소프트 웨어의 성능은 어떤 알고리즘을 썼느냐도 중요하지만 어떻게 RAM을 잘 활용했는지도 영향을 받는다.

메모리가 성능에 영향을 끼치는 형태는 다음 두가지가 있다.


* 동적 메모리 할당은 느리다. 동적 메모리할당을 피하거나, 할당비용을 줄일 수 있는 메모리 할당자를 직접 만들어야 한다.


* 메모리를 효율적으로 배치해되 있을수록 속도가 빠르다.


1. 동적 메모리 할당 최적화

 동적 메모리 할당이 느린 이유

  * 힙 할당자는 범용 목적이기 때문에 이렇게 하면 관리하는 부가적인 비용이 들기 때문이다.

  * malloc(), free()를 호출할 때 대부분 운영체제 에서는 먼저 유저 모드에서 커널 모드로 컨텍스트 전환을 하고, 필요한 동작을 수행한 후 다시 프로그램으로 컨텍스트 전환을 해야한다.


 사용자 제작 할당자가 빠른 점

  * 미리 할당된 블록을 이용할 수 있다. 즉, 유저모드에서만 처리하기 때문에 컨텍스트 스위칭이 일어나지 않는다.

  * 사용 패턴을 예측할 수 있기 때문에 범용 힙 할당자에 비해 훨씬 효율적으로 동작한다.


2. 커스텀 할당자의 종류

  ⓐ 스택 할당자 - 데이터를 쌓아가며 할당하고 그러므로 해제할땐 순서대로 해제해야 한다. 해제 시 해제하고 할당 된만큼 꼭대기의 주소를 내려줘야 한다.


  ⓑ 풀 할당자 - 개별 원소들의 크기에 정확히 배수가 되는 큰 메모리 블록을 할당하는 것이다. 처음에는 모든 풀의 원소들이 사용 가능 리스트에 들어가 있다가 할당 요청이 오면 제일 처음 원소를 가져와서 리턴한다.


  ⓒ 정렬된 할당자 - 실제 요청된 것보다 조금 큰 메모리를 할당하고 블록의 주소를 살짝 위로 조정해서 정렬을 맞춘 다음 조정된 주소를 리턴한다.


  ⓓ 단일 프레임 할당자 - 메모리를 해제하지 않고 한 프레임 마다 메모리 블록의 가장 아래로 꼭대기 포인터를 초기화 한다. 프레임 진행 중 할당이 일어나면 블록의 위방향으로 진행한다.


3. 메모리 단편화

 - 메모리가 충분히 있음에도 연속되 있지 않아서 할당에 실패한다.

해결 방안

 ⓐ 스택할당자와 풀할당자를 사용한다.

   - 이 두 할당자는 메모리 단편화를 겪지 않는다.

 ⓑ 조각 모음과 재배치

   - 할당된 메모리들을 메모리 구멍을을 메워가며 앞으로 당긴다.

   - 하지만 메모리를 가리키고 있는 포인터가 있을 경우 문제가 생길 수 있으니 메모리들을 옮기면서 이 과정 또한 다시 재배치 해주어야 한다.

    (스마트 포인터가 스스로 재배치를 하게 하거나 핸들을 사용한다.)

   - 조각 모음이 한 프레임에 모든 블록을 옮긴다면 속도가 굉장히 느릴 것이다 그러므로 한프레임당 8개나 16개 정도 갯수를 조정하는 것이 좋다.


4. 캐시 일관성

 시스템이 메인 RAM에 접근하는 일은 시작부터 끝까지 항상 수천 프로세스 주기가 걸리기 때문에 언제나 느리다.

 대게 수십주기, 어떤 경우는 한주기만에 끝나기도 하는 CPU 레지스터 접근과 비교하면 엄청난 차이다.

 메인 RAM에서 읽고 쓰는 평균적인 비용을 줄이기 위해 오늘날의 프로세서는 고성능 메모리 캐시를 이용한다.

 캐시는 특수한 형태의 메모리로 CPI가 읽고 쓰는 속도가 메인 RAM 보다 훨씬 빠르다.

 메모리 캐싱의 기본적 원리는 메인 RAM의 영역을 처음 읽을 때, 작은 메모리 조각을 고성능 캐시에 읽어 들이는 것이다.

 이 메모리 조각을 캐시라인이라고 하고 보통 8바이트에서 512바이트 사이의 값이다.

 요청한 데이터가 캐시에 있을 땐 캐시에서 바로 읽어 들이고 그렇지 않으면 메인 RAM에 접근한다. 이 경우를 캐시 미스라고 한다.

 캐시 미스가 발생하면 캐시라인을 다시 읽어 들일 때 까지 프로그램은 기다려야 한다.

 RAM에 데이터를 쓰거나 읽어야 하기 때문에 캐시 미스를 완전히 피할 수는 없다.


  ⓐ 명령어 캐시(instruction I 캐시) 데이터(Data D 캐시) 캐시

    - 명령어 캐시는 실행 파일의 명령어 코드가 실행되기 전에 미리 불러오는데 쓰인다.

    - 데이터 캐시는 데이터를 메인 RAM에 읽고 쓰는 속도를 빠르게 하는 데 쓰인다.


  ⓑ 데이터 캐시 미스를 피하는 가장 좋은 방법은 데이터를 잘개 쪼개어 연속적인 블록으로 배치하고 순서대로 접근하는 것이다.


  ⓒ 명령어 캐시 미스를 피하는 방법은 코드의 메모리에서의 모양은 데이터와 링커에 달려 있다 그래서 다음위 규칙을 이용하면 좋다.

    - 함수 하나를 나타내는 기계어 코드는 항상 메모리에서 연속적이다.

      (인라인 함수는 예외)

    - 번역 단위의 소스코드에 나오는 순서대로 함수들이 메모리에 배열된다.

    - 같은 번역 단위 안에 있는 함수들은 거의 대부분 메모리에서 연속적으로 놓인다.

      ( 링커는 여러 번역 단위를 컴파일한 결과물들을 서로 섞어서 놓지 않는다.)

  

  * 성능이 중요한 코드는 가능한 한 기계어 명령어 수가 적어야 한다.

  * 성능에 결정적인 영향을 끼치는 코드에서는 함수 호출을 피애햐 한다.

  * 함수를 반드시 호출해야 하는 경우에는 가능한 현재 함수와 가까운 곳에 배치해야 한다. 다른 번역 단위에 있는 함수를 호출하면 안된다.( 거리를 보장할 수 없기 때문)

  * 인라인 함수를 사용할 때는 조심해야 한다. 인라인 함수는 코드의 크기를 크게 할하기 때문에 결정적 영향을 끼치는 코드가 캐시에 한번에 다 못 들어 가는 경우가 생길 수 있다.



posted by 알쿠미