CPU — 메인 메모리 간의 통신속도는 매우 느리다. 다만 HDD와 비교해서는 빠르겠지만, HDD와의 통신은 흔히 백만년 쯤 걸린다고 표현한다 ((그래서 CPU–메인 메모리 사이에는 L1, L2 cache 등이 존재하게 되고, HDD에는 OS에서의 버퍼링, HDD의 물리적인 버퍼링이 존재한다.)) . 만약 CPU의 cache 용량을 초과하는 메모리 접근을 자주하게되면 못 볼 꼴을 보게된다 ((밑에서 얘기하고 있는 내용이지만, L1 -> L2 -> (L3) -> Main memory로 넘어갈 때마다 걸리는 시간은 "배수"로 늘어난다. 대충 단계마다 2~100배 정도씩 시간이 증가한다. 물론 HDD까지 간다면 20만~200만 배 느려지지만. 그래서 메모리 연산에 필요한 시간 * 배수 만큼 프로그램이 느려지는걸 경험하게 될 것이다.)) . 그런 경우가 잘 안나오니, 정 궁굼하다면 부팅할 때 CPU cache를 한 번 꺼보면 된다.
객체 caching — 미리 계산한 결과를 특정 객체로 관리하는 것 — 을 쓸 때의 문제점을 간단히 요약하면,
- 간단한 데이터 몇 개 (크기가 작으면 금상첨화) 로 값을 다시 계산할 수 있는 경우 CPU 명령어 수는 적을 (100~300 이하라면 내가 원하는 수준) 것이다.
- 미리 계산해서 저장된 데이터를 위해선 특정 key를 사용해서 접근하게 될 텐데 ((꼭 그런 것은 아니지만 많은 경우)) 이런 key 기반 접근의 동기화는 부담되는 경우가 있다 — 특히 멀티스레딩의 경우
- 적절하지 않은 caching policy;정책 은 메모리 릭과 아주 친하다(…) — 사실 적절한 caching 정책 자체를 만들기가 쉽지 않다. 해당 문제 영역을 아주 잘 관찰해야…
- Caching을 할 때는 언제나 data-consistency 문제를 걱정해야 한다 — 실제 값과 caching된 값이 같은지 신경 써 줘야하고, caching된 값을 자주 쓰는게 아니면 오히려 문제는…
- Caching된 값들의 working-set이 CPU cache의 크기를 넘어서면(…)
정도가 있겠다.
특히나 마지막 문제 때문에 "caching했더니 성능이 떨어진다" 하는 케이스가 가끔 나온다. L2 cache도 아주 빠르다곤 못하지만 견딜만큼 빠른데, 그 용량을 초과하는 순간 — 즉 working set 크기 > L2 cache 크기 — 성능이 멋지게 떨어지기 시작한다. 그래서 KLDP나 GPGForum보다 보면 -Os 최적화 (코드 크기 감소 최적화) 를 수행했을 때 더 빨랐어요! 하는 게시물이 가끔씩 올라온다. ((물론 이 경우는 이 글에서 설명하는 것과는 조금 다른 경우다. L1에 존재하는 instruction cache 가 넘쳐서 생기는 문제다(L1 cache는 흔히 instruction/data를 따로 가진다) 가장 자주 실행되는 루프가 L1 instruction cache 보다 크면 이런 경우가 생길 수 있다.))
ps. 그런 의미에서 이것도 "Premature optimization is the root of all evil" 의 한 경우? 그러니 객체 caching을 처음부터 고려하는게 아니라, 원재료값에서 cache될 수도 있는 값을 계산하는게 정말 의미있게 느릴 때만 생각하자. 그것도 문제를 열심히 관찰하고 caching policy를 잘 선택하고나서야.
물론 이 글 자체는 학부 + 석사 과정 수준의 컴퓨터 구조 지식만으로 씌여졌다. 결정적으로 나는 무선 통신망을 전공해서 정확한 분석이 아닐 수 있다. 그러니 틀린 점이 있다면 지적하거나, 설명해주면 감사하겠다 :)