성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
[정성태] 만드실 수 있습니다. 단지, Unity 엔진 내의 스크립트와 W...
[공진영] 안녕하세요 좋은글 감사합니다. 현재 제가 wpf로 관제 모...
[정성태] The Windows Registry Adventure #1: ...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>DataTable에 대해서 Dispose 메서드를 호출할 필요가 있을까?</h1> <p> <br /> 재미있는 질문 글이 하나 눈에 띄더군요. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > DataTable DataSet의 경우 Dispose 해주지 않으면 메모리 Leak이 나는가요? ; <a target='tab' href='http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=17&MAEULNO=8&no=140314&ref=140314'>http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=17&MAEULNO=8&no=140314&ref=140314</a> </pre> <br /> 이런 유의 질문은 직접 테스트 해보는 경우 닷넷 프레임워크에 대한 내부적인 이해도를 증가시키기 때문에, 성질상 그냥 지나칠 수가 없더군요. ^^<br /> <br /> 일단, 제 처음 예상은 위의 댓글(techshare)에서도 달려있지만 다음과 같았습니다.<br /> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> 문제는, 다름 아닌 DataTable이 상속받은 MarshalByValueComponent가 Finalizer를 구현했다는 것입니다.<br /> <br /> 즉, Dispose를 명시적으로 호출하지 않으면 Finalizer가 구현되었다는 것으로 인해 해당 개체는 첫 번째 GC 사이클에서 살아남아 1세대 힙으로 넘어가게 됩니다. 이후 1세대 GC가 구동이 될 때에야 비로소 힙에서 제거될 수 있는 것입니다.<br /> <br /> 반면, DataTable의 Dispose를 명시적으로 해줄 경우, Dispose 메서드 내에 포함된 GC.SuppressFinalize 호출로 인해 Finalizer 큐의 관리에서 제외되므로 일반적인 개체처럼 첫 번째 GC 사이클에서 힙이 해제될 수 있습니다.<br /> </div><br /> <br /> 즉, 가능하다면 명시적으로 DataTable.Dispose를 해줄 것을 권장한다는 내용의 댓글을 달은 것입니다.<br /> <br /> 자, 그럼 정말 그런지 한번 테스트 해볼까요? ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 우선, 다음과 같이 DataTable의 내용을 채워주는 코드를 만들고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > DataTable NewDataTable() { DataTable dt = new DataTable("test"); DataColumn dc1 = dt.Columns.Add(); DataColumn dc2 = dt.Columns.Add(); DataColumn dc3 = dt.Columns.Add(); for (int i = 0; i < 10000; i++) { DataRow dtRow = dt.Rows.Add(); dtRow.SetField(dc1, Guid.NewGuid().ToString()); dtRow.SetField(dc2, Guid.NewGuid().ToString()); dtRow.SetField(dc3, Guid.NewGuid().ToString()); } return dt; } </pre> <br /> 버튼 1, 버튼 2를 각각 두어 다음과 같이 테스트 코드를 만들어 줍니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > private void button1_Click(object sender, EventArgs e) { for (int i = 0; i < 1000; i++) { DataTable dt = NewDataTable(); } } private void button2_Click(object sender, EventArgs e) { for (int i = 0; i < 1000; i++) { DataTable dt = NewDataTable(); <span style='color: blue; font-weight: bold'>dt.Dispose()</span>; } } </pre> <br /> 테스트를 들어가기 전에, GC 카운트를 확인할 수 있도록 별도의 스레드를 만들어 다음과 같이 출력해 주는 코드를 만들어 주면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>Thread thread = new Thread(ThreadFunc);</span> thread.IsBackground = true; thread.Start(); void ThreadFunc(object state) { while (true) { string txt = string.Format("{0,6}, {1,6}, {2,6}, {3,10}", <span style='color: blue; font-weight: bold'>GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2), GC.GetTotalMemory(false)</span>); System.Diagnostics.Trace.WriteLine(txt); Thread.Sleep(1000 * 2); } } </pre> <br /> 준비는 이걸로 모두 끝입니다. 이제 응용 프로그램을 실행하고, "버튼 1"을 눌러 "<a target='tab' href='https://docs.microsoft.com/en-us/sysinternals/downloads/debugview'>DebugView</a>" 화면에서 GC 호출 횟수를 확인하고, 다시 응용 프로그램을 재시작한 후 "버튼 2"를 눌러 그 결과를 비교하면 다음과 같습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ===== DataTable - Dispose 호출하지 않은 경우 ===== ===== DataTable - Dispose 호출한 경우 ===== [2376] 0, 0, 0, 324132 [9372] 0, 0, 0, 332324 [2376] 20, 15, 7, 11297372 [9372] 30, 23, 11, 9738848 [2376] 65, 51, 25, 16362628 [9372] 78, 61, 30, 7734852 [2376] 112, 88, 44, 12551840 [9372] 125, 99, 49, 12852724 [2376] 160, 127, 63, 10160820 [9372] 173, 137, 68, 11636900 [2376] 203, 161, 80, 12678136 [9372] 220, 175, 87, 16065456 [2376] 1120, 883, 441, 12516788 [9372] 1127, 888, 444, 10145972 ...[중간 생략]... ...[중간 생략]... [2376] 1171, 921, 460, 14585640 [9372] 1176, 926, 463, 10166208 [2376] 1219, 959, 479, 7840512 [9372] 1224, 965, 482, 8799928 [2376] 1264, 995, 497, 9269460 [9372] 1272, 1003, 501, 9937680 [2376] 1304, 1027, 513, 7965312 [9372] 1319, 1041, 520, 15611404 [2376] 1349, 1063, 531, 13537072 [9372] 1368, 1080, 540, 14742832 [2376] <span style='color: blue; font-weight: bold'>1382, 1090, 545, 8501220</span> [9372] <span style='color: blue; font-weight: bold'>1380, 1090, 545, 8507300</span> </pre> <br /> 이럴 수가... ^^; Dispose 호출 유무에 상관없이 GC #0, GC #1, GC #2에 대한 호출 횟수가 거의 유사합니다. 어떻게 이런 결과가 나온 걸까요?<br /> <br /> <hr style='width: 50%' /><br /> <br /> 혹시나 싶어서, DataTable이 아닌 그와 유사하게 메모리를 소비하는 클래스를 별도로 만들어서 테스트를 해보았습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public class MemData : IDisposable { byte[] bytes; public MemData() { bytes = new byte[1024]; } <span style='color: blue; font-weight: bold'>~MemData()</span> { } public void Dispose() { <span style='color: blue; font-weight: bold'>GC.SuppressFinalize(this);</span> } } </pre> <br /> 버튼 1, 2에 대해 각각 다음과 같은 코드로 루프를 돌아 실행했고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > private void button1_Click(object sender, EventArgs e) { for (int i = 0; i < 10000000; i++) { MemData md = new MemData(); } } private void button2_Click(object sender, EventArgs e) { for (int i = 0; i < 10000000; i++) { MemData md = new MemData(); <span style='color: blue; font-weight: bold'>md.Dispose();</span> } } </pre> <br /> 다시 DebugView에 출력된 결과를 비교하면 다음과 같습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ===== MemData - Dispose 호출하지 않은 경우 ===== [4592] 0, 0, 0, 324132 [4592] 227, 226, 3, 4609632 [4592] 725, 724, 8, 7622192 [4592] 1210, 1209, 14, 4507120 [4592] 1717, 1716, 21, 4567520 [4592] 2199, 2198, 30, 5552640 [4592] <span style='color: blue; font-weight: bold'>2498, 2497, 33, 5340032</span> ===== MemData - Dispose 호출한 경우 ===== [7308] 0, 0, 0, 332324 [7308] 1570, 1, 0, 2282728 [7308] <span style='color: blue; font-weight: bold'>2498, 1, 0, 1308048</span> </pre> <br /> 오호~~~ 이번에는 예상했던 결과가 나왔습니다. GC #0의 횟수는 같지만 GC #1, #2 단계에서 확실히 비교가 되는 차이를 보여주었으며 GC Heap 메모리도 Dispose를 호출한 경우에 안정적으로 유지가 되었습니다. 결국 GC 호출로 인한 오버헤드가 줄어듦으로 인해 실행시간도 빨라져서 Dispose를 호출하지 않은 경우 약 14초의 실행시간을 보인 반면 Dispose를 호출한 경우에는 6초 정도에 테스트가 마무리되었습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 임의로 작성한 MemData에서는 예상되는 결과를 보였지만, DataTable에서는 전혀 의도치 않은 결과가 나온 것을 어떻게 해석할 수 있을까요?<br /> <br /> DataTable의 테스트 결과에 대해 고민한 끝에, 이것은 분명 내부적으로 DataTable 측에서 이미 GC.SuppressFinalize 호출을 했을 거라는 판단이 들었습니다. 그래서, .NET Reflector를 이용하여 DataTable의 생성자를 확인해 보았는데! ^^ 아니나 다를까, 반갑게 다음과 같은 코드가 포함되어 있었습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public DataTable() { this.tableName = ""; ...[생략]... <span style='color: blue; font-weight: bold'>GC.SuppressFinalize(this);</span> ...[생략]... this.rowBuilder = new DataRowBuilder(this, -1); } </pre> <br /> 자, 그럼 마음 편하게 ^^ 결론을 내려볼까요? <span style='color: blue; font-weight: bold'>DataTable은 Dispose를 명시적으로 호출해 주지 않아도 성능적인 면에서 아무런 영향도 발생하지 않습니다.</span><br /> <br /> 그리고 위의 테스트 결과에 따라, <span style='color: blue; font-weight: bold'>Finalizer를 구현한 클래스의 경우 Dispose를 해주는 것과 그렇지 않은 경우의 성능 차이는 분명히 발생한다는 것!</span><br /> <br /> <a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=630&boardid=331301885'>첨부된 파일은 위의 코드를 포함한 예제 프로젝트</a>입니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1265
(왼쪽의 숫자를 입력해야 합니다.)