'메모리 최적화'에 해당되는 글 2건

  1. 2011.04.25 윈도우즈 메모리 최적화의 구현 by movsd 2
  2. 2010.07.17 프로그램의 메모리 사용량 줄이기 by movsd

지난번의 메모리 사용 분석의 예에 이어, 이번에는 메모리 정리 유틸리티를 만들 수 있는 핵심 코드를 가지고 또다른 유틸리티를 하나 만들어 본다. 어렵게 생각할 것 없이 자기 취향이나 요구사항에 맞게 이 핵심코드를 포장하면 바로 완성이다.

원리

NT계통의 윈도우즈에서 사용되는 메모리 최적화 프로그램의 원리는 매우 단순하다. 즉, 현재 시스템에 돌아가고 있는 프로세스마다 SetProcessWorkingSetSize() 또는 EmptyWorkingSet()를 호출하는 것이다. 이 단순한 원리를 어떻게 사용자에게 제공하는가에 따라 CleanMem같은 모양을 가질 수도 있고, Minimem이나 Beautiful Memory같은 모양이 나오기도 한다.

위에서 한줄로 쓴 원리를 코드로 표현할 때에는, 현재 시스템에 돌아가고 있는 프로세스를 찾는 방식에 따라 몇가지 구현이 가능하다. 예를 들어, 윈도우즈 SDK에 포함된 예제중에 하나는 프로세스 목록을 얻기 위해 레지스트리를 읽어와서 그 내용을 해독한다. 다른 방법으로는 toolhelp32로 분류되는 API를 이용하는 방법이 있다. 이 방법을 이용해서 메모리 최적화 프로그램의 원리를 C 코드로 표현하면 다음과 같다.

HANDLE hProcess, hSnap;
PROCESSENTRY32 pe32;

hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnap == INVALID_HANDLE_VALUE)
	return;

pe32.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(hSnap, &pe32)) {
	CloseHandle(hSnap);
	return;
}

do {
	hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE,
				pe32.th32ProcessID);
	if (hProcess) {
		EmptyWorkingSet(hProcess);
		CloseHandle(hProcess);
	}
} while (Process32Next(hSnap, &pe32));
CloseHandle(hSnap);
어떤 방식으로 루프를 구성하든지, OpenProcess()SetProcessWorkingSetSize() 또는 EmptyWorkingSet() 함수로 구성되는 루프내용은 동일하다.

요구사항

이미 있는 구현은 위에서 언급한 세가지 말고도 아주 많다. 아무리 그렇게 많아도 원리는 모두 똑같은 것이니 아무거나 자기 취향에 맞는 것을 골라서 사용하면 된다. 여기서 다시 만드는 이유는 다음과 같은 요구사항을 만족시키는 기존의 구현을 찾지 못했기 때문이다.

  • 간단한 원리를 실행하기 위해 배보다 배꼽이 더 커서는 안된다. 비주얼 베이직 런타임이나 .NET 프레임웍을 따로 설치하는건 딱 질색.
  • 실행 파일 자체가 뭔지 모를 도구로 압축되어 있는 것은 안된다. 이런 것은 도리어 메모리를 더 많이 소비하게 만들고 많은 경우에 보안사고로 연결된다. 바이러스 감시 프로그램이 압축된 실행파일을 경고하는 것은 다 이유가 있다.
  • 설정파일을 메모장으로 언제든지 읽어서 쉽게 설정을 바꿀 수 있어야 한다. 레지스트리 깊숙히 설정을 숨겨놓는 것은 설정할 때에도 곤란하고 나중에 삭제할 때에도 골치 아프다.
  • 한번 설치하면 일일이 신경쓰지 않고 잊어버려도 상관없어야 한다. 위의 작동 원리는 NT계통의 윈도우즈에서만 돌아가니까 NT 서비스로 돌아가면 좋겠다. 서비스로 돌아갈거면 자체적으로 사용하는 메모리 양도 적어야 한다.
  • 메모리 정리하지 않을 프로그램을 지정할 수 있어야 한다. 어떤 프로그램은 이미 자체적으로 메모리를 정리하고 있으므로 따로 해줄 필요가 없다. 불필요하게 건드리면 오히려 그 프로그램의 실행만 방해한다.
