간단한 캐시 메모리 활용 최적화 |
| programming/general 2002/10/14 22:03 |
특별하게 어떤 장치를 해야 되는 것은 아니지만 조금만 고려해주면 괜찮은 효과를 얻을 수도(^^) 있는 팁입니다. (크리스 해커 아저씨의 글을 번역해서 옮길까 했지만, 허락 받고 옮기기가 번거로울 거 같아서 그냥 제 맘대로 작성합니다. 아래 링크 참고…)
캐시메모리는 간단하게 CPU와 RAM(메인 메모리)의 다리 역할을 하는 부분이라고 할 수 있습니다. 메인 메모리는 억세스 속도가 매우(!) 느리지만, CPU는 캐시를 통해서 메모리를 빠르게 억세스 하도록 관리하고 있습니다. (한번 load 된 부분은 나중에 flush 될 때까지 메인 메모리를 대신해서 메모리처리를 해주고, 억세스 하려는 내용이 캐시에 없을 때는 메인 메모리에서 일정 블록만큼 load 합니다.) - 자세한건 검색엔진에서 검색해보세요. (불친절... ^^)
효과적으로 어플리케이션의 옵티마이징을 하려면 메인 메모리의 억세스를 줄이는 것, 즉 캐시메모리를 활용하는 것이 중요하다고 하겠습니다. (몰론 훨씬 더 빠른 레지스터 레벨까지 활용하면 좋겠지만 … 인간이 하기엔 약간 무리가 있죠. ^^ )
캐시 메모리를 활용하는 방법은 많겠습니다만, 가장 쉽게 할 수 있는 것은 억세스 할 내용이 분산되지 않도록 만드는 것입니다. 캐시 블록의 개수는 제한되어 있기 때문에… (캐시 메모리는 속도만큼 가격이 비싸서 일반적으로 8k나 32k 정도만 사용합니다.) 가능한 읽을 것들이 이전에 읽은 블록 안에 있는 것이 좋습니다. 그래서 인덱싱이나 간접 포인터 보다는 리니어하게 데이터를 배치하는 것이, 캐시메모리 히트율을 높일 수 있는 방법이라고 할 수 있겠습니다. (물론 인덱싱하는 것도 잘 배치하면 효율을 높일 수 있겠죠.)
그 차이를 경험할 수 있도록 간단하게 예제를 하나 작성했습니다. (소스의 정밀성은 무시하시고 의미만 살펴봐주세요. ^^) 제 시스템에서 테스트해본 결과는 아래와 같습니다.
func1(18) : linear
func2(68) : random
두 함수는 같은 기능을 하는 함수입니다만, func1은 연속해서 메모리를 억세스 하고 있고, func2는 큰 간격으로 메모리를 억세스하고 있습니다. 수치상 비율에 의미를 두긴 힘들지만 확실하게 속도차이는 난다고 할 수 있겠습니다. (전자가 후자보다 RAM에서 캐시메모리로 읽어 들이는 횟수가 훨씬 적다고 할 수 있겠습니다.)
데이터 구조를 잡을 때 잘만 적용하면 난해하거나 복잡한 알고리즘 없이도 괜찮은 효과를 얻으실 수 있을 겁니다.
셈플소스
참고문서 : http://www.d6.com/users/checker/pdfs/gdmmem.pdf
ps. 당연한 얘기지만 최적화는 효과가 있을 만한 부분에서만... ( 전체적으로 해줘도 좋습니다만 노력에 비해 효과가… T_T ) 비중에 따라선 비 효율적인 코드도 별 문제가 안 되니, 너무 꼼꼼하게 프로그래머 구박하지 않으셔도... ^^
캐시메모리는 간단하게 CPU와 RAM(메인 메모리)의 다리 역할을 하는 부분이라고 할 수 있습니다. 메인 메모리는 억세스 속도가 매우(!) 느리지만, CPU는 캐시를 통해서 메모리를 빠르게 억세스 하도록 관리하고 있습니다. (한번 load 된 부분은 나중에 flush 될 때까지 메인 메모리를 대신해서 메모리처리를 해주고, 억세스 하려는 내용이 캐시에 없을 때는 메인 메모리에서 일정 블록만큼 load 합니다.) - 자세한건 검색엔진에서 검색해보세요. (불친절... ^^)
효과적으로 어플리케이션의 옵티마이징을 하려면 메인 메모리의 억세스를 줄이는 것, 즉 캐시메모리를 활용하는 것이 중요하다고 하겠습니다. (몰론 훨씬 더 빠른 레지스터 레벨까지 활용하면 좋겠지만 … 인간이 하기엔 약간 무리가 있죠. ^^ )
캐시 메모리를 활용하는 방법은 많겠습니다만, 가장 쉽게 할 수 있는 것은 억세스 할 내용이 분산되지 않도록 만드는 것입니다. 캐시 블록의 개수는 제한되어 있기 때문에… (캐시 메모리는 속도만큼 가격이 비싸서 일반적으로 8k나 32k 정도만 사용합니다.) 가능한 읽을 것들이 이전에 읽은 블록 안에 있는 것이 좋습니다. 그래서 인덱싱이나 간접 포인터 보다는 리니어하게 데이터를 배치하는 것이, 캐시메모리 히트율을 높일 수 있는 방법이라고 할 수 있겠습니다. (물론 인덱싱하는 것도 잘 배치하면 효율을 높일 수 있겠죠.)
그 차이를 경험할 수 있도록 간단하게 예제를 하나 작성했습니다. (소스의 정밀성은 무시하시고 의미만 살펴봐주세요. ^^) 제 시스템에서 테스트해본 결과는 아래와 같습니다.
func1(18) : linear
func2(68) : random
두 함수는 같은 기능을 하는 함수입니다만, func1은 연속해서 메모리를 억세스 하고 있고, func2는 큰 간격으로 메모리를 억세스하고 있습니다. 수치상 비율에 의미를 두긴 힘들지만 확실하게 속도차이는 난다고 할 수 있겠습니다. (전자가 후자보다 RAM에서 캐시메모리로 읽어 들이는 횟수가 훨씬 적다고 할 수 있겠습니다.)
데이터 구조를 잡을 때 잘만 적용하면 난해하거나 복잡한 알고리즘 없이도 괜찮은 효과를 얻으실 수 있을 겁니다.
셈플소스
| #include <stdio.h> #include <windows.h> #include <mmsystem.h> char * buffer; int func1() { int i, j, time; time = timeGetTime(); for(i=0; i<65536; i++) for(j=0; j<16; j++) buffer[i*16 + j] = 0; return timeGetTime() - time; } int func2() { int i, j, time; time = timeGetTime(); for(j=0; j<16; j++) for(i=0; i<65536; i++) buffer[i*16 + j] = 0; return timeGetTime() - time; } void clearcache() { char * dummy = new char [65536*16]; for(int i=0; i<65536*16; i++) dummy[i] = i; delete [] dummy; } void main() { int t[2] = { 0, 0 }; buffer = new char [65536*16]; clearcache(); t[0] += func1(); clearcache(); t[1] += func2(); delete [] buffer; printf("func1(%d) : linear nfunc2(%d) : random n", t[0], t[1]); } |
참고문서 : http://www.d6.com/users/checker/pdfs/gdmmem.pdf
ps. 당연한 얘기지만 최적화는 효과가 있을 만한 부분에서만... ( 전체적으로 해줘도 좋습니다만 노력에 비해 효과가… T_T ) 비중에 따라선 비 효율적인 코드도 별 문제가 안 되니, 너무 꼼꼼하게 프로그래머 구박하지 않으셔도... ^^
댓글을 달아 주세요
상당히 이쁜(?)Tip이군요. 감사.
그런 경이롭 위치를 위해 많게의 감사!
중대하고 유용한 위치!