최적화 도중에 난제를 만났습니다.(지역성 관련)
글쓴이: Samuro / 작성시간: 금, 2016/04/29 - 12:20오후
while(likely(i<count)){ map[randome_index()]++; i++; }
현재 count는 약 150만번 정도, 맵의 전체 크기는 800만 정도이고 unsigned char 타입이니 800만 바이트정도 됩니다.
여기서 0~800만 사이의 랜덤 넘버가 발생해서 랜덤 넘버를 인덱스로 맵에 접근해서 값을 1씩 증가시켜 줍니다.
제가 만들고 있는 프로그램의 가장 큰 성능요인이 이부분인데, 맵의 인덱스를 0부터 순차적으로 접근할때와 비교해서
정확히 측정이 안될정도로 엄청난 성능 감소가 발생합니다. random넘버를 생성하는 함수는 더이상 건드릴 수 없습니다.
도대체 어떻게 해야 지역성을 살릴 수 있을까요?
Forums:
이러면 어떨까요? 먼저 150만개에 대한 random
이러면 어떨까요?
먼저 150만개에 대한 random 숫자를 미리 계산해 놓고 그 후에 map[] 연산을 하는 것으로요.
감사합니다.. 제가 한가지 놓치고 있었네요.
나름 지역성 신경쓰면서 짠다고 생각했는데, 크게 반성했습니다. 저는 랜덤 인덱스를 모아서 맵 array를 순차적으로 접근하는 방법만 생각했는데, 랜덤 인덱스를 받아오는 작업때문에 맵에 대한 지역성이 깨지는걸 생각못했었네요. 덕분에 크게 개선되었습니다. 저부분에서 소모하는 시간이 거의 반으로 줄어들었어요. 정말 감사합니다!
다행이네요. 만약 실시간 random 연산 결과가
다행이네요. 만약 실시간 random 연산 결과가 필요하지 않다면 먼저 random() 배열을 한 100개 정도 set을 만들어 놓고 실제 맵 계산에는 그 중에 하나를 불러서 쓸수도 있습니다.
적용가능한 상황인지는 모르겠습니다만, 라스코니님
적용가능한 상황인지는 모르겠습니다만,
라스코니님 코드에서 temp를 sort한 후에 map에 적용하는 방법은 사용할 수 없는 건가요? 적용가능하다면 말씁하신 map에 순차적으로 접근하게 되는 걸로 보입니다.
간단히 테스트해보았는데, 소트 오버헤드가 temp가
간단히 테스트해보았는데, 소트 오버헤드가 temp가 정렬되었을 때 얻는 이득보다 너무 크기 때문에 오히려 가장 성능이 떨어지네요.
저도 고려해봤던 부분인데
100만개가 넘는 숫자의 정렬 오버헤드가 굉장히 클거같아서 제외했던 방법인데 직접 테스트 해주셨네요 감사합니다 ㅎㅎ. 현재는 라스코니님 방법대로 사용중입니다.
공부가 되네요. 감사합니다.
공부가 되네요. 감사합니다.
심심해서 잠깐 샘플 프로그램을 만들어봤는데 재밌는
심심해서 잠깐 샘플 프로그램을 만들어봤는데 재밌는 결과가 나왔네요.
이미 사용중이실지도 모르겠지만.. 최적화 옵션도 함께 사용해 보세요. 정말 빨라 집니다. :)
without mapper
with map[er
---------------------------------
제일 왼쪽이 저입니다 :)
저도 놀랐습니다.
생각보다 굉장히 빨라져서 놀랐습니다ㅎㅎ. 루프를 도는 횟수를 줄이는게 좋다고만 생각했었는데 루프를 늘리더라도 지역성을 살리는게 중요하네요
저도 심심해서 잠깐 돌려 봤는데, 결과가 다르네요.
제가 코드를 아무리 봐도 with_mapper 코드가 왜 지역성이 더 좋은지 이해가 안되네요.
혹시 설명해 주실수 있으세요.
그리고 without_mapp와 with_mapper 코드를 실행하면,
매번 실행 할 때 마다 조금씩 다른 결과가 나오긴 하지만, 의미 있는 결과는 아니네요.
시스템의 차이나, 컴파일러의 차이라고 보기도 어렵고....
cpu정보 - 24 core
CPU는 꾸준히 클럭을 높이는 것에 투자를 했지만 곧
CPU는 꾸준히 클럭을 높이는 것에 투자를 했지만 곧 성능상 한계에 도달했습니다. 그래서 소프트웨어 실행 성능을 가능한 높이기 위해서 나온게 파이프라인 처리, 캐쉬 확장, 분기 예측 처리 등입니다.
결론적으로 본다면 CPU는 캐쉬 등과 결합해서 가장 최근에 했던 일을 다시 할 때 (시스템 지연을 최소화하는 과정을 통해) 제일 빨리 수행이 됩니다.
제일 최악은 하드 디스크에서 다른 코드/데이터를 로드해서 불러들이는 상황입니다. 이것은 1차 캐쉬, 2차 캐쉬에서도 못찾고 심지어 램에 로딩조차 안되었을 때 발생합니다.
어느 정도 코드가 완성되고 마지막 단계에서 최적화를 할 때 이런 지역성(locality)을 고려하면 좋은 효과를 볼수 있는 경우가 많습니다.
저는 gcc-4.8 쓰는 Ubuntu-14.04
저는 gcc-4.8 쓰는 Ubuntu-14.04 quad-core 환경에서 해 봤는데,
최적화옵션 없이 컴파일하면 아래와 같이 나오고
gcc -O3 옵션으로 빌드하면 더 눈에 띄는 차이가 납니다.
이렇게 실행해보면 차이가 나기는 하는데,
이런 차이가 지역성이 높아지기 때문이라는 것은 저도 이해가 안 됩니다.
with_mapper는 랜덤인덱스 임시저장용으로 정수 1500만개짜리 메모리가 추가할당되고
이 메모리에 순차쓰기/순차읽기 동작이 추가되었을 뿐
without_mapper와 마찬가지로 정수 5000만개의 메모리에 대한 랜덤 접근은 그대로 유지되고 있는데
어떤 면에서 지역성이 높아지고 성능이 좋아지는지 감을 못잡겠네요.
with, without 코드에서 눈에 보이는
with, without 코드에서 눈에 보이는 차이는
와
와 같이 똑같은 일을 하는 루프를 하나로 하느냐 여러개로 나누느냐의 차이입니다.
얼핏 아래의 코드(케이스 (2))가 복잡해 보이나 실제 수행시간은 더 짧은 이유는
(가정컨대) 케이스 (1)에서는 rand() 함수가 호출되면서 결과적으로 현재 사용되어지고 있는 캐시 영역에서 벗어날 경우가 발생한다는 것입니다.
이 상황이 매번 1500만번 반복됨으로써 캐시가 재사용될 수 있는 가능성을 최악으로 가져가고 있습니다.
케이스 (2)에서는 루프를 두번으로 나누고 심지어 첫번째 루프에서 arr_ran[i]에 방금 만들어진 랜덤값을 임시 보관하는 잉여짓(?)을 하고 있음에도 불구하고 전체적인 속도가 더 빠른 것은 두번째 루프에서 (캐시 hit이 발생할 확률이 많은) 비슷한 영역을 액세스하도록 구조화하였기 때문이라고 생각됩니다.
예를 들어 케이스 (1), (2)에서 arr_buf[random]++;, arr_buf[arr_ran[i]]++; 부분을 제거하고 실행하면 오히려 케이스 (1)이 좀더 빨리 실행될 것이라고 생각됩니다. 케이스 (2)가 arr_ran[i]에 방금 만들어진 랜덤값을 임시 보관하는 잉여짓(?)을 하고 있기 때문입니다.
설명 고맙습니다. 지금도 잘 이해는 못하고 있는
설명 고맙습니다.
지금도 잘 이해는 못하고 있는 상태입니다만..
(1) 그 자체로서 CPU 캐시 활용에 유리한 코드
(2) 컴파일러가 최적화하기에 유리한 코드
이렇게 두 가지가 머리속에서 뒤섞이는 느낌입니다.
with_mapper는 (2)에 해당하는 코드임은 확실한 것 같은데
(1)에도 해당되는지는 실감하지 못한다고 하면 비슷할 것 같네요.
(1)과 (2)가 어차피 같은 얘기인가 싶기도 하고,
시간을 두고 더 생각해봐야겠습니다.
음 ..
with_mapper 의 경우 without 에 비해 cache miss 가 상당히 적게 나고 있던데..
이게 결과에 큰 영향을 미치고 있는게 아닐까 싶네요.
특히 with_mapper 의 arr_buf[arr_ran[i]]++; 부분에서 cache miss 가 없는게...;;;
되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』
헉.. valgrind로 cache miss까지 볼수
헉.. valgrind로 cache miss까지 볼수 있군요. 또 좋은 것 배웠습니다. 감사합니다.
동감입니다. 저도 좋은것 배워갑니다. 글 보면서 엄청
동감입니다. 저도 좋은것 배워갑니다. 글 보면서 엄청 놀랬네요. ㄷ ㄷ ㄷ
감사합니다. :)
---------------------------------
제일 왼쪽이 저입니다 :)
arr_buf[arr_ran[i]]++; 캐시 미스가 없는게 이해가 안되네요.
arr_buf[arr_ran[i]]++; 이 라인에서 어째서 캐시 미스가 없는 걸까요?
arr_ran 배열에는 50,000,000 범위내의 숫자가 15,000,000개 들어 있고,
arr_ran 배열을 i값에 따라 순차 접근하기 때문에 캐시 미스가 적은 것(없을수가 있나요?)은 이해가 됩니다.
그러나 만일 arr_ran[1]=1이고, arr_ran[2]=50,000,000이라면, arr_buf[1]++, arr_buf[50,000,000]++ 이렇게 연산을 할텐데,
이때는 캐시 미스 발생 할 거라고 예상 됩니다.
즉 arr_ran[] 값이 정말 랜덤하다면, arr_buf[] 접근은 캐시 미스가 발생 할거라고 예상 되는데, 왜 캐시 미시가 없다고 나올까요?
혹시 램덤 값이 덜 램덤하거나, 캐쉬 라인 범위내에서 랜덤 한 걸까요?
음 ..
저도 그 부분이 아직 이해가 안 가고 있습니다만.. 최적화 옵션에 무슨 트릭이 있는 것 같네요.
일단, with_mapper 를 최적화 옵션 없이 빌드해서 보면, without 에 비해 80~90% 정도의 시간이 소요되는데..
전체 D reference 횟수가 363M 에서 612M 정도로 증가하고, 해당 라인에서 cache miss 가 충분히 많이 발생하네요.
그러고 보니, -O3 로 빌드한 경우에 with 와 without 의 D refs 횟수가 비슷한 걸로 봐서..
오히려 그 루프는 호출 되지 않고 다르게 처리된 게 아닐까 싶은데.. 어셈 쪽은 까막눈이라..;;
되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』
지역성을 고려해서 코딩하기가....
참 어려운듯해요. 나름 고민을 해서 알고리즘을 구현했은데, -O3 한방에 예상치 못한 결과가 나오기도하고, 가끔은 -O3에서 paaaaanic !도 발생하고....
이럴때는 어셈을 해야 되나 싶기도 하고.... C만으로를 부족할때로 있는듯 하고...
10G 트래픽을 처리하려면, 초당 14.88M PPS의 속도로 연산을 해야 하는데, 지역성을 고려해서 코딩해보려해도 원하는대로 잘 동작을 안하더라구요....
어째든 다시 한번 최적화/지역성을 중요성을 알고 갑니다...
이렇게 캐시관련 분석을 하는 방법이 있군요. 많이
이렇게 캐시관련 분석을 하는 방법이 있군요.
많이 배웁니다 ^^
-O3 옵션으로 최적화된 경우 with_mapper의 캐시미스 비율이 뚝 떨어지면서
실행시간도 크게 단축되는 모습이 뚜렷이 드러나네요.
컴파일러가 어떤 식으로 캐시미스를 줄인 것인지는 모릅니다만
어쨌든 이 경우의 성능차이는 캐시미스 차이 덕분이라는 실감이 듭니다.
그런데, 최적화하지 않고 그냥 컴파일한 경우에는 아래와 같이
명령어캐시와 데이터캐시, L3 캐시 모두에서 총 레퍼런스 횟수 및 캐시미스 횟수가
with_mapper쪽이 without_mapper보다 더 크게 나옵니다.
캐시미스 비율만 봐서는 서로 옥신각신하는 모습이지만
총 레퍼런스 횟수와 캐시미스 횟수는 읽기든 쓰기든 with_mapper 쪽이 더 높습니다.
결국 with_mapper 실행시 더 많은 메모리참조가 일어나고 캐시미스도 더 많이 발생한다는 뜻 같은데
막상 실행시간을 재보면 with_mapper가 without_mapper의 2/3 정도밖에 안 걸립니다.
이 경우엔 캐시미스가 아닌 다른 요인이 실행시간을 가름하고 있는 것 아닐까요?
gcc에 -O3 옵션을 주면 캐시미스 비율이 뚝
gcc에 -O3 옵션을 주면 with_mapper의 캐시미스 비율이 뚝 떨어지는 이유는
최적화 과정에서 과감한 코드생략이 일어났기 때문인 것 같습니다.
arr_buf에 대한 calloc()도 생략되고,
arr_buf의 값들을 증가시키는 두 번째 for 루프도 통째로 생략되어
마치 arr_buf 관련된 코드는 존재하지 않는 것처럼 컴파일되네요.
하지만 main()의 마지막 부분을 아래와 같이 수정해서 arr_buf의 쓰임새를 만들면
이런 과감한 생략이 불가능해지고 컴파일 결과도 달라집니다.
실행시간도 without_mapper는 수정전과 거의 같은 0.71s 수준이지만,
with_mapper는 0.190s --> 0.415s 수준으로 올라갑니다.
valgrind, cg_annotate 결과는 아래에 첨부했습니다.
한 가지 눈에 띄는 점은,
코드 수정 전에는 with_mapper가 캐시미스 빈도가 현저히 적었지만
지금은 with_mapper가 without_mapper보다 전체 메모리 레퍼런스 횟수도 더 많고
캐시미스 횟수도 더 많은 것으로 나온다는 것입니다.
gcc 최적화를 끈 상태로 컴파일했을 때와 같아진 것이죠.
그럼에도 실행시간은 더 짧다는 점도 같습니다.
결국, gcc 최적화를 하든 하지 않든
메모리 레퍼런스 횟수도 with_mapper 쪽이 더 많고 캐시미스 휫수도 더 많은 셈인데,
with_mapper에서 for 루프를 분리함으로써 얻은 성능향상의 이유가 뭔지
저는 아직 머리에 그려지지 않는군요. 어렵네요..
음 ..
역시 with_mapper 의 두번째 loop 가 최적화 되면서 날아간 것이네요.
그럼에도 여전히 with_mapper 가 대략 10~40% 정도의 이득을 보이고 있다는게 특이합니다.
차이점이라면 without_mapper 의 경우 loop 내에서 function call + cache miss 조합이라는 것인데..;;
그래서 혹시나 해서 다음과 같이 without_mapper 의 rand 를 inline 으로 바꿔봤습니다.
http://opensource.apple.com/source/Libc/Libc-594.9.4/stdlib/FreeBSD/rand.c
나머지 다른 소스들도 마지막의 return 을 동일하게 해서 비교해 봤는데..
without_mapper 의 rand 를 inline 시켜서 -O3 를 주고 빌드한게 with_mapper 에 비해 근소하게 이득을 보여주네요.
(wm_* -> with_mapper, wom_* -> without_mapper)
되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』
http://www.pixelbeat.org/prog
http://www.pixelbeat.org/programming/profiling/
위 사이트 참조해서 해 본 perf stat 데이터입니다. 바이너리는 모두 -O3 컴파일했습니다.
with_mapper / without_mapper / without_mapper_inline 순서로 봤을 때,
실행시간은 각각 0.415초 / 0.717초 / 0.371초로, inline rand를 쓴 코드가 가장 짧았는데
수치들을 좀 살펴볼 필요가 있는 것 같습니다.
[1] with_mapper와 without_mapper 비교
실행해야 하는 명령의 갯수는 1,010,293,714개와 935,441,846개로 with_mapper 쪽이 8%정도 더 많고
캐시미스 비율도 81.677%와 79.120%로 with_mapper 쪽이 약간 큰 편이지만
실행에 걸린 시간은 with_mapper 쪽이 더 짧았습니다.
수치중에 눈에 띄는 점은 stalled-cycles인 것 같습니다.
stalled-cycles-frontend와 stalled-cycles-backend 퍼센티지가 with_mapper 쪽이 더 작습니다.
그 결과가, with_mapper에서 1.00 insns per cycle / 0.60 stalled cycles per insn 으로 나오고
without_mapper에서는 0.51 insns per cycle / 1.59 stalled cycles per insn 으로 나옵니다.
with_mapper는 한 사이클당 1.00개의 명령을 실행하는 효율이 나왔고,
without_mapper는 한 사이클당 0.51개의 명령을 실행하는 효율이 나왔다는 말 같습니다.
stalled-cycles는 idle-cycles라고도 부르는 것 같고, CPU 파이프라인이 진행되지 못하는 상태를 가리키는 모양입니다.
( http://stackoverflow.com/questions/22165299/what-are-stalled-cycles-frontend-and-stalled-cycles-backend-in-perf-stat-resul )
메모리로부터의 명령어/데이터 페치를 기다린다든지, 페치된 명령어가 복잡해서 미처 디코딩되지 못해서
그걸 기다린다든지 하면서 CPU 사이클을 흘려보내는 상황을 말하는 것 같습니다.
대표적인 경우로, 캐시미스가 나면 stall 상태가 길어지겠지만, 캐시미스 말고도 stall이 발생하는 상황은 더 있을 것입니다.
with_mapper는 without_mapper에 비해 더 많은 명령을 실행해야 하고 캐시미스도 더 많이 발생하면서도 실행시간은 더 빨랐으므로,
without_mapper는 캐시미스 말고 다른 원인에 의한 stall을 많이 겪었다는 말이 될 겁니다.
with_mapper에서 stall 발생이 줄어든 이유만 머리에 그릴 수 있으면 제 궁금증은 풀릴 것 같습니다.
아직은 안 풀리지만요.
[2] without_mapper_inline이 빠른 이유
이 코드가 빨리 동작한 것은 좀 다른 이유 같습니다.
무슨 이유인지 이 코드의 stall-cycles 비율이 매우 높아서
0.44 insns per cycle 로 세 샘플 가운데 가장 낮은 효율로 동작했는데도 실행시간은 더 짧았는데
그 이유는 실행해야 하는 명령의 갯수가 392,815,782 instructions로
위의 non-inline 샘플들의 40% 수준으로 줄어들었기 때문인 것 같습니다.
음 ..
with_mapper 의 효율이 1 insns per cycle 이상 나오는 걸로 봐서..
두 개의 loop 가 파이프라인에서 동시 처리되고 있는 건 아닐까 하는 생각도 듭니다. (추측)
어차피 두 loop 의 종속 변수는 arr_ran[i] 뿐이니, 두 번째 loop 가 첫 번째 loop 의 결과를 받아서 동시에 처리되어도 별 문제는 없어 보이고..
그렇다면 실제 수행 시간은 두 개의 loop 중, 가장 오래 시간이 걸리는 loop 정도 만큼만 되지 않을까 생각되네요.
그래서 loop 에 있는 rand 를 inline 시켜서 loop 내에서 걸리는 시간을 최대한 줄여 버리면..
오히려 두 개의 loop 를 동시 컨트롤 하는 쪽이 좀 더 부담이 되는 상황이 되는게 아닐까 싶습니다.
거기에 with_mapper 쪽에는 arr_ran[i] = random; + calloc() 하는 부분도 추가되어 있는 상태고 말이죠.
myrand 에서 쓸데 없는 if 문 없애고, with_mapper 와 without_mapper 에 inline 시켜서..
with_mapper 쪽에 추가된 코드 외에는 완전히 동일한 상태로 -O3 를 주고 빌드해서 비교해 보면..
약간의 instruction 과 branch-instruction 에서 차이를 보이는 것을 감안하면 큰 차이가 없어 보입니다.
제 서버에서는 stalled-cycles 가 안 나오네요.. O.o
되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』
덕분에 새로운 툴도 알게 되고 평소 안 하던 공부도
덕분에 새로운 툴도 알게 되고 평소 안 하던 공부도 조금 하는 계기가 생기네요.
고맙습니다.
[1] 기존의 rand()를 쓰는 버전 변경
기존의 rand()를 쓰는 without_mapper.c를 좀 수정해서
for 루프의 한 사이클에서 복수개의 작업을 처리하도록 했습니다.
아래 코드는 4개짜리이고, 2개/4개/8개/16개 식으로 만들어서 해 봤습니다.
2개를 한 번에 처리할 때는 오히려 성능저하가 왔는데 그 이유는 잘 모르겠습니다.
하지만 4개/8개/16개 식으로 늘려가면 점차 with_mapper의 성능에 근접해가는 경향이 보입니다.
stall_cycles 비율이 점차 낮아지고, insns per cycle 도 점차 높아져 갑니다.
캐시미스 빈도에 별 변화가 일어나지 않으면서도 실행시간이 크게 변하는 패턴이 느껴집니다.
아마도, rand()호출 직후에 arr_buf[...]++; 이 실행되면
rand() 실행중에 동작하던 CPU 파이프라인이 arr_buf[...]++; 처리하면서
그 연속성을 잃는(?) 식의 동작이 1500만번 반복되고 있었는데
이런 빈도를 낮춰주니 파이프라인의 동작효율이 올라갔다든지 하는 이유가 아닐까 끼워맞춰 봅니다.
(사실 이 부분을 잘 이해하고 싶은데 적으면서도 무슨 말인지 잘 분간이 되지는 않네요)
어쨌든 1500만번을 한 방에 모아서 새로운 for loop을 만드는 대신
8번이나 16번씩만 모아서 처리하는 정도만으로도 꽤 큰 변화가 나왔습니다.
힙에 메모리 60MB를 할당하지 않는 대신 약간의 속도 패널티를 감수하는 선택이 되겠네요.
[2] inline rand 쓰는 버전
올려주신 대로, inline rand를 with_mapper와 without_mapper 모두에 사용하고 비교해보면
둘 사이의 차이가 거의 사라지네요. stalled-cycles 비율도 모두 높게 나오고요.
아래와 같이 1500만개의 랜덤넘버만 만들고 종료하도록 만들어서 비교해 봤습니다.
rand()호출 버전과 inline rand 호출버전을 만들어 각각 rand_orig.c와 rand_inline.c라고 했습니다.
CRT의 rand()는 1.92 insns per cycle 로 동작하고
인라인 버전은 1.06 insns per cycle 로 동작했습니다 (stalled-cycles-frontend가 높게 나오네요)
CRT의 rand()는 자체의 stalled-cycles 비율이 낮아서 단속적 동작시보다 연속동작시 효율이 좋고
인라인 버전은 자체의 stalled-cycles 비율이 높은 상태라서 단속적/연속적 동작에 따른
차이가 부각되지 않는 것이 아닌가 싶습니다.
다만, CRT의 rand()보다 인라인 버전이 더 가볍게 구현되어 있어서,
1500만번 동작시 0.189초 - 0.142초 == 0.057초의 이득이 있고
이 차이가 inline 버전 적용시 반영되고 있는 것 같습니다.
아래와 같이 prefetch를 활용해보니 꽤
아래와 같이 prefetch를 활용해보니 꽤 빨라집니다.
2개/4개/8개/16개/32개까지 단계적으로 성능 향상이 이뤄졌습니다.
stalled-cycles 비율도 매우 낮아지고, insns per cycle도 1.48 정도까지 올라갑니다.
처음엔 공연히 할 줄도 모르는 인라인 어셈블리 삽질을 했는데
그냥 gcc의 _mm_prefetch() intrinsic을 쓰면 간단히 되네요.
prefetch 힌트로는 _MM_HINT_NTA말고 다른 걸 줘도 별 차이 없는 것 같은데 다 해보지는 않았습니다.
without_mapper 코드를 수정했고, 빌드는 -O3로 했습니다.
우연히 들어왔다 많이 배우네요.
우연히 들어왔다 많이 배우네요.
우선 짚고 넘어가야 할
우선 짚고 넘어가야 할 것은,
map[random_index(i)]++; 나 map[temp[i]]++; 둘다 캐쉬 지역성을 깨뜨리는 데는 동일합니다. 어차피 map에 인덱스되는 값이 랜덤이기 때문이죠.
위에서 테스트하신 결과들을 살펴봐도 모두 캐쉬 미스 비율이 대동소이한 것을 보면 알 수 있습니다.
map[temp[i]]++;의 경우 이 시점에 이미 temp는 결정된 값이기 때문에 CPU가 내부적으로 인스트럭션을 최적화하는 데 유리합니다.
하지만 temp 용량만큼 메모리를 더 잡아야 한다는 단점이 있습니다.
살짝 개선해서 아래와 같은 코드를 생각해 보았습니다. 개념 확인 정도로만 봐아주십시오.
테스트해 보니까 map[myrand()]++; 식으로 쓸 경우에도 map[temp[i]]++; 과 유사한 결과가 나왔습니다.
buffer 사이즈를 10과 100으로 테스트했는데, 10에서는 조금 느렸고 100으로 바꾸니까 비슷하게 나오더군요.
댓글 달기