이런 요구사항은 주관적이고 개인적 취향일 뿐이지만, 이와 비슷한 요구사항에 맞는 메모리 관리 프로그램을 찾는다면 아래에서 다운로드 받을 수 있다.

설치

압축파일을 받아 적절한 곳에 압축을 풀면 다음과 같은 두개의 파일이 나온다.

wsmin.exe  (13312 바이트)  NT 서비스 실행파일
wsmin.ini  (3087 바이트)  간단한 설정파일

wsmin.exe는 NT서비스 실행파일이며 윈도우즈2000이상의 시스템에서만 작동한다. 설치를 완료하기 위해서는 NT서비스 데이타베이스에 등록해야 한다. 이는 명령행에서 다음과 같이 입력하면 된다. (관리자 권한이 필요하다.)

wsmin.exe /i
(이것이 귀찮거나 어렵다면 설치용 실행파일을 받아서 설치하는 수도 있다. 그런데, 설치용 실행파일로 설치하면 배보다 배꼽이 더 큰 느낌이 있다.)

설치가 완료되면 시스템 시동때마다 자동으로 시작하게 기본값이 설정되어 있다. 이것을 바꾸려면 시작-제어판-관리도구-서비스를 열어서 Working Set Minimizer라고 된 것을 찾아 바꾸어주면 된다. (고수들은 명령행에서 services.msc라고 치는 것을 좋아한다.)

Working Set Minimizer 서비스가 등록된 상태

설정

설정파일은 wsmin.ini이며, 없어도 상관없다. 설정파일이 없으면 밑에서 설명하는 기본값들을 가지고 서비스가 작동한다. 만일 설정파일이 있다면 실행파일과 같은 디렉토리에 있어야 한다. 설정을 바꾸는 방법은 메모장(notepad.exe)같은 편집기로 열어서 이하에서 설명하는 항목을 편집하고 저장하면 된다. 변경한 설정을 적용하기 위해서는 wsmin.exe를 다시 시작한다. 이를 위해서는 시작-제어판-관리도구-서비스를 열어서 Working Set Minimizer라고 된 것을 찾아 "다시 시작"을 누르면 된다.

설정할 수 있는 것들 중 중요한 것은 메모리 정리하는 시간 간격과 정리하지 않을 파일들의 목록이다. 그외에도 심각한 오류를 기록할 로그파일 이름과 부팅이 느린 시스템에서 처음 메모리 정리까지 기다릴 시간을 설정할 수 있다.

메모리를 정리하는 시간간격은 1분부터 480분(= 8시간)사이의 값을 분 단위로 적어준다. 기본값은 30분이다.

[Settings]
Interval=60
이 예에서는 60분(=1시간)마다 한번씩 메모리 정리를 하게 지정했다.

그런데 이 프로그램은 시스템이 시작할때 자동으로 실행하는 것을 상정하고 있기 때문에, 프로그램 실행 이후 최초로 메모리 정리 작업을 하기까지 일정시간을 기다린다. 이는 부팅이 느린 시스템에서 다른 서비스들과 드라이버들을 올리기 전에 섣불리 메모리 정리를 시도하다가 시스템을 불안정하게 만드는 것을 방지하기 위해서 정한 값이다. 이 값도 역시 1분부터 480분 사이의 값을 분 단위로 설정할 수 있으며, 기본값은 1분이다.

[Settings]
InitialWait=3
이 예에서는 맨 처음으로 메모리를 정리하기까지 3분을 기다리게 설정했다. 하지만 오래된 시스템에서 안티바이러스를 여러개 실행하고 키보드 보안 프로그램을 겹겹이 띄우는 시스템이 아니면 보통 기본값으로도 충분하다.

메모리 정리에서 제외할 실행파일 이름은 [Ignore]섹션에 한줄에 하나씩 적어준다. 예를 들어, 서비스 관리자에는 손대지 않으려면

