Great Code 1권 – 하드웨어의 이해

출처: [[Great Code]] 1권: 하드웨어의 이해

2장. 수치 표기법

비트 스트링

  • 니블 (Nibble): 4 비트, 1/2 바이트
  • 바이트 (Byte): 8 비트
  • 워드 (Word): 16 비트, 2 바이트
  • 더블워드 (DWord): 32 비트, 4 바이트를
  • 쿼드워드 (QWord): 64 비트, 8 바이트
  • 롱워드 (Long Word): 128비트, 16 바이트

3장. 2진법 연산과 비트 연산

유용한 비트 연산

데이터 묶기와 풀기

묶인 데이터(struct 등)에서는 비트 0위치에 있는 객체의 접근이 가장 효율적이다. 그러므로 가장 자주 접근하게 될 데이터를 비트 0 위치에 배치하는 것이 좋다. 접근 빈도를 짐작하기 어렵다면 각 필드를 바이트 단위로 정렬되게 하는 편이 좋다.

4장. 부동소수점 표기

부동소수점 연산 소개

  • 연산의 순서가 결과의 정확도에 영향을 미칠 수 있다.
  • 같은 부호의 수를 빼거나 다른 부호의 수를 더하면 결과값의 정확도는 부동소수점 포맷에서 지원하는 최대치보다 떨어질 수 있다.
  • 덧셈, 뺄셈, 곱셈, 나눗셈이 섞인 일련의 연산을 할 때는 가급적 곱셈과 나눗셈을 먼저 하라.

        x * (y + z) 
        x * y + x * z // slower, but more accurate
    
  • 곱셈이나 나눗셈을 할 때는 비슷한 크기의 수끼리 연산을 하라.

  • 2개의 부동소수점 수가 같은지 확인할 때는 언제나 두 수의 차이가 일정 허용오차 이내에 있는지 확인해야 한다.

6장. 메모리 구조와 접근

시스템 버스
데이터 버스

데이터 버스의 크기와 범용 정수 레지스터의 크기 중 작은 작은 값이 프로세서의 크기를 결정한다. 예를 들어 요즘 나오는 인텔 80×86 CPU는 모두 64비트 버스를 사용하지만, 32비트 범용 정수 레지스터를 사용하므로 32비트 프로세서에 속한다.

80×86 계열 프로세서는 한번에 버스 크기만큼의 데이터를 처리할 수 있지만, 그보다 작은 8, 16, 32비트 단위로 메모리에 접근할 수도 있다.

주소 버스
제어 버스
  • 읽기, 쓰기 신호
  • 바이트 인에이블(byte enable) 신호 : 바이트 단위의 메모리 접근을 지원하는 CPU에서 사용한다.
  • 메모리 및 I/O 주소 공간 구분 : 80×86 계열 프로세서
메모리의 물리 구조
8비트 데이터 버스

한번에 8비트만 전송 가능하므로 워드 값을 읽으려면 메모리 오퍼레이션을 두 번 해야 하고, 더블 워드를 읽으려면 네 번 해야 한다.

16비트 데이터 버스

8086, 80286 프로세서 등은 메모리를 짝수 뱅크와 홀수 뱅크로 나눠 사용한다.

16비트 80×86 프로세서는 버스에서 언제나 짝수 값만을 사용한다. 만약 홀수 번지에서 시작하는 워드에 접근하면 두 번의 메모리 오퍼레이션이 필요하다. 먼저 CPU는 125번지(예로 든 주소)에서 바이트를 읽어온 후 126번지에서 나머지 바이트를 읽어야 한다. 마지막으로 올바르지 못한 데이터 버스를 통해 두 바이트가 전송됐으므로 내부적으로 2개의 바이트를 스왑해야 한다.

32비트 데이터 버스

메모리 뱅크 4개를 이용한다.

캐쉬 메모리

일반 메모리와는 달리 캐쉬 내부의 메모리 셀은 지정된 주소를 갖지 않는다. 대신 캐쉬 메모리는 동적으로 주소를 할당한다.

\’캐쉬 라인\’: 캐쉬 미스가 일어나면 캐쉬 메모리에는 일정량의 연속적인 메인 메모리 데이터가 들어간다. 80×86의 경우 캐시 미스가 일어나면 16바이트에서 64바이트를 읽어온다.

7장. 혼합 데이터 형과 메모리 객체

레코드를 위한 메모리 공간

80×86에서 인텔 ABI(응용프로그램 바이너리 인터페이스)를 따르는 컴파일러는 1 바이트 객체를 임의의 위치에 필드를 배치할 것이고, 워드는 짝수의 오프셋에, 더블 워드나 좀더 큰 오브젝트는 더블 워드의 오프셋에만 배치할 것이다.

대부분의 컴파일러는 적당한 오프셋 위치에 레코드의 필드를 위치시키는 것 외에도, 전체 레코드의 크기가 2, 4, 8의 배수가 되도록 조절한다. 컴파일러가 패딩 바이트를 넣는 이유는 레코드의 크기가 레코드 안에 있는 가장 큰 스칼라(비 혼합 데이터 타입)의 크기(혹은 그보다 작은 CPU 최적 정렬의 크기)의 짝수배가 되는 것을 보장하기 위해서다. 이렇게 하면 레코드의 배열을 생성했을 때, 배열 안에 있는 모든 레코드가 적당한 주소에서 시작한다.

유니온의 다른 용법

  • 프로그램의 어떤 부분에서 특정 객체를 참조하기 위해 강제로 형을 지정해야 하는 경우.
  • 큰 객체를 바이트별로 나누는 경우. 단, 이식성이 떨어진다(시스템이 사용하는 엔디안에 따라 다른 결과가 나온다).

