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

프로그래머가 몰랐던 멀티코어 CPU 이야기 - 15, 16장

  • 1.
    프로그래머가 몰랐던멀티코어 CPU 이야기유유- 유장혁
  • 2.
  • 3.
    병목의 원인긴 메모리레이턴시와 부족한 메모리 대역폭캐시 말고도 여러 가지 방법으로 메모리 레이턴시를줄이려고 노력한다해결 방법효율적인 메모리 로드와 스토어의 처리
  • 4.
  • 5.
    메모리 레이턴시메모리에서 원하는데이터를 가져오고 쓰는데 걸리는 시간캐시가 그토록중요한 이유도 긴 메모리 레이턴시를 조금이나마 극복하고자 함이었다메모리 대역폭단위 시간당 메모리에서 가져올 수 있는 데이터의 양메모리 로드/스토어인자로 받은 주소 값을 구해 데이터를 읽거나 쓰기
  • 6.
    비순차 프로세서에서 명령어가완료(commit, retirement)된다라는 의미반드시 자신이 비투기적인(nonspeculative)상태임반드시 확실하게 판명이 난 분기문의 흐름에 있어야 한다.언제든지 분기 예측 실패로 무효화될 수 있다임시 결과 값을 ROB(리오더버퍼)그리고 혹은 다른 버 퍼 에 가지고 있다.마지막으로 완료되어야 비로소 실제 메모리를 읽고 쓴다.
  • 7.
    메모리 로드/스토어의 비순차실행실행순서는 메모리 의존성명령어 해독만으로 파악할 수 없고 반드시 주소 값을 구해야 한다레지스터 값을 읽고 거기에 오프셋까지 더해야 한다실제 연산을 거쳐야만 알 수 있다실제 메모리 로드와 스토어의 주소 값을 알아야만 의존성을 알 수 있다
  • 8.
    컨트롤 의존성 해결의가장 쉬운 방법그냥 비순차 실행을 포기하면 정확하게 명령을 실행할수 있다.일반적인 x86 프로그램은 코드 중 38%가 메모리 로드와 스토어이다.순차적으로만 실행해야 한다면 성능에 치명적이다. 얻어낼 수 있는 ILP(명령어 수준 병렬성, Instruction Level Parallelism))가 크게 줄어든다.
  • 9.
  • 10.
    효율적인 메모리 연산의첫 번째 목표메모리 로드와 스토어 중 메모리 의존성 이 확실히 없는 것에 대해 비순차적 실행을 허용하는 것이다메모리 의존성같은 메모리주소에 대해 현재 수행 중인 메모리 스토어가 모두 완료되어야 그 주소에 대한 뒤따르는 메모리 로드를 할 수 있음
  • 11.
    로드스토어 큐(Load StoreQueue,LSQ)현재 수행 중인 메모리로드와 스토어의 내용을 간직하여 메모리 의존성을 검사한다.메모리 의존성이 없다는 것이 명확해지면 비순차수행이 가능해진다.메모리 명확화(memory disambiguation) 문제를 푸는장치다
  • 12.
    LSQ의 대략적인 형태엔트리마다순번(새로운 명령어일수록 큰 값)로드/스토어 여부PC데이터 주소데이터 값부가 정보
  • 13.
    Store-to-Load Forwarding데이터 캐시가아니라 바로 LSQ에서 값을 얻어오는 과정
  • 14.
    메모리 의존성 예측기와또 다른 투기적 실행
  • 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 포워딩 문제를 풀어 비순차프로세서에서 효율적 인 메모리 연산 실행을 가능케 한다메모리 연산에도 예측과 투기적 실행을 더 해 메모리 로드를 최대한 미 리 수행해 명령어 완료 시간을 단축시킨다
  • 22.
  • 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명령을기록해가며 메모리 의존성을 추적했다예측하여 안전하다고 판명된 로드를 미 리 투기적으로 수행하기도 했다포인터는 컴파일러 고급 최적화에 큰 걸림돌이다
  • 31.
    Story 16.메모리 레이턴시감추기 : 프리펫처
  • 32.
    프리펫칭(Prefetching)어떤 데이터가 미래에 사용됨을 예측하고 미리 가져와 메모리 레이턴시를줄이는 아이디어메모리 레이턴시를감추는 셈소프트웨어와 하드웨어에 모두 적용
  • 33.
  • 34.
    반드시 최초 한번은 데이터를 메모리에서 가져올 때 생기는 캐시 콜드 미스 현상캐시 콜드 미스캐시 라인 크기를 크게 해서 어느 정도 줄일 수 있었다최소화할 수 있는 다른 방법 중 하나가 프리펫칭기법이다. 어떤 데이터 가 쓰이는 것을 예측하고 미리 데이터를 가져와서 긴 레이턴시를 감추는 것이다.캐시 미스가 일어나지만 그 지연 시간을 감추는 것이다.
  • 35.
    슈퍼펫치(SuperFetch)윈도우 운영체제사용자가 자주쓰는 프로그램을 기억해 미리 디스크에서 읽어 메모리에 올려놓는 기술단점하드디스크를 쉬는 시간에도 읽어 불편함예측이 틀릴 수도 있다대역폭 낭비최악의 경우 성능을 하락 유발 가능성이 있음
  • 36.
    캐시 오염(cache pollution)과도한프리펫칭으로 쓸데 없이 캐시를 낭비하는 것전력을 더 소비한다
  • 37.
  • 38.
    소프트웨어 프리펫칭프로그래머나 컴파일러가직접적인 ISA가 정의하는 명령어를 이용한다.x86에서는 PREFETCHO I고,이에 대응되는 _mm_prefetch함수다메모리 로드와 상당히 흡사하다
  • 39.
    프리펫칭할주소 값을 인자로주면 캐시에 데이터를 읽어넣는다어떤 캐시 (예를 들어 L1 혹은 L2)로 읽을지도 정할 수 있다페이지 폴트 같은 예외를 만들지 않는다예외가 발생해도 무시한다.프로그램 알고리즘에 영향을 주변 안되기 때문이다루프를 풀어 프리펫정 오버헤드를 최소한으로 줄인다.너무 큰 루프를 많이 풀면 코드 크기가 커져 캐시 미스를 유발할 수 있다
  • 40.
    기본적인 캐시 정보는알아야한다.프리펫칭을 정확하게 하려면 그 코드가 어떤 캐시 미스 형태를 만들어낼지 미리 예측해야만 한다.명령어가 추가로 들어가므로 오버헤드가 발생보통 프리펫칭 요구는 일반적인 메모리 요구와 달리 우선순위가 낮게 책정된다
  • 41.
    최적의 프리펫칭거리명시적으로 프리펫칭메모리레이턴시를 감출 수 있을 만큼 미리 수행되어야 한다메모리 레이턴시와 루프 순환의 길이를 고려해야 한다.실수값 올림으로 정수값으로 변경약간 더 빠르게 프리펫칭을 요구분기문이 있어 수행 사이클이 가변 적일 때는 최적 프리펫칭 거리를 구하는 것이 더 어려워진다.
  • 42.
    포인터 기반 가료구조의소프트웨어 프리펫칭
  • 43.
    포인터 기반 자료구조RecursiveData Structure 또는 Linked Data Structure라고 표현하기도 한다.연속된 메모리 에 있지 않다.
  • 44.
    로드 의존성(load-to-load dependence)더욱이앞 로드의 결과가 다음 로드의 인자로 사용되어 모두 순차적으로 실행되어야 하므로 오버헤드가 크다포인터 추적(pointer chasing) 코드포인터가 서로 꼬리를 물며 접근하는 코드프리펫칭하기 어려운 대표적인 부류에 속한다.
  • 45.
  • 46.
    그리디(greedy) 프리펫칭어떤 노드가있다면 그 노드에 인접한 모든 노드를 미리 읽는 것이다비록 완벽한 프리펫칭은안되더라도 메모리 레이턴시(=캐시 미스 패널티)를 조금이라도 줄일 수 있다.프리펫칭 거리를 조절하기는 어렵지만 구현이 쉽고 어 떠한 포인터 기반 자료구조에도 적용이 가능하다.프리펫칭자체로 인한 오버헤드가 적다.
  • 47.
  • 48.
    히스토리포인터추가로 기억하는 포인터일종의점프 포인터순회 내력을 기억해서 프리펫칭 거리만큼 떨어져 있는 노드를서로 잇는다.주어진 자료구조가 자주 변경되지 않아야 하며,같은 패턴으로 순회가 자주 되어야만 할 것이다
  • 49.
    자료구조가 최초로 순회될때 갱신된다.히스토리포인터를 처음 만드는 시점에서는 그리디 프리펫칭을쓰면 된다.포인터 공간이 더 필요하며 히스토리포인터를 관리하는 오버헤드가 든다
  • 50.
  • 51.
    데이터선형화한정적이지만 극단적으로 포인터기반자료구조의 프리펫칭비용을 연속적인 메모리를 쓰는 배열처럼 만드는 것이다.
  • 52.
    일반적으로 매우 위험하고비용이 크다.메모리를 가리키는 포인터를 모두 알아내 바꿔야 하기 때문이다처음 자료구조를 만들 때 순회 순서를 미리 알 수 있다면 그 순서 에 맞춰 자료구조를 메모리에 잘 배치하면 이 기법의 활용이 가능하다.
  • 53.
    소프트웨어 프리펫칭최적은 어렵지만규칙적이고 예측이 가능한 범위 내에서는 컴파일러도 훌륭하게 일을 할 수 있다단점코드가 커진다캐시 미스를 예측하기가 어렵다.캐시 라인과 캐시 크기는 프로세서마다 다르다캐시가 여러 코어에 의해 공유된다
  • 54.
  • 55.
    하드웨어가 메인 메모리와캐시 사이에서 일어나는 행동을 관찰한다이러한 정보를 이용해 미래에 일어날 일을 예측한다.
  • 56.
    캐시 라인(블록) 크기가64바이트처럼 큰 이유는 공간적 지역성 (spatial locality)을 살리기 위해서인데, 이는 인접한 데이터를 프리펫칭하는 것과 같다.캐시 오염의 위험도 커 진다.가짜 공유(false sharing) : Story 20최신 x86 고성능 프로세서는 10개에 가까운 다양한 종류의 하드웨어 프리펫처가 각 캐시마다 붙어 작동한다.
  • 57.
  • 58.
    인접한 캐시 라인을프리펫칭마치 캐시라인 크기를 두 배 늘리는 것과 유사명령어가 분기만 하지 않는다면 항상 순차적으로 명령어 캐시를 접근하면 명령어 투입 속도를 높일 수 있다.너무 커지면 캐시 오염이 심각해 진다.해결할 수 있는 방법 중 FIFO 큐 형태의 스트림버퍼(stream buffer)를 이용하는 방법 이 있다스트림 버퍼에 저장한다 내용이 접근되면 비로서 캐시로 옮겨 진다바로 인접한 데이터 에 자주 접근하는 지역성이 부족하다면, 특히 포인터 기반 자료구조라면 스트림 버퍼로 인한 프리펫칭 효과 는 기대할 수 없다.
  • 59.
    거리가 일정한 데이터스트라이드(stride) 프리펫칭스트라이드는 두 주소의 차이를 가리폭이 일정한 메모리 접근을 한다면 그 정보를 배워 프리멧칭에 쓴다.for-k 루프에서행같은 행에서 접근되므로 캐시 적중률이 매우 높다열만일 N 이 큰 값이라면 매 원소에 접근할 때마다 캐시 미스를 일으킨다따라서 이 때 프리펫칭할수 있으면 효과가 커 진다
  • 60.
    개선하려 면 각메모리 로드의 스트라이드 값을 기 억 해 프리멧칭하면현재 주소 + (스트라이드*프리멧칭거리가장 최근의 두 데 이 터 주소 값의 차이만PC(Program Counter) 정보를 활용한다모든 PC에 대한 메모리 접근 내 역을 기록할 수 없기에간단한 해싱 기 법 으로 처리
  • 61.
  • 62.
    마르코프(Markov) 모델을 이용한프리펫칭 알고리즘이중 캐시 미스를 일으키는 메모리 주소의 스트림을관찰해캐시 미스가 정보로 활용된다.“주소 X가 미스가 났는데 다음에는 어 떤 주소가 캐시 미스날까? 식으로 접근포인터 기반 자료구조라 해도 잘 동작함
  • 63.
    캐시 같은 테이블이필요 데이터 주소 값에 해당하는 인덱스를 찾아 뒤따르는 미스 주소를 기록하면 된다캐시 미스 스트림을 볼 때마다 적절히 테이블을 갱신하면 된다테이블의 엔트리와일치하면 그 엔트리가 가지고 있는 예측 주소 값 집합에 대해 프리펫칭을 실시확률 근사 값에 따라 프리펫칭 우선순위를 조절 주소들의 관계를 기억하여 프리펫칭정확도를 높이는 것이다.반드시 학습 (training) 단계, 즉 테이블을 채우는 단계가 필요하다. 이 시점 에 발생하는 캐시 미스를 잡기는 어렵다.
  • 64.
  • 65.
    직접적으로 데이터 `내용(content)`을 읽어 이 값이 포인터 같으면 나중에 사용된다는 기대를 가지고 프리펫칭하는 알고리즘포인터 기반 자료구조를 보면 포인터 값을 읽고 읽은 내용은 다음 로드할주소가 된다.따라서 만일 하드웨어가 어떤 데이터 의 내용을 엿봐서 유효한 가상 메모리 주소가 같으면 프리펫칭을 하는 아이디어가 먹힐 수 있을 것이다
  • 66.
    도대체 어떤 값이어야데이터 주소가 같다고 판단할 수 있을까?같은 자료구조 내에 있는 원소들은 비슷한 시기에 동적으로 할당 받는다.보통 대부분 운영 체제의 힙 관리자는 이럴 때 포인터 주소의 상위 영역이 상당 부분 겹친다.겹치면 포인터로 추측할 수 있다.예) Node 구조체의 left, right와 가까운 거 리에 있을 확률이 높다학습 시간이 필요치 않아 포인터 기반 자료구조가 많이 쓰이는 코드에서는 좋은 성능을 보인다
  • 67.
    히스토리 포인터처럼 순회한포인터를 캐싱해프리펫칭에활용라는방법도 었다헬퍼스레드(helper therad)라는 기법아예 프로그램의 일부를 앞서 실행시켜 캐시 미스가 미 리 처리 되게 끔 한다.
  • 68.
    특정 시나리오에서는 아주크게 효과가 나와서 40%까지 개선된다고 하지만 일반적으로는 4~5% 정도 나아진다싱글코어가 아닌 여러 코어가 경쟁적으로 프리펫칭한다면분명 메모리 대여폭을 갉아먹고 성능 저하로 이어질 수가 있어 적절한 제어가 필요하다
  • 69.
    결론프리펫칭 기법긴 메모리레이턴시를 조금이라도 줄이고 감춰보자는 취지 에 서 나온 알고리즘이다. 필요할 것 같은 데이터를 적절한 타이밍에 레지스터나 캐시에 미리 불러 놓는 기술이다