5. 메모리 레이턴시 메모리에서 원하는 데이터를 가져오고 쓰는데 걸리는 시간 캐시가 그토록중요한 이유도 긴 메모리 레이턴시를 조금이나마 극복하고자 함이었다 메모리 대역폭 단위 시간당 메모리에서 가져올 수 있는 데이터의 양 메모리 로드/스토어 인자로 받은 주소 값을 구해 데이터를 읽거나 쓰기
6. 비순차 프로세서에서 명령어가 완료(commit, retirement)된다라는 의미 반드시 자신이 비투기적인(nonspeculative)상태임 반드시 확실하게 판명이 난 분기문의 흐름에 있어야 한다. 언제든지 분기 예측 실패로 무효화될 수 있다 임시 결과 값을 ROB(리오더버퍼)그리고 혹은 다른 버 퍼 에 가지고 있다. 마지막으로 완료되어야 비로소 실제 메모리를 읽고 쓴다.
7. 메모리 로드/스토어의 비순차실행 실행 순서는 메모리 의존성 명령어 해독만으로 파악할 수 없고 반드시 주소 값을 구해야 한다 레지스터 값을 읽고 거기에 오프셋까지 더해야 한다 실제 연산을 거쳐야만 알 수 있다 실제 메모리 로드와 스토어의 주소 값을 알아야만 의존성을 알 수 있다
8. 컨트롤 의존성 해결의 가장 쉬운 방법 그냥 비순차 실행을 포기하면 정확하게 명령을 실행할수 있다. 일반적인 x86 프로그램은 코드 중 38%가 메모리 로드와 스토어이다. 순차적으로만 실행해야 한다면 성능에 치명적이다. 얻어낼 수 있는 ILP(명령어 수준 병렬성, Instruction Level Parallelism))가 크게 줄어든다.
10. 효율적인 메모리 연산의 첫 번째 목표 메모리 로드와 스토어 중 메모리 의존성 이 확실히 없는 것에 대해 비순차적 실행을 허용하는 것이다 메모리 의존성 같은 메모리주소에 대해 현재 수행 중인 메모리 스토어가 모두 완료되어야 그 주소에 대한 뒤따르는 메모리 로드를 할 수 있음
11. 로드스토어 큐(Load Store Queue,LSQ) 현재 수행 중인 메모리로드와 스토어의 내용을 간직하여 메모리 의존성을 검사한다. 메모리 의존성이 없다는 것이 명확해지면 비순차수행이 가능해진다. 메모리 명확화(memory disambiguation) 문제를 푸는장치다
12. LSQ의 대략적인 형태 엔트리마다순번 (새로운 명령어일수록 큰 값) 로드/스토어 여부 PC 데이터 주소 데이터 값 부가 정보
15. 메모리 로드는 최대한 앞당겨 실행시키는 것이 중요하다 캐시 미스가 있더라도 손해를 감출 수 있다 예측기 여러 과거 정보를 조합해 기억한 뒤 결과를 예측했다
16. 메모리 의존성 예측기가 잘 작동할 수 있는 이유 어떤 메모리 로드가 자신보다 앞선 메모리 스토어와 충돌을 일으킨 전력이 없다면 앞으로도 그러지 않을 것이라는 관찰에 근거한다 충돌을 많이 일으켰다면 앞으로도 그럴 것이라는 기본적인 지역성을 기초 어떤 로드와 이보다 앞선 가까운 스토어는 서로 다른 메모리 주소를 가리 키는 것이 흔하다 같은 주소에 값을 쓰고 바로 읽는 코드는 흔하지 않다.
17. 예) 인텔 Core 마이크로아키텍처(Core 2 Duo 및 그 후속 프로세서의 기본 구조)에 적용된 메모리 의존성 예측기의작동 방식 분기 예측기나 캐시처럼 PC 주소를 입력받는해시테이블 구조다 PC 주소와 혹은 히스토리 정보를 조합해 이 예측기의엔트리에접근한다. 각 엔트리는카운터값을가지고 있다. 키운터에는 한계 값이 있어서 어느 수준 이상으로는 늘지 않는다 (salurating counter) 엔트리는 메모리 로드가 완료되는 시점에 갱신한다.
18. 규칙 만일 로드가 잘 작동했다면 즉 주소가 밝혀지지 않은 스토어와 충돌하지 않았다면 카운터 값이 증가한다 만일 로드가 적어도 하나라도 앞선 스토어와 충돌을 일으켰다면 카운터를 0으로 초기화한다. 단,이 스토어는 로드보다 코드상으로는 앞에 있지만 실제로는 이 로드 이후에 실행이 시작된 것만 해당한다.
19. 메모리 의존성 예측기 로드 명 령 어 가 실행되기 시작할 때(Reservation Station에서 실행 장치로 할당되었을 때) 사용된다. 로드에 해당하는 예측기엔트리의 카운터 값이 최대라면 `안전하다`라고 예측하고 LSQ 엔트리 에 기록한다
20. 1999년에 출시된 알파 21264 마이크로프로세서 에 이미 구현되어 있다 Core 구조는 기본적 인 메모리 의존성 예측기에 감시자(Watchdog) 기능을 더 했다. 적중률이 어느 수준 이하로 낮아지면 잠깐 예측 자체를 중단한다.
21. LSQ 기능 메모리 명확화 문제와 Store-to-Load 포워딩 문제를 풀어 비순차프로세서에서 효율적 인 메모리 연산 실행을 가능케 한다 메모리 연산에도 예측과 투기적 실행을 더 해 메모리 로드를 최대한 미 리 수행해 명령어 완료 시간을 단축시킨다
23. 포인터 분석(pointer analysis) 또는앨리어스분석(alias analysis) 두 포인터가 서로 겹치는 일이 있는지 없는지를 따지는 문제 C/C++ 프로그래머는 막연하게 포인터를 많이 쓰면 그것이 곧 최적화라는 착각한다.
24. 컴파일러도 이 문제로 최적화가 어렵다 최적화의 기본은 명령어 사이의 의존성을 분석하는 일이다. 포인터가 끼어들면 의존성 분석 이 어려워 고급최적화가 어려워진다
25. 큰 프로그램의 포인터 최적화 문제 NP-Hard라는 영역 에 속한 문제 포인터 변수가 미구함수 넘어서 (interprocedural) 불릴 수 있고 포인터 의 포인터를 생각하면 가리 킬 수 있는 대상이 기하급수적으로 늘어난다. undecidable문제 대상을 가리 킬 수 있는 포인터와 동적 메모리 할당이 있으면 정확한 포인터 분석은 아예 풀 수 없다.
26. 이상적 인 포인터 분석은 두 포인터가 반드시 겹치거나 혹은 겹치지 않음을 밝힐 수 있을 것이다 현실은 대부분이 ‘ 겹칠지도 모른다’ 로끝난다 컴파일러는 대부분 보수적인 최적화만 수행하므로 결국 포인터가 들어간 코드는 최적화를 많이 못하는 경우가 잦다.
27. C 언어 restrict 키워드를 C99에 도입 병렬 가능함을 알고 루프 풀기를 하거나 아니면 SIMD 연산으로 대체하는 자동 벡터화(automatic vectorization) 를 할 수도 있다
28. 자동 병렬화(automatic parallelization) 포인터가 없는 포트란 기반코드에서는 꽤 잘 되지만 C/C++에서는 간단한 코드조차 포인터 문제로 실패한다 인텔 컴파일러와 포틀랜드(portland) 컴파일러 가 C/C++ 코드에 대해 자동병렬화를 지원한다.
29. 동적 할당이 아닌 정적으로 할당한다. 자동 병렬화 최근에는 동적 프로파일링정보를 이용해 자동병렬화 성공률을 높일 수 있다는 연구 결과도 있다 C++컴파일러는 restrict를 지원하지 않는다.
30. 결론 메모리 명령 메모리 의존성이다. LSQ 명령을 기록해가며 메모리 의존성을 추적했다 예측하여 안전하다고 판명된 로드를 미 리 투기적으로 수행하기도 했다 포인터는 컴파일러 고급 최적화에 큰 걸림돌이다
34. 반드시 최초 한 번은 데이터를 메모리에서 가져올 때 생기는 캐시 콜드 미스 현상 캐시 콜드 미스 캐시 라인 크기를 크게 해서 어느 정도 줄일 수 있었다 최소화할 수 있는 다른 방법 중 하나가 프리펫칭기법이다. 어떤 데이터 가 쓰이는 것을 예측하고 미리 데이터를 가져와서 긴 레이턴시를 감추는 것이다. 캐시 미스가 일어나지만 그 지연 시간을 감추는 것이다.
35. 슈퍼펫치(SuperFetch) 윈도우 운영체제 사용자가 자주 쓰는 프로그램을 기억해 미리 디스크에서 읽어 메모리에 올려놓는 기술 단점 하드디스크를 쉬는 시간에도 읽어 불편함 예측이 틀릴 수도 있다 대역폭 낭비 최악의 경우 성능을 하락 유발 가능성이 있음
38. 소프트웨어 프리펫칭 프로그래머나 컴파일러가 직접적인 ISA가 정의하는 명령어를 이용한다. x86에서는 PREFETCHO I고,이에 대응되는 _mm_prefetch함수다 메모리 로드와 상당히 흡사하다
39. 프리펫칭할주소 값을 인자로 주면 캐시에 데이터를 읽어넣는다 어떤 캐시 (예를 들어 L1 혹은 L2)로 읽을지도 정할 수 있다 페이지 폴트 같은 예외를 만들지 않는다 예외가 발생해도 무시한다. 프로그램 알고리즘에 영향을 주변 안되기 때문이다 루프를 풀어 프리펫정 오버헤드를 최소한으로 줄인다. 너무 큰 루프를 많이 풀면 코드 크기가 커져 캐시 미스를 유발할 수 있다
40. 기본적인 캐시 정보는 알아야한다. 프리펫칭을 정확하게 하려면 그 코드가 어떤 캐시 미스 형태를 만들어낼지 미리 예측해야만 한다. 명령어가 추가로 들어가므로 오버헤드가 발생 보통 프리펫칭 요구는 일반적인 메모리 요구와 달리 우선순위가 낮게 책정된다
41. 최적의 프리펫칭거리 명시적으로 프리펫칭 메모리 레이턴시를 감출 수 있을 만큼 미리 수행되어야 한다 메모리 레이턴시와 루프 순환의 길이를 고려해야 한다. 실수값 올림으로 정수값으로 변경 약간 더 빠르게 프리펫칭을 요구 분기문이 있어 수행 사이클이 가변 적일 때는 최적 프리펫칭 거리를 구하는 것이 더 어려워진다.
43. 포인터 기반 자료구조 Recursive Data Structure 또는 Linked Data Structure라고 표현하기도 한다. 연속된 메모리 에 있지 않다.
44. 로드 의존성(load-to-load dependence) 더욱이 앞 로드의 결과가 다음 로드의 인자로 사용되어 모두 순차적으로 실행되어야 하므로 오버헤드가 크다 포인터 추적(pointer chasing) 코드 포인터가 서로 꼬리를 물며 접근하는 코드 프리펫칭하기 어려운 대표적인 부류에 속한다.
46. 그리디(greedy) 프리펫칭 어떤 노드가 있다면 그 노드에 인접한 모든 노드를 미리 읽는 것이다 비록 완벽한 프리펫칭은안되더라도 메모리 레이턴시(=캐시 미스 패널티)를 조금이라도 줄일 수 있다. 프리펫칭 거리를 조절하기는 어렵지만 구현이 쉽고 어 떠한 포인터 기반 자료구조에도 적용이 가능하다. 프리펫칭자체로 인한 오버헤드가 적다.
55. 하드웨어가 메인 메모리와 캐시 사이에서 일어나는 행동을 관찰한다 이러한 정보를 이용해 미래에 일어날 일을 예측한다.
56. 캐시 라인(블록) 크기가 64바이트처럼 큰 이유는 공간적 지역성 (spatial locality)을 살리기 위해서인데, 이는 인접한 데이터를 프리펫칭하는 것과 같다. 캐시 오염의 위험도 커 진다. 가짜 공유(false sharing) : Story 20 최신 x86 고성능 프로세서는 10개에 가까운 다양한 종류의 하드웨어 프리펫처가 각 캐시마다 붙어 작동한다.
58. 인접한 캐시 라인을 프리펫칭 마치 캐시라인 크기를 두 배 늘리는 것과 유사 명령어가 분기만 하지 않는다면 항상 순차적으로 명령어 캐시를 접근하면 명령어 투입 속도를 높일 수 있다. 너무 커지면 캐시 오염이 심각해 진다. 해결할 수 있는 방법 중 FIFO 큐 형태의 스트림버퍼(stream buffer)를 이용하는 방법 이 있다 스트림 버퍼에 저장한다 내용이 접근되면 비로서 캐시로 옮겨 진다 바로 인접한 데이터 에 자주 접근하는 지역성이 부족하다면, 특히 포인터 기반 자료구조라면 스트림 버퍼로 인한 프리펫칭 효과 는 기대할 수 없다.
59. 거리가 일정한 데이터 스트라이드(stride) 프리펫칭 스트라이드는 두 주소의 차이를 가리 폭이 일정한 메모리 접근을 한다면 그 정보를 배워 프리멧칭에 쓴다. for-k 루프에서 행 같은 행에서 접근되므로 캐시 적중률이 매우 높다 열 만일 N 이 큰 값이라면 매 원소에 접근할 때마다 캐시 미스를 일으킨다 따라서 이 때 프리펫칭할수 있으면 효과가 커 진다
60. 개선하려 면 각 메모리 로드의 스트라이드 값을 기 억 해 프리멧칭하면 현 재 주소 + (스트라이드*프리멧칭거리 가장 최근의 두 데 이 터 주소 값의 차이만 PC(Program Counter) 정보를 활용한다 모든 PC에 대한 메모리 접근 내 역을 기록할 수 없기에 간단한 해싱 기 법 으로 처리
62. 마르코프(Markov) 모델을 이용한 프리펫칭 알고리즘 이중 캐시 미스를 일으키는 메모리 주소의 스트림을관찰해 캐시 미스가 정보로 활용된다. “주소 X가 미스가 났는데 다음에는 어 떤 주소가 캐시 미스날까? 식으로 접근 포인터 기반 자료구조라 해도 잘 동작함
63. 캐시 같은 테이블이 필요 데이터 주소 값에 해당하는 인덱스를 찾아 뒤따르는 미스 주소를 기록하면 된다 캐시 미스 스트림을 볼 때마다 적절히 테이블을 갱신하면 된다 테이블의 엔트리와일치하면 그 엔트리가 가지고 있는 예측 주소 값 집합에 대해 프리펫칭을 실시 확률 근사 값에 따라 프리펫칭 우선순위를 조절 주소들의 관계를 기억하여 프리펫칭정확도를 높이는 것이다. 반드시 학습 (training) 단계, 즉 테이블을 채우는 단계가 필요하다. 이 시점 에 발생하는 캐시 미스를 잡기는 어렵다.
65. 직접적으로 데이터 `내용(content)` 을 읽어 이 값이 포인터 같으면 나중에 사용된다는 기대를 가지고 프리펫칭하는 알고리즘 포인터 기반 자료구조를 보면 포인터 값을 읽고 읽은 내용은 다음 로드할주소가 된다. 따라서 만일 하드웨어가 어떤 데이터 의 내용을 엿봐서 유효한 가상 메모리 주소가 같으면 프리펫칭을 하는 아이디어가 먹힐 수 있을 것이다
66. 도대체 어떤 값이어야 데이터 주소가 같다고 판단할 수 있을까? 같은 자료구조 내에 있는 원소들은 비슷한 시기에 동적으로 할당 받는다. 보통 대부분 운영 체제의 힙 관리자는 이럴 때 포인터 주소의 상위 영역이 상당 부분 겹친다. 겹치면 포인터로 추측할 수 있다. 예) Node 구조체의 left, right와 가까운 거 리에 있을 확률이 높다 학습 시간이 필요치 않아 포인터 기반 자료구조가 많이 쓰이는 코드에서는 좋은 성능을 보인다
67. 히스토리 포인터처럼 순회한 포인터를 캐싱해프리펫칭에활용라는방법도 었다 헬퍼스레드(helper therad)라는 기법 아예 프로그램의 일부를 앞서 실행시켜 캐시 미스가 미 리 처리 되게 끔 한다.
68. 특정 시나리오에서는 아주 크게 효과가 나와서 40%까지 개선된다고 하지만 일반적으로는 4~5% 정도 나아진다 싱글코어가 아닌 여러 코어가 경쟁적으로 프리펫칭한다면분명 메모리 대여폭을 갉아먹고 성능 저하로 이어질 수가 있어 적절한 제어가 필요하다
69. 결론 프리펫칭 기법 긴 메모리 레이턴시를 조금이라도 줄이고 감춰보자는 취지 에 서 나온 알고리즘이다. 필요할 것 같은 데이터를 적절한 타이밍에 레지스터나 캐시에 미리 불러 놓는 기술이다