[Ignore]
services.exe
이렇게 설정하면 된다. 그런데, 어떤 경우에는 전혀 다른 프로그램이 같은 실행파일 이름을 가지는 경우가 있다. 이때 그중 일부만 working set을 정리하고 나머지는 손대지 않도록 하려면, 제외할 실행파일의 경로명 전체를 써준다.
[Ignore]
C:\WINDOWS\system32\services.exe
실행파일 경로에서 공백문자는 모두 유의미하다고 인식한다.

삭제

이 프로그램을 삭제하려면, 우선 서비스를 종료하고 명령행에서 다음과 같이 입력하여 서비스 데이타베이스에서 삭제한다.

wsmin.exe /u
그 다음에 실행파일을 지우고 설정파일이나 로그파일도 지우면 삭제가 완료된다. (설치용 실행파일로 설치했다면 제어판을 통해서 삭제하면 된다.)

덧붙임

이미 언급한 바와 같이, working set을 줄이는 메모리 최적화는 메모리 할당/해제를 제대로 하지 않는 그저 그런 품질의 프로그램을 많이 실행할 때에 큰 효과가 있다. 그런 프로그램은 아예 사용하지 않는다면, 그 어떤 메모리 최적화 프로그램도 소용이 없다. 이런 경우에는 메모리 최적화 프로그램을 사용하는 만큼 메모리를 손해보는 것 밖에 없다.

게임은 메모리 정리 프로그램을 써봤자 소용없는 경우다. 가만 생각해보면 게임의 실행속도를 높이기 위해 굉장히 큰 캐쉬를 사용하는게 너무나 당연해서, 그 틈에 메모리 낭비가 있을 법도 하다. 그런데도 메모리 정리가 소용없는 이유는 게임을 실행하는 중이라면 그 게임이 실질적으로 유일한 작업이나 다름없기 때문이다. 바꾸어 말하면, 게임을 하는 중이라면 워드프로세서, 웹브라우저, 기타 등등 여러개의 작업이 게임과 함께 동시에 돌아갈 가능성이 별로 없다. 이런 상황에서는 메모리 정리를 해봐야 게임만 느려지고 다른 프로그램을 위해 메모리를 확보하는 효과도 없다.

파일

다운로드 전에: 이 프로그램을 실행함으로써 발생할지도 모르는 어떠한 불상사에 대해서도 제작자는 책임지지 않는다. (이는 더도 아니고 덜도 아니고 딱 마이크로소프트 수준의 사용허가 문구이다.)

wsmin.zip은 아래 표에 적힌 파일들이 담긴 압축파일이다. 압축을 풀고 NT서비스 데이타베이스에 등록해야 설치가 완료되며, 이에 관해서는 위의 설명을 참조하면 된다. 이런 작업이 귀찮거나 어려운 사람들은 wsminsetup.exe를 받아서 실행하면 설치가 된다. 이것은 똑같은 내용물을 Inno Setup을 이용하여 설치파일로 만든 것이다.

단순 압축 파일:   설치 실행 파일:
wsmin.zip의 내용물
wsmin.exe  (13312 바이트)  NT 서비스 실행파일
wsmin.ini  (3087 바이트)  간단한 설정파일
Posted by movsd
,
메모리 정리 프로그램 자체를 검색하다가 찾아온 것이라면 다음에 이어지는 포스팅에 C로 표현한 핵심코드, 기존 프로그램 몇개에 대한 링크와 개인적 요구사항에 맞추어 새로 하나 만든 것이 있고, 이 포스팅은 working set자체를 어떻게 해석하는가에 중점이 맞추어져 있다는 것을 미리 밝혀둔다.

윈도우즈의 메모리를 관리/정리/최적화해준다는 프로그램들을 많이 보았을 것이다. 이런 프로그램들은 항상 그 유효성에 관해 논란이 있기 마련이다. 최근에 웹광고를 비켜가는 방법으로 사용하는 가짜 웹서버메모리 사용량을 줄였으면 좋겠다는 개선점 제안을 받고, 이것을 구현하는 과정에서 한동안 잊어버리고 있던 부분을 다시 찾아보고 정리해 볼 기회가 되었다.

메모리 정리 방법

