GC의 부하는 상대적인 것!
최근에 읽은 글인데요.
Managed code Performance 측정하기
; http://blogs.msdn.com/b/heejaechang/archive/2010/11/07/managed-code-performance.aspx
꼭 위의 글을 읽지 않았더라도, 우리가 흔히 알고 있기를 GC가 구동된다는 것은 그만큼 부하가 높아진다는 인식을 가지고 있습니다.
"
제니퍼 닷넷" 제품을 만들어서 테스트하다 보니, 나름대로 성능에 대한 다양한 경험을 하게 되는데, 위의 글을 읽으면서 갑자기 GC와 관련된 한가지 사례가 생각나더군요.
이야기의 발단은, "제니퍼 닷넷"에 COM+ 모니터링을 추가한 것에서부터 시작됩니다.
COM+ 호출 모니터링 및 누수 확인
; https://www.sysnet.pe.kr/2/0/881
COM+ 서버 모니터링
; https://www.sysnet.pe.kr/2/0/884
처음 "제니퍼 닷넷"에 "COM+ 모니터링" 기능을 넣었을 때는 명시적인 Dispose를 하지 않은 경우, 그에 대해 "경고"로 알려주는 것이 기본 동작이었습니다. 그런데, 차츰
제니퍼 닷넷 Trial 적용을 직접 사이트에 해보면서 제법 많은 사이트들이 "명시적인 Dispose"를 하지 않는다는 것을 경험하게 되었고, 처음에는 그런 고객들에게 "명시적인 Dispose"를 하지 않은 경우 웹 사이트의 성능이 어느 정도 안 좋아질 수 있는 지를 "성능 비교 자료"를 통해 알려주기 위해 내부적으로 부하 테스트를 시작하게 되었습니다.
그런데, 엉뚱하게도 그 결과로 인해 "제니퍼 닷넷"의 기본 동작이 바뀌게 됩니다.
자, 그럼 어떤 결과이길래 그럴까요?
테스트를 위해, "COM+ 서버"와 그것을 호출하는 "ComplusLeakTest.aspx"를 만들었습니다.
[환경]
* 부하 도구가 설치된 운영체제: Windows Server 2008 R2 (물리 머신 A)
* 테스트 웹 응용 프로그램이 설치된 운영체제: Windows Server 2008 R2 (물리 머신 B의 가상 머신)
* EXE 유형의 COM+ 서버와 웹 서버는 동일한 PC에 구성
=== EXE 유형의 COM+ 서버 ===
public class MyComponentServer : ServicedComponent
{
public MyComponentServer()
{
count = 0;
}
int count;
[AutoComplete]
public int MethodAC()
{
return count++;
}
}
=== 웹 페이지 ComplusLeakTest.aspx ===
if (Request.QueryString["m"] == "6")
{
// 명시적인 Dispose 호출
using (EntLibServerApp.MyComponentServer mcs = new EntLibServerApp.MyComponentServer())
{
mcs.MethodAC(connectionString);
}
}
else
{
// Dispose 호출하지 않음.
EntLibServerApp.MyComponentServer mcs = new EntLibServerApp.MyComponentServer();
mcs.MethodAC(connectionString);
}
이제 부하 툴을 이용해서 "ComplusLeakTest.aspx?m=6"과 "ComplusLeakTest.aspx?m=7"로 비교를 해봅니다.
CPU 소비가 100%가 되도록 요청 부하를 올리고, 처리량을 보니 전혀 의도하지 않았던 결과가 나왔습니다.
"명시적인 Dispose(m=6)"
"Dispose 를 GC에 맡긴 경우(m=7)"
보는 바와 같이 명시적인 Dispose를 한 경우에는 서비스 처리가 초당 384건만 되었던 반면, GC에 맡긴 경우에는 초당 600건이 넘는 압도적인 차이를 보였습니다.
재미있는 것은, 이렇게 호출이 되는 경우 GC가 발생한 건수입니다. 테스트를 1분 정도 진행하는 동안 GC의 발생횟수입니다.
[m=6, 명시적인 Dispose]
1분 동안 총 호출 건수: 22,108
GC: 35번
[m=7, Dispose 제거]
1분 동안 총 호출 건수: 34,678
GC: 6,024번
웹 페이지 처리 건수에 비례해서 GC가 호출된다고 보기에는 너무나 큰 차이입니다. 즉, "명시적인 Dispose"를 안해줌으로 인해 GC의 호출 건수는 대폭 증가하지만, 오히려 그 부하는 아무것도 아니라는 듯이 처리 건수는 Dispose를 호출하지 않았을 때가 더 많습니다.
게다가 CPU 소비 차이를 보이기 위해서 부하량을 초당 220번으로 낮춰서 꾸준히 보내는 방식으로도 테스트를 해본 것도 흥미롭습니다.
[m=6, 명시적인 Dispose]
CPU 사용률: 62%
[m=7, Dispose 제거]
CPU 사용률: 33%
6,024번의 GC 발생을 동반한 CPU 소비율이 33%인 반면, 명시적인 Dispose를 이용하여 겨우 35번의 GC만 발생했을 때의 CPU 소비율이 62%라는 것은, 개인적으로도 매우 의외의 결과였습니다. 이 결과로 판단해 보면, COM+ 서버의 명시적인 Dispose 호출로 인해 프로세스간 통신이 발생하고 자원 정리가 되는 부하가 GC의 일반적인 부하보다도 훨씬 크다는 결론이 나오고, 따라서 우리가 알고 있는 "GC의 부하"라는 것이 상황에 따라서는 결코 expensive하지 않다는 것입니다.
결국, 오히려 COM+ 개체의 명시적인 Dispose 호출이 시스템 성능적인 면에서 결코 도움이 되지 않았다는 것인데 "
제니퍼 닷넷"에서 이를 "경고"로 알려주기에는 무리가 있었으므로 기본 동작이 바뀌게 된 것입니다. (물론, 옵션 값 조정을 통해서 경고로 나타낼 수 있습니다. ^^)
이 글을 읽어보시는 분들 중에 실제로 위와 같은 테스트를 하시는 분들이 있다면 데이터 공유를 부탁드립니다. 혹시나 제가 테스트 조건을 올바르지 않게 주었을 가능성도 있기 때문에, 가능한 여러 테스트 결과가 나오면 좋을 것 같습니다.
그리고, 혹시나 극단적인 결론을 내리실 분들을 위해서 미리 말씀드리면,
위의 결과만 보면, 명시적인 Dispose를 코드 상에서 없애는 것이 더 좋을 수도 있지만 꼭 그렇지 않을 수도 있습니다. 예를 들어, JITA/AutoComplete이 아닌 COM+ 개체가 native 자원을 사용하는 경우 등에는 반드시 명시적인 Dispose가 필요하겠습니다.
그나저나, 제가 직접 저런 테스트 결과를 겪긴 했지만, 여전히 Dispose를 없애는 것에 대해서 망설여지는 것은... 왜일까요? ^^
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]