성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
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'>pagefile.sys를 비활성화시켰는데도 working set 메모리가 줄어드는 이유</h1> <p> 다음과 같은 재미있는 질문이 있었습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 메인메모리 사용 방법이 궁금합니다. ; <a target='tab' href='https://social.msdn.microsoft.com/Forums/ko-KR/1648fcf5-6e57-4037-9852-9fada322f46c/-?forum=visualcplusko'>https://social.msdn.microsoft.com/Forums/ko-KR/1648fcf5-6e57-4037-9852-9fada322f46c/-?forum=visualcplusko</a> </pre> <br /> 사실, 처음 저 질문을 봤을 때 왠지 사용자가 할당한 메모리 중에서 (할당 후) 한 번도 쓰지 않는 영역을 어떤 식으로든 내려서 그런 것이 아닌가... 하고 생각했습니다. 그래도 어쨌든 현상이 재미있었는데요, 완벽한(?) 테스트 환경 구성을 위해 저도 pagefile.sys 기능을 제거하고, 다음의 명령어로 메모리 압축 기능도 껐습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Disable-MMAgent -mc </pre> <br /> 혹시나 싶어 hiberfil.sys도 어차피 메모리를 받아 두는 것이니 영향을 주지 않을까 싶어 최대 절전 모드 기능도 껐습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > powercfg.exe -h off </pre> <br /> 이 상태에서 다음의 소스 코드를 x64로 빌드해 실행했습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #include "pch.h" #include <iostream> int main() { struct CHUNK { char data[413] = { 0, }; }; const int itemLen = 25000000; <span style='color: blue; font-weight: bold'>CHUNK *pChunk = new CHUNK[itemLen];</span> int k = 0; std::cin >> k; for (SIZE_T pt = 0; pt < itemLen; pt++) { pChunk[pt].data[0] = 0; } std::cin >> k; } </pre> <br /> <hr style='width: 50%' /><br /> <br /> 결과가 어땠을까요? ^^<br /> <br /> 25000000 * 413은 약 10GB의 메모리를 할당합니다. 이때의 작업 관리자에서 보이는 수치는 다음과 같았습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 10,085,464K - Working set (memory) 10,083,336K - Memory (private working set) 10,103,332K - Commit size </pre> <br /> 페이징 기능을 껐으므로 당연히 해당 메모리는 - 즉, Working set 메모리는 줄지 않고 있어야 합니다. 그런데, 놀랍게도!!! 얼마의 시간이 흐른 뒤 다음과 같이 상태가 변경되었습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 2,176K - Working set (memory) 112K - Memory (private working set) 10,103,332K - Commit size </pre> <br /> 이때 아무 입력이나 해서 "pChunk[pt].data[0] = 0;" 코드를 수행하면 다시 10GB 메모리가 올라오는 것을 볼 수 있습니다. 오~~~ 놀랍습니다. ^^ 기존의 지식에 따라 일단 이를 방지할 수 있게 다음과 같이 SetProcessWorkingSetSize를 이용해 Working Set을 유지하도록 명시했습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > SIZE_T allocSize = itemLen * sizeof(CHUNK); SIZE_T workingSetSize = allocSize + 1024 * 1024 * 200; SetProcessWorkingSetSize(GetCurrentProcess(), workingSetSize, workingSetSize); /* [DllImport("kernel32.dll")] static extern bool SetProcessWorkingSetSize(IntPtr hProcess, nint dwMinimumWorkingSetSize, nint dwMaximumWorkingSetSize); */ </pre> <br /> 그랬더니, 이번에는 아무리 시간이 흘러도 10GB 메모리가 내려가지 않았습니다. 오호~~~ 신기합니다. 도대체 윈도우는 RAM에 있는 메모리를 어떻게 처리한 걸까요?<br /> <br /> 제 생각은, 분명히 무언가가... 그래도 무언가가 (pagefile.sys를 사용하지 않더라도) 페이징을 하는 구성 요소가 있다는 것이었습니다. 그래서 좀 더 검색을 했더니 Superfetch가 후보로 떠올랐습니다. 실제로 Superfetch NT 서비스를 중지(net stop sysmain)하고 나서는 다음과 같은 코드만으로 10GB 메모리가 잘 유지가 되었습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > #include "pch.h" #include <iostream> int main() { struct CHUNK { char data[413] = { 0, }; }; const int itemLen = 25000000; CHUNK *pChunk = new CHUNK[itemLen]; int k = 0; std::cin >> k; } </pre> <br /> 이쯤 되니 이제 결론이 납니다. 만약 여러분들의 Working Set을 그대로 유지하고 싶다면 언제나 SetProcessWorkingSetSize가 답입니다. 또는 페이징(pagefile.sys)을 없앤 상태에서 "Superfetch" 서비스를 중지하면 Working Set이 보존됩니다. (hiberfil.sys나 메모리 압축은 영향을 주지 않았습니다.)<br /> <br /> (<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1366&boardid=331301885'>첨부 파일은 이 글의 테스트 코드를 포함</a>합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 테스트를 좀 더 해보면, Superfetch가 전반적인 메모리 관련 기능들을 제어한다는 것을 알 수 있습니다. 일례로 메모리 압축을 disable 시키면 MMAgent 상태가 다음과 같이 바뀝니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > PS C:\WINDOWS\system32> <span style='color: blue; font-weight: bold'>Disable-MMAgent -MemoryCompression</span> PS C:\WINDOWS\system32> <span style='color: blue; font-weight: bold'>Get-MMAgent</span> ApplicationLaunchPrefetching : True ApplicationPreLaunch : True MaxOperationAPIFiles : 256 <span style='color: blue; font-weight: bold'>MemoryCompression : False</span> OperationAPI : True PageCombining : False PSComputerName : </pre> <br /> 메모리 압축을 껐다고 해서 Superfetch 서비스가 중지하지는 않습니다. 위의 목록을 보면, MemoryCompression, PageCombining을 제외하고는 다른 기능들은 여전히 True인 것을 볼 수 있는데요, 이 상태에서 Superfetch 서비스를 중지시키고 다시 확인하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > PS C:\WINDOWS\system32> <span style='color: blue; font-weight: bold'>Get-MMAgent</span> ApplicationLaunchPrefetching : False ApplicationPreLaunch : False MaxOperationAPIFiles : 256 MemoryCompression : False OperationAPI : False PageCombining : False PSComputerName : </pre> <br /> 모든 기능이 중지된 것을 볼 수 있습니다. 마찬가지로 이 상태에서 메모리 압축을 다시 켜면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > PS C:\WINDOWS\system32> <span style='color: blue; font-weight: bold'>Enable-MMAgent -MemoryCompression</span> PS C:\WINDOWS\system32> <span style='color: blue; font-weight: bold'>Get-MMAgent</span> ApplicationLaunchPrefetching : True ApplicationPreLaunch : True MaxOperationAPIFiles : 256 MemoryCompression : True OperationAPI : True PageCombining : False PSComputerName : </pre> <br /> Superfetch 서비스가 다시 시작 상태로 바뀌면서 이전 상태의 기능들이 복원됩니다. 물론 개별 기능들은 다음과 같이 선택적으로 On/Off할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Disable-MMAgent -ApplicationPreLaunch Disable-MMAgent -MemoryCompression Disable-MMAgent -PageCombining Disable-MMAgent -ApplicationLaunchPrefetching Disable-MMAgent -OperationAPI </pre> <br /> 여기서 재미있는 것이 있다면, Working Set이 수치 상으로는 내려가긴 하지만 다시 10GB를 복원할 때 리소스 모니터로 확인해 보면 실질적인 디스크 사용량이 없다는 점입니다. 즉, pagefile.sys가 없는 상태에서 SuperFetch 서비스가 Working Set을 수치 상으로 줄이기는 하지만 해당 메모리를 완전히 해제해 디스크로 내리는 것은 아니고 별도의 RAM 영역에 cache 형태로만 미뤄두는 것 같습니다. (순전히 가정입니다.) 만약 그 상태에서 pagefile.sys가 켜져 있다면 cache 형태로 나온 메모리 영역을 디스크로 내릴 수 있겠지만 그렇지 않은 경우에는 계속 cache 상태로 머물러 있는 정도인 것입니다. 따라서 어찌 보면 Working Set 수치로는 내려가 있는 것은 맞지만 필요할 때 곧바로 RAM 상에서 복원하는 것이기 때문에 성능상 거의 부하가 없다는... 뭐 그런 해석이 됩니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 참고로 이번 테스트를 하다가 발생한 오류를 정리합니다. VirtualAlloc에서 "8 == Not enough memory resources are available to process this command." 오류 코드를 낼 경우가 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > LPVOID pVoid = VirtualAlloc(nullptr, allocSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (pVoid == nullptr) { DWORD dwLastError = GetLastError(); // 8 == Not enough memory resources are available to process this command. } </pre> <br /> 알고 보니, x64 빌드가 아닌 x86으로 했기 때문에 할당에 실패한 것이었습니다. 또는, "87 == The parameter is incorrect." 에러가 다음과 같은 상황에서 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > LPVOID pVoid = VirtualAlloc(nullptr, allocSize, MEM_COMMIT | MEM_RESERVE, 0); // 마지막 옵션 값이 0 </pre> <br /> 마지막 옵션 값을 PAGE_READWRITE 등의 것으로 명시를 하지 않았기 때문에 "The parameter is incorrect." 오류로 나옵니다. 그런데, 다음과 같이 올바르게 했는데도 "1453 == Insufficient quota to complete the requested service." 오류가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // allocSize == 10GB LPVOID pVoid = VirtualAlloc(nullptr, allocSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (pVoid == nullptr) { DWORD dwLastError = GetLastError(); std::cout << dwLastError; } if (VirtualLock(pVoid, allocSize) == FALSE) { DWORD dwLastError = GetLastError(); // 1453 == Insufficient quota to complete the requested service. std::cout << dwLastError; } </pre> <br /> 위의 코드에서 VirtualAlloc 단계만 보면 Commit size가 10GB로 됩니다. VirtualLock을 해야 Working Set 메모리로 올라오게 되는데 quota가 충분하지 않다는 것입니다. quota는 SetProcessWorkingSetSize로 올려줄 수 있습니다. 따라서 대용량 메모리를 VirtualAlloc/VirtualLock으로 할당할 때는 다음과 같은 식으로 코딩을 해야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // allocSize == 10GB LPVOID pVoid = VirtualAlloc(nullptr, allocSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (pVoid == nullptr) { DWORD dwLastError = GetLastError(); std::cout << dwLastError; } SIZE_T workingSetSize = allocSize + 1024 * 1024 * 200; // 1024 * 1024 * 200는 임의로 지정한 크기 SetProcessWorkingSetSize(GetCurrentProcess(), workingSetSize, workingSetSize); if (VirtualLock(pVoid, allocSize) == FALSE) { DWORD dwLastError = GetLastError(); std::cout << dwLastError; } </pre> <br /> <hr style='width: 50%' /><br /> <br /> 경우에 따라 Enable/Disable MMAgent 명령어 수행 시,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 예) Disable-MMAgent -ApplicationLaunchPrefetching </pre> <br /> 다음과 같이 CimException 예외가 발생할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > PS C:\Windows\System32> Disable-MMAgent -ApplicationLaunchPrefetching Disable-MMAgent : The request is not supported. At line:1 char:1 + Disable-MMAgent -ApplicationLaunchPrefetching + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (PS_MMAgent:Root\Microsoft\...gent\PS_MMAgent) [Disable-MMAgent], CimException + FullyQualifiedErrorId : Windows System Error 50,Disable-MMAgent </pre> <br /> WinRM 서비스도 잘 활성화되어 있는데 왜 이런 오류가 발생하는지 아직 원인을 모르겠습니다. ^^ 혹시 아시는 분은 덧글 부탁드립니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> "winrm quickconfig" 수행 시 다음과 같은 식의 오류가 발생할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > WSManFault Message ProviderFault WSManFault Message = WinRM firewall exception will not work since one of the network connection types on this machine is set to Public. Change the network connection type to either Domain or Private and try again. Error number: -2144108183 0x80338169 WinRM firewall exception will not work since one of the network connection types on this machine is set to Public. Change the network connection type to either Domain or Private and try again. </pre> <br /> 오류 메시지에 따라 네트워크 유형을 Domain이나 Private으로 바꿔야 하는데 이에 대해서는 다음의 글에서 소개한 적이 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Hyper-V VM의 Internal Network를 Private 유형으로 만드는 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11299'>http://www.sysnet.pe.kr/2/0/11299</a> </pre> <br /> 그런데 사실 WinRM 보호를 위한 것이어서 공개된 네트워크 망에서 원격 관리를 켜지 않도록 하기 위한 제한으로 보이기 때문에 굳이 저 오류가 나왔다고 해서 해결하려고 하지 않아도 됩니다. 단지 그래도 해결하고 싶다면, Set-NetConnectionProfile로 다음과 같이 Private이나 AD 참여한 PC에서는 명시적으로 Domain으로 바꾸면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Set-NetConnectionProfile -Name "...network name..." -NetworkCategory Private </pre> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1692
(왼쪽의 숫자를 입력해야 합니다.)