Software Prefetch

메모리 최적화의 목적은 수행에 필요한 최소량의 메모리(값)를 가능한 빠르게 전달하는 것이다. 따라서 가능한 cache hits 를 최대화 하기 위해 데이터구조와 메모리버퍼를 주의깊게 다룰 필요가 있다. 데이터가 cache 에 없을 때, 프로세서의 prefetch 기능을 이용하여 memory 의 값을 명시적으로 cache 위로 불러올 수 있다.

prefetch 명령은 프로세서에게 특정 메모리의 값을 곧 사용할테니 미리 cache 에 준비해두라고 지시한다. bus bandwidth 가 사용 가능하다면 프로세서는 지정된 메모리 주소의 데이터를 cache 로 로드해둔다. 실제로 메모리의 값이 사용될 때가 되면, 데이터가 이미 cache 에 있으니 바로 사용가능한 상태가 된다. 또는 적어도 데이터의 처음 부분은 바로 사용가능한 상태가 된다.

prefetch 명령은 어느 cache 로 (미리)로드해둘 것인지에 따라 네가지 타입이 존재한다. 그 중 Non-temporal aligned (NTA) 는 단 한번의 읽기read만 수행할 데이터에 한해서만 사용해야 한다.1) 만약 알고리즘이 prefetch 후에 데이터를 업데이트(read-modify-write) 하거나 한번 이상 접근access할 경우 NTA 를 사용하지 말고 T0 을 사용해야 한다.

아래는 네가지 prefetch 타입을 정리한 표이다.

Assembly Instruction C++ Compiler Intrinsic Type Used as Second Parameter in _mm_prefetch(char *p, int Hint) Description
PREFETCHNTA _MM_HINT_NTA 단 한번 읽기read 를 수행하기 위해 non-temporal buffer 로 로드한다.
PREFETCHT0 _MM_HINT_T0 프로세서의 모든 cache 로 로드한다. 곧이어 바로 데이터를 read/write 할 경우 이게 적합하다.
PREFETCHT1 _MM_HINT_T1 데이터를 L2, L3 cache 에만 로드한다. L1 은 제외
PREFETCHT2 _MM_HINT_T2 데이터를 L3 cache 에만 로드한다. L1, L2 는 제외

prefetch 전략은 데이터가 필요로할 때 이미 cache 에 있을때 최고의 성능을 발휘한다. 즉 이 말은 데이터가 실제 필요로하기 이전에 충분히 미리 prefetch 를 수행해야 함을 뜻한다. 얼마나 미리 수행해야 하는지는 많은 요소들에 따라 달라진다. 그러나 대략 100 clocks 전 쯤이면 적당하다.

loop 에 사용되는 데이터를 prefetching 하는 것은 손쉽게 프로그램하여 좋은 결과를 얻을 수 있는 좋은 예이다. 때때로 최상의 결과를 얻기 위해서는 두번, 네번, 혹은 loop 횟수만큼 prefetch 해야 하는 경우도 있다. prefetch 를 할 최적의 장소와 데이터를 정하기 위해서는 여러번의 시행착오(시도)가 있어야 한다. 그러나 메모리, 메모리 컨트롤러, 그리고 버스 스피드는 (기술의 발전에 따라) 계속 변하기에 prefetch 할 장소 역시 다양해질 수 있다. prefetch 는 cache line 단위로 로드한다. 그래서 지정된 메모리로부터 64 바이트까지 로드되어진다.

너무 잦은 prefetch 사용은 오히려 성능을 저하시킨다.

아래 코드는 16 번의 loop 후에 사용될 데이터를 미리 prefetch 하는 예제이다.

for ( i = 0; i < 1000; ++i )
{
    x = fn( array[ i ] );
    _mm_prefetch( array[ i + 16 ], MM_HINT_T0 );
}

참조

1) read 를 수행한 후 cache 에 저장하지 않고 (더럽히지 않고) 바로 Non-temporal buffer 에서 제거되기 때문이다. 따라서 이후 동일한 데이터에 대하여 다시한번 read/write 가 발생할 경우 cache 로 불려오는 일이 발생한다.