메모리 정리 프로그램은 윈도우즈95가 시장을 지배하던 시절에도 있었다. 당시에 사용되던 방법은 RAM의 용량만큼 메모리 할당을 시도하는 것이었다. 메모리 정리와 전혀 관계없이 보이는 이 작업이 효과가 있는 듯이 보인 이유는 단 하나이다. 운영체제가 메모리 할당 요청에 응하면서 당장 쓰이지 않는 부분을 swap파일로 옮겨버린다. 그 결과로 이 프로그램이 메모리를 모두 돌려주고 종료하면, RAM이 텅 비어버린다. 이 원리를 이해하고 나면, 이 방식은 실제로 메모리를 정리하지 않는다는 것을 금방 알게된다. 자연스럽게 사기논란이 일어났고, 요즘은 초보 프로그래머가 아니면 이 방식을 쓰지 않는다.

다른 방식은 NT계통의 윈도우즈가 시장을 지배하면서 나타난 것으로, 윈도우즈가 내부적으로 사용하는 방법을 이용하는 것이다. 작업관리자로 메모리 사용량을 지켜보다 보면, 어떤 프로그램을 최소화하면 메모리 사용량이 확 줄어버리는 것을 본 적이 있을 것이다. 그것을 모든 프로그램에 적용하자는 것이 요즘 메모리 정리 프로그램의 기본 아이디어이다. 이는 윈도우즈 API로 제공되는 SetProcessWorkingSetSize()EmptyWorkingSet() 함수를 사용한다. 이 방법은 brhttpd를 개선하는데 사용된 방법이기도 하다.

자신의 프로그램의 메모리 사용량을 줄이기 위해서 이 방법을 사용하고자 한다면, 적절한 위치에

SetProcessWorkingSetSize(GetCurrentProcess(), -1, -1);
이렇게 한줄 집어 넣음으로써, 지금까지 사용하던 메모리중 당장 쓰는 것을 빼고는 다 털어버리게 한다. 그 다음에는 page fault 메카니즘을 통해 다시 필요한 부분을 메모리에 올린다. 당연한 얘기이지만, page fault는 프로그램의 실행속도에 영향을 주게 된다. 그럼에도 불구하고 .net환경에서 개발하는 사람에게는 메모리 사용량을 줄이는 방편으로 이 방법이 권장되기도 한다.

문제의 핵심: working set

윈도우즈 API를 사용해서 메모리를 정리한다고 해도 논란이 잦아들지는 않았다. 논란이 아직도 계속되고 있다는 것을 웅변하듯이, 메모리 정리 프로그램 중의 하나인 CleanMem은 홈페이지의 아래 절반을 자기 프로그램이 효과가 있다고 주장하는 것에 할애하고 있다. 이 프로그램은 프로세스마다 EmptyWorkingSet() 함수를 호출하는 매우 단순한 비주얼베이직 프로그램이다. 그러므로 윈도우즈가 효과가 있는 만큼 이 프로그램이 효과가 있다는 주장이 나름대로 설득력이 있다. 그러나, 이 프로그램이 효과가 없다는 주장도 마찬가지로 설득력이 있다. 그 이유는 working set에 대한 해석에 달려있다.

Working set은 MSDN페이지에 잘 설명되어 있듯이 현재 물리적 메모리(RAM)에 상주하고 있는 가상 메모리 부분이다. 작업관리자가 보여주는 메모리 사용량은 바로 이 working set의 양이다. Working set은 다시 공유 가능한 부분과 공유 불가능한 부분으로 나누어진다. 보통, 공유가 불가능한 부분은 현재 프로세스의 데이타이고, 공유가 가능한 부분은 DLL이나 프로세스간 통신을 위한 공유메모리 같이 말그대로 공유하도록 만들어진 부분이다. 공유가 가능한 부분의 대부분은 실제로 공유된다.

Working set의 구분을 보면 바로 떠오르는 의문은, 공유하고 있는 부분을 각각의 프로세스의 메모리 사용량으로 계산하는 것이 정확한 것인가 하는 질문이다. 예를 들어, kernel32.dll같이 모든 사용자 모드 프로그램이 공유할 수 밖에 없는 DLL은 프로세스의 갯수만큼 RAM을 사용하고 있다고 계산되는데, 실제로 점유하고 있는 RAM의 양은 DLL하나에만 해당한다. (그렇지 않다면 DLL이 아니다!) 프로세스가 오로지 하나만 돌아가고 있다면 이 계산은 맞지만, 그렇지 않다면 작업관리자가 보여주는 메모리 사용량은 과다계상되고 있는 것이다.

