Managed code Performance 측정하기

안녕하세요. 오늘은 managed code 성능에 대해 얘기해 볼까 하는데요. 뭐 managed code가 초기 사용은 쉽지만 환상 처럼 그냥 대충 써도 빠르고 효율적으로 작동하진 않거든요. native code가 그 나름의 문제가 있고 성능 튜닝이 필요 하다면, managed code는 또 다른 면에서 나름 문제와 성능 튜닝이 필요 하죠.

네이티브 랭귀지인 C++/C와 managed 랭귀지인 C#, VB의 가장 큰 차이점은 역시 GC인데요. 이 GC가 성능에 미치는 영향이 아주 없진 않거든요. 그래서 오늘 GC에 관계된 성능 튜닝에 대해 얘기 해 볼까 합니다. 아니 얘기라기 보단 PDC 토크를 링크 하고, 거기에 대해 잠시 요약을 쓸까 합니다.

일단 PDC 토크는 이거 입니다.

Real-World Analysis and Optimization of XNA Framework Games on Windows Phone 7

제목에서 보실수 있는거 처럼 내용은 WP7 상의 XNA Framework에 관계된 내용이긴 하지만, 앞에 메모리 튜닝에 관계된 talk이 바로 제가 얘기 하려고 하는 GC에 관계된 성능 튜닝입니다.

아시다시피, managed code 에서는 object의 메모리에 관계된 사항은 사용자가 관리하지 않고, CLR이 알아서 관리하도록 냅둡니다. 복잡한 메모리 관리를 사용자가 하지 않음으로써 메모리 leak이나 아니면 circular 레퍼런스 같은 문제, object lifetime, 혹은 ownership에 대해 걱정하지 않아도 되니까 일반적으로 managed code에서 복잡한 데이타 스트럭쳐나 알고리듬을 만들고 쓰기가 C++이나 C 와 같은 사용자가 직접 메모리를 관리해야 하는 코드 보다 쉽게 느껴집니다. 물론 managed code도 그 나름의 문제를 생각해야 하지만, 전체적으로 봤을때 C++ 과 같은 코드 보다 확실히 사용하기가 쉽죠.

하지만 이게 공짜로 제공 되어지진 않습니다. 소위 말하는 GC tax이죠. 이 랜덤하게 보이는 GC가 런 할때 프로그램이 잠시 pause되고 메모리가 collect되는 이 현상이 때로는 성능에 영향을 미치게 됩니다.

보통 이 문제는 사용자들이 managed code가 메모리 관리를 대신 해 준다는 것만 생각하고 heap allocation 자체는 C++와 같은 언어 처럼 cheap하다고 생각함으로써 발생됩니다. GC로 인해 managed code에서의 heap allocation은 네이티브 코드에 비해 무척 expensive 하죠. 또 하나의 착각은 보통 GC 관련된 글을 보면 Gen 0 의 오브젝트는 무척 cheap한 GC 라고 되어 있다는 겁니다. 이건 완전 잘못 이해 할수 있는 발언입니다. GC입장에서 보면 Gen 0 object를 collect하는건 cost가 적습니다. 아주 빠른 시간에 할수 있다는거죠. 하지만, 프로그램 입장에서 보면 GC가 런 한다는것 자체가 expensive 한거기 때문에, Gen 0 object 역시 cheap하지 않습니다. 특히, 이 Gen 0 가 cheap하다는 생각으로 무지막지한 양의 임시 변수를 heap allocate 하는 일은 정말 최악입니다.

GC가 런 한다는것 자체가 expensive 하다는 말을 좀 더 보충 설명 하자면, GC가 런 할때 마다 여러가지 일이 일어 나는데 (물론 GC의 종류에 따라 일어나는 일이 다르긴 합니다.) 일반적으로

  1. 모든 thread들을 pause하고 (many core 을 생각하시면 이게 얼마나 expensive 한건지 감이 오실겁니다. 한 thread에서 20ms을 멈춰있다면, 4core를 생각하지만 80ms 의 일을 못한겁니다.)
  2. managed heap을 조사 하며 (현재 heap있는 object들의 양에 따라 시간이 달라지는데, 무지막지한 양의 임시 object가 있다 할때, 많이 조사해야겠죠?)
  3. 생명이 다한 objects들의 finalize 코드들을 런하고 메모리를 릴리즈 하고 (finalize 코드를 런하는게 공짜는 아니겠죠?)
  4. 살아 남은 objects들의 generation을 다음 gen으로 옮기고 (쓸데 없이 GC가 많이 런 하게 되면, gen 0에 있어도 되는 object들이 자꾸 더 높은 gen으로 옮겨지게 되고, 따라서 점 점 더 GC하기 비싸지게 됩니다.)
  5. object들을 compact (move) 하고, 레퍼런스들을 고치게 됩니다.

딱 봐도, 뭐 작은 일을 하는게 아니죠? 될수록 GC가 런 하지 않도록 할수록 managed app의 성능은 향상 됩니다. 위에 링크를 걸어 둔 토크에서 어떻게 하면 GC가 좀 더 덜 run하게 할수 있나에 대해서 알려줍니다.

시간 나면 함 보시길, 수고요!