11장. 메모리 구조와 구성

NUMA와 주변 장치

시스템에서 사용하는 RAM은 대부분 프로세서의 버스와 직접 연결된 고속 DRAM이지만 모든 메모리가 이 같은 방식으로 CPU에 연결돼 있는 것은 아니다.

게임 프로그래머들은 주메모리에 스크린 데이터를 복사하고 조작한 후에, 수직 다시 그리기가 다시 발생할 때마다(1초에 약 60번) 해당 데이터를 비디오 디스플레이 메모리로 복사하는 것이 훨씬 빠르다든 사실을 이미 오래 전부터 알았다.

캐쉬나 가상 메모리 서브시스템은 사용자의 간섭 없이 자동으로 동작하지만, NUMA 메모리는 그렇지 않다. 따라서 NUMA 장치에 데이터를 쓰는 프로그램은 접근 횟수를 가능한 최소화해야 한다. 즉 플래쉬 메모리 카드 같은 NUMA 장치에 데이터를 읽고 쓰는 경우에는 별도로 직접 데이터를 캐쉬해야 한다.

메모리 계층을 고려한 소프트웨어 작성

  • 사용하는 모든 변수를 공용 코드에 모아서 선언하라. 이렇게 하면 대부분의 프로그래밍 언어에서는 이들 변수를 물리적으로 인접한 메모리 영역에 할당한다.
  • 지역 변수는 프로시저 내부에 선언하라. 대부분의 프로그래밍 언어에서는 지역 변수를 스택 영역에 할당하는데 시스템은 스택을 자주 참조하므로 스택에 있는 변수들은 캐쉬에 있는 확률이 높기 때문이다.
  • 일반 변수는 배열이나 레코드 변수와는 따로 모아서 선언하라. 이 변수 중 하나에 접근하면 시스템은 자동으로 인접한 객체를 모두 캐쉬에 적재할 것이다. 따라서 한 변수에 접근할 때마다 시스템은 인접한 변수를 캐쉬에 적재한다.

힙 영역과 동적 메모리 할당

메모리 할당
최초발견 알고리즘과 최적발견 알고리즘
  • 최초발견 알고리즘이 빠르다.
  • \’외부 단편화\'(external fragmentation)가 덜하다. 최적발견 알고리즘을 사용해서 한동안 메모리 할당과 반환 요청을 처리하고 나면, 크기가 작은 메모리 블록이 많이 생긴다.
힙의 메모리 오버헤드
할당 정보를 저장하는 방식
  • 별도의 내부 테이블에 저장하기
    • 실수로 블록에 저장된 정보를 겹쳐 쓰는 일이 거의 발생하지 않는다.
    • 메모리 관리 정보를 내부 데이터 구조에 저장하면 메모리 관리자는 어떤 포인터가 유효한 포인터인지 알지 못한다.
  • 할당하는 블록에 직접 저장하기
할당 정밀도

대부분의 힙 관리자는 메모리 블록을 4, 8, 16바이트 단위로 할당한다. 더 높은 성능을 위해 일반적인 캐쉬 라인의 크기에 맞춰 16, 32, 64바이트 단위로 메모리를 할당하는 관리자도 많다.

내부 단편화

힙 관리자가 블록의 크기를 정밀도의 배수로 맞춰 할당하기 위해 더 많은 메모리를 할당하다 보면, 내부 단편화가 발생한다.

12장. 입력과 출력

입출력 메커니즘

메모리맵 입출력
  • 장점: CPU가 메모리에 수행하는 명령(예. mov)을 입출력에도 그대로 사용가능하다.
  • 단점: 주기억장치의 메모리 공간을 차지한다. 비디오 카드와 같이 큰 주소 영역을 차지하는 장치의 경우에 문제가 되기도 한다.
  • 주의: 캐쉬를 사용하면 안 된다. 이는 CPU의 가상 메모리 시스템을 이용해 해결 가능하다. 예를 들어, 80×86 페이지 테이블에는 CPU가 각 페이지를 캐쉬할 것인지 설정하는 항목이 있다.
I/O맵 입출력

80×86 프로세서가 I/O맵 영역과 주기억장치 영역에 접근할 때 사용하는 물리적인 주소 버스와 데이터 버스는 같다. 제어신호만 바꾸면 I/O맵 입출력을 메모리맵 입출력으로 바꿀 수 있다.

직접 메모리 접근 (DMA)
프로그램 I/O

CPU가 개입하는 입출력 방식을 말한다.

  • 메모리맵 입출력
  • I/O맵 입출력
Direct Memory Access

CPU 도움 없이 주변 장치가 직접 메모리에 접근한다. 그러나 CPU와 주변장치가 동시에 주소 버스나 데이터 버스에 접근할 순 없다. 설사 CPU가 DMA 작업이 끝날 때까지 연산을 멈추고 기다리더라도 DMA쪽이 더 빠르다. 프로그램 I/O는 부하가 많기 때문이다.

대용량 장치의 데이터를 조작하는 소프트웨어 작성

파일 접근 속도
  • 디스크 드라이브에서는 순차 접근이 상대적으로 효율적이다.
  • 한번에 큰 블록의 데이터를 읽고 쓰라.
    • OS 호출은 빠르지 않다.
    • OS는 블록 단위로 읽고 쓴다.
    • 파일 단편화 가능성을 줄인다.

최 재훈

블로그, 페이스북, 트위터 고성능 서버 엔진, 데이터베이스, 지속적인 통합 등 다양한 주제에 관심이 많다.
Close Menu