두번째로 생기는 의문은, working set을 다 비우고 처음부터 다시 구성한다면 과연 효율적으로 메모리 사용량을 재편할 수 있는가 하는 질문이다. 아까의 예를 계속하면, kernel32.dll은 어차피 메모리에 상주하고 있을 것이다. 그렇다면 프로세스 하나의 working set을 비우고 다시 (soft) page fault를 거쳐 kernel32.dll의 필요한 부분만 이 프로세스의 working set에 포함한다면, 메모리 사용량이 줄어들었다고 볼 수 있는가 하는 의문을 가지게 된다.

사례 분석

불행한 일이지만, 정답은 없다. 정답이 있었다면 애초에 논란이 생기지도 않았다. 결국 사용자로서 할 수 있는 일은, 자신이 주로 사용하는 프로그램들이 메모리 정리 프로그램의 효과를 볼 수 있는지 알아보고 결정하는 수 밖에 없다. 개발자라면 자신의 (메모리 누수가 없는) 프로그램의 working set을 분석해보고 SetProcessWorkingSetSize() 함수를 쓸 것인지 결정할 수 있다.

간단한 working set 분석의 예로 VMMap을 이용하여 brhttpd에서 메모리 정리 이전과 이후를 비교해보자. 이를 위해서 brhttpd의 자체 메모리 정리 기능은 사용하지 않도록 설정하고 실행한다.

[Settings]
MemCleanTimeOut=-1
이제 VMMap을 실행하여 brhttpd.exe를 선택하면 다음과 같은 화면이 보인다. 메모리 정리 이전의 VMMap 실행화면 이 화면은 메모리 정리 이전의 사용량을 보여준다. 화면에서 보이듯이 시스템 DLL이 차지하는 양이 꽤 많다. 이제 메모리 정리 프로그램이 하듯이 working set을 비운다. 메뉴에서 Empty Working Set을 선택하거나 Ctrl-E를 누른다. 그리고 나서 새롭게 재편된 메모리 사용량을 보기 위해 Refresh를 선택한다. 메뉴에서 Refresh를 선택하거나 F5를 누른다. 메모리 정리 이후의 사용량은 다음과 같은 화면으로 나타난다. 메모리 정리 이후의 VMMap 실행화면

그림에 나와있는 숫자만 읽어보면 전체 working set이 1680KB에서 188KB로 줄어들었으니 90%에 가까운 효율을 보이는 메모리 절약의 효과가 있다고 볼 수도 있다. 이것을 좀더 자세히 들여다 보기 위해, 메모리 사용량이 차이를 계산해보면 다음의 표와 같다.

EmptyWorkingSet() 함수로 절약한 메모리 양 (단위: KB)
TypeTotal WSPrivate WSShareable WSShared WS
Total-1,492-212-1,280-1,268
Image-1,260-104-1,156-1,144
Mapped File-480-48-48
Shareable-760-76-76
Stack-20-2000
Private Data-88-8800

이 표를 보면 메모리 절약의 효과의 대부분이 공유가능한 working set부분에서 나타났음을 볼 수 있다. 실제로 공유된 부분이 절감된 수치(오른쪽 마지막 열)를 보면 공유 가능한 부분은 실제 공유가 이루어지고 있었다. (12KB의 차이는 brhttpd가 한번만 실행되었기 때문에 그 실행파일 이미지는 공유할 기회가 없어서 생긴 것이다.)

공유가 되고 있었던 부분중에 절약된 부분을 알아보기 위해 VMMap의 아래쪽 창을 보면, 공유가 되고 있었던 Image 영역은 대부분 시스템 DLL이다. 여기서 절약된 1MB 남짓한 부분중에, brhttpd.exe 실행파일에 20KB를 할당했던 것을 필요한 부분 8KB만 남기고 털어버리는 12KB의 절약분을 제외하면, 실제로 RAM에서 없어졌을 것 같이 보이지 않는다. 다시 말하면, brhttpd가 쓰는 부분만 골라서 사용량을 계산했을뿐이지, 실제로 RAM에서 시스템 DLL을 내려서 메모리를 정리했을 가능성이 없다는 것이다. Mapped file이나 shareable 영역도 비슷한 얘기가 이어진다.

이렇게 생각해보면 working set을 비워서 얻게되는 실질적인 절약효과는 공유가 불가능한 working set에서 생긴다는 것을 알 수 있다. 이미 설명한 바와 같이 공유가 불가능한 working set은 프로그램의 데이타이다. 위의 표에서는 image영역, 스택 영역, 그리고 프로그램 데이타 영역에서 메모리 절약이 있다.

위의 예에서 image영역의 비공유 working set의 대부분이 사라진 것은 메모리 정리의 효과가 빛을 발하는 부분이다. 둘째로, 스택영역은 운영체제가 관리하는 부분이므로, 운영체제가 약간 여유를 준 부분을 빡빡하게 만드는 효과가 있다. 하지만, 원래 운영체제에서 메모리를 방만하게 관리하지 않으므로, 절약되는 바이트 수는 미미할 수 밖에 없다. 마지막으로, 순수한 프로그램 데이타에서 절약되는 부분은 working set을 비우는 것의 효과가 제일 중요한 부분이다. (여기에도 환경변수영역같이 운영체제가 생성하는 부분이 포함되기는 하지만, 그 크기는 상대적으로 작다.) 이 부분에는 프로그램을 짤때 메모리의 재사용을 미리 고려할 수 없어서 생기는 (어쩔 수 없는) 낭비도 포함된다. 사용자의 입장에서는 이 부분이 절약되는 것이 가장 기분 좋은 일이다.

brhttpd의 경우, 비공유 working set이 200KB정도 절약되는 것은, 정리 이전에 350KB남짓한 비공유 working set을 사용하고 있었다는 것에 비추어 보면 엄청난 양의 절약이라고 판단된다. 이 판단에 기초하여 working set을 줄일 수 있는 선택이 가능하도록 개선하였다. 그러나, 실제로 절약되는 부분이 200KB 정도에 불과하다면, 몇 GB짜리 RAM의 0.1%도 안되는 미미한 양이라고 생각할 수도 있다. 한편, 절약되는 200KB의 대부분은 빈 페이지를 보여주는 작업을 할 때에 다시 사용된다. 그래서, 메모리 정리에 관한 부분을 아예 신경쓰지 않도록 설정할 수도 있게 지원한다.

아직도 남은 논란

사용자의 입장에서 안 쓰는 프로그램 데이타 영역을 절약하는 것은 기분 좋은 일이라고 이미 말한바 있다. 그런데, 개발자라면 프로그램 데이타가 차지하는 working set이 많이 감소한다는 것을 다른 관점에서 봐야한다.

순수 프로그램 데이타가 상당히 크게 늘어나면서 당장 필요하지 않은 부분이 아주 많다면, 그것은 어디인가 메모리 누수가 있기 때문일 가능성이 매우 높다. 그렇지 않다면, 자체 캐쉬 알고리즘을 사용하고 있는데, 그 구현에 뭔가 헛점이 있다는 말이다. 그렇다면, 지금 당장은 임시방편으로 SetProcessWorkingSetSize() 함수를 이용해 메모리를 정리하더라도, 언젠가는 코드를 꼼꼼히 점검해봐야 하는 상황인 것이다.

한편, 이런 개발자의 관점은 사용자에게 새로운 시각을 제공한다. 프로그램 데이타 관리를 허술하게 해서 메모리 정리 프로그램을 따로 쓰게끔 만드는 응용프로그램을 쓸 것인가, 아니면 꼼꼼하게 짜여진 응용프로그램을 쓰고 메모리 정리 프로그램 같은 것은 잊어버릴 것인가 하는 질문을 해보는 것이 그것이다. 꼼꼼하게 짠 응용프로그램만 쓴다면 메모리 정리 프로그램을 써봐야 별 효과가 없을 것이고, 그렇게 되면 또다시 논란에 불을 지피는 묘한 피드백이 생긴다.

Posted by movsd
,