Memory Pool Manager 소멸에 대해...

프로그래밍 일반에 관한 포럼입니다.

Moderator: 류광

Locked
쌀밥
Posts: 1058
Joined: 2003-02-02 20:23
Location: THQ Inc.
Contact:

Memory Pool Manager 소멸에 대해...

Post by 쌀밥 »

최근 메모리 메니져를 만지고 있는데요...
Memory Pool 구현한 코드들이 꽤 많지만,
그중에서 제가 보고 있는 것은 Loki 의 SmallObject 입니다.

책으로 (Modern C++ design) 설명도 자세히 되어있고 해서 가벼운 마음으로 가져다 사용했는데...
이게 생각지도 못한 문제들이 많이 있더군요...
게다가 처음에는 Loki::SmallObject 에 버그가 있어서... 그거 잡느라 몇일 날리고...;;

지금 당면한 문제는 메모리 메니져가 singleton 으로 사용되고 있는데요.
전역으로 선언된 클래스가 메모리 메니져로 부터 메모리를 할당 받아서 사용하고 있다가
프로그램이 종료되는 순간에 메모리 메니져 보다 나중에 죽는 경우가 있어서 문제가 되고 있습니다.

코드로 설명하면 이렇습니다.

Code: Select all

class cMyClass
{
    int * pVal;
public:
    cMyClass() { pVal = MyMemoryPoolManager::new int; }
    virtual ~cMyClass() { MyMemoryPoolManager::delete pVal; }
};

static cMyClass s_myVal;
이렇게 했는데 s_myVal 보다 MyMemoryPoolManager 싱글톤이 먼저 소멸되고나서
~cMyClass() 가 호출되면 MyMemoryPoolManager::delete 가 비정상적으로 동작한다는 것이죠...

해결 방법을 여러가지 생각해 냈는데요..... Loki 개발자 분들과 이야기를 나누어 보고는... 쓸 수 있는 방법은 static 으로 클래스를 생성시키지 말아야 한다는 결론 밖에 내지 못했습니다.

현재 Loki::SmallObject 의 LifetimePolicy 는 Loki::NoDestroy 인데, 이것은 소멸자 호출이나 메모리 해제를 아에 한지 않는 방식입니다.
그러니까 이렇게 하면 static/전역 변수들이 다 죽어 나간 뒤에도 끝까지 살아 남는 것이죠.
그리고나서, 메모리는 (말하자면) leak 상태로 처리되고 OS 에 의해 최종 해지 되도록 하는 것입니다.

근데, 클라이언트 프로그래머 분들은 눈치 채셨겠지만, Win9X 에서는 이런식으로 게임 클라이언트를 종료 시키게 되면, 메모리 관리에 애로 사항이 만개 한다는 사실을 아실겁니다..

그래서 Loki::NoDestroy 는 사용할 수 없고, Loki::PhoenixPolicy 가 가능성이 높다고 생각하고 코드를 수정해서 돌려봤더니... 이것도 역시 심각한 문제가 발생하더군요...
(이 문제를 설명하려면 길어지는데, 간단히 말하면, 메모리 메니져가 죽으면서 자신이 예전에 할당해 줬던 메모리들을 모두 한꺼번에 해지 해버리고 죽는데, 그 뒤에 전역 변수의 소멸자가 수행되는 과정에서 이미 해지된 메모리들을 가지고 뭔가 작업을 하다가 access violation 예외가 뜬다는 겁니다)

그래서 결국 Loki::SingletonWithLongenvity 를 쓸 수 밖에 없게 되었고,
그 말은 즉, static/전역을 모두 Singleton 으로 바꾸어서 소멸되는 순서를 정해주어야 한다는 것입니다...
그래서 이 부분을 오늘 작업하던 중이었는데
모든 static 변수를 Singleton 으로 바꾸는 작업이 단순한게 아니더군요...;



앞에서 설명한 문제점은 비단 Loki 에만 있는 문제가 아니라는 걸 아실겁니다..
모든 종류의 메모리 관리자는 항상 자신으로 부터 할당 받은 메모리를 사용하는 녀석들 보다 나중에 죽지 않으면 안됩니다.
즉 전역으로 선언된 녀석이 메모리 관리자로부터 메모리를 받아서 사용하는 경우라면 Loki 만이 아니라 모든 메모리 관리자에 문제가 있을 수 있지요...
(OS 를 믿고 맏길 수 있는 서버 프로그래밍 같으면 문제가 안되겠지만...)

사실, 이거는 메모리 메니져 문제가 아니라 싱글톤 문제입니다.

다른 분들은 이런 문제를 어떻게 해결 하셨는지 궁금합니다....

*PS : 앞에서 설명한 내용들은 다음 주소에서 보실 수 있습니다.
http://sourceforge.net/tracker/index.ph ... tid=396644
맨 아랫쪽 부터 위로 올라가면서 읽어야 합니다. 시간 순서가 그렇습니다...
I want to live in korea, making programs, but...
http://wrice.egloos.com
비회원

Post by 비회원 »

윈도우 전용이라면

#pragma init_seg를 사용해서 해결할 수 있을 것 같습니다.

http://msdn.microsoft.com/library/defau ... agm_14.asp

lib로 지정하면 충분할 것 같고, 정말 배를 째야 하면 compiler로 지정해보세요.
Yeol
Posts: 50
Joined: 2005-06-24 15:40
Contact:

객체들간의 의존성

Post by Yeol »

말씀하신 문제를 저도 겪은 적이 있는데, 이 문제는 Loki나 싱글톤만의 문제가 아니라
서로 의존관계에 있는 객체들에서는 항상 발생하는 문제입니다. 객체들간의 의존성을
줄이는 것이 최선인 것 같습니다.

문제를 해결하기 위해 모든 전역 객체를 수명 제어 싱글톤으로 만드는 것은
너무 과도한 해법인것 같고, 어쩔 수 없이 의존관계가 발생하는 객체들만 따로
분류해서 수명 제어를 하는 것이 좋을 것 같습니다.

PS. 싱글톤이나 스마트 포인터등을 사용할때 Loki 라이브러리를 자주 사용하는데,
단위전략들 중에서 필요한 기능들을 조합해서 쓸 수 있다는 점이 참 좋더군요.
그런데 Loki를 쓰다보니 싱글톤, 스마트 포인터를 너무 남발하게 되는 것 같아서
요즘은 쓰기전에 항상 정말 꼭 써야 하는가 한번더 고민하며 사용하고 있습니다.
쌀밥
Posts: 1058
Joined: 2003-02-02 20:23
Location: THQ Inc.
Contact:

Post by 쌀밥 »

표준에 따르면 static 객체들의 생성 소멸 순서는 정해져 있지 않기 때문에, 각 컴파일러 고유의 명령을 사용해서 문제를 해결하는 방법도 나쁘지는 않을것 같습니다.
init_seg 라는건 처음 들었는데, 좋은 정보 감사합니다.

꼭 필요한 곳에만 싱글톤으로 교체하고, 문제가 없는 부분은 그대로 static 으로 남겨두는 방법도 좋은 방법이라고 생각합니다.
성능면에서도 좋고요...

근데, 두가지 방식이 같이 공존 한다는게 조금 불편하군요...
이런 방법도 쓰고 저런 방법도 쓰고....
나쁘다고는 할 수 없지만, 한가지로 통일하고 싶다는 생각이 듭니다.

비유를 하자면, array 를 항상 vector 로 교체해서 사용하지는 않지요...
그래서 array 방식과 vector 방식을 같이 적절히 사용하게 되는데..
여기에 myvector 같은, 메모리 풀을 사용하는 것 까지 추가 되게 되면, 조금 머리가 복잡해지기 시작하니까요...;

고민입니다...;;;
I want to live in korea, making programs, but...
http://wrice.egloos.com
hurguy
Posts: 53
Joined: 2002-03-04 09:00
Location: 12 장미단

그걸로 저도 순서 정해보려 해봤지만 단순 한프로젝트에서만 되

Post by hurguy »

비회원 wrote:윈도우 전용이라면

#pragma init_seg를 사용해서 해결할 수 있을 것 같습니다.

http://msdn.microsoft.com/library/defau ... agm_14.asp

lib로 지정하면 충분할 것 같고, 정말 배를 째야 하면 compiler로 지정해보세요.
lib에도 사용되고 그걸 참조하는 프로젝트에도 사용될텐데 두 프로젝트에서 동시에 사용되는

단일체의 순서를 정할 수 없을 텐데요.
어둠은 어둠으로 맞서라 그러면 재밌는 일이 생긴다.
hurguy
Posts: 53
Joined: 2002-03-04 09:00
Location: 12 장미단

Re: Memory Pool Manager 소멸에 대해...

Post by hurguy »

쌀밥 wrote:그래서 결국 Loki::SingletonWithLongenvity 를 쓸 수 밖에 없게 되었고,
그 말은 즉, static/전역을 모두 Singleton 으로 바꾸어서 소멸되는 순서를 정해주어야 한다는 것입니다...
저도 이문제때문에 이것저것 생각하고 시도 해봤는데 자동적으로 할 방법이 생각이 안납니다.

메모리 해제를 명시적으로 각 단일체 클레스에 Destroy()함수를 만들어 호출 후 프로그램 종료를

시켜버렸습니다.
어둠은 어둠으로 맞서라 그러면 재밌는 일이 생긴다.
쌀밥
Posts: 1058
Joined: 2003-02-02 20:23
Location: THQ Inc.
Contact:

Post by 쌀밥 »

결국 어떻게 했는가 하면...

앞에서 말씀 드렸던것과 같이

static 들을 Loki::SingtonWithLongevity 형태의 싱글톤으로 바꾸었습니다.

모두 다 바꾼 것은 아니고, 메모리 메니져를 사용하는 경우에 한해서 바꾸었습니다.

의외로 복잡하지 않게 수정하였습니다.

다만, 두가지 마음에 들지 않았던 점은...

Loki 자체의 수명 선언 방식이... ::GetLongevity( Type *) 의 함수들을 만들어주는 방식으로 하는 건데...
이 함수들이 너무 많아지는게 좀.. 마음에 안들더군요..

소스 포지에 보면 이런 문제를 개선할 수 있는 코드가 있는데
http://sourceforge.net/forum/forum.php? ... m_id=93008
이 글 쓴 분의 이야기 처럼.. 왜인지 문법은 맞는거 같은데 VC++ 2003 에서는 컴파일 에러가 나더군요...
'컴파일러 내부 에러' 라던가...;;

그리고 마음에 들지 않았던 두번째는...

이.. Longevity 관련 코드에 버그가 있었습니다;;;
레포팅을 하기는 했는데, 책에 있는 설명이 틀린건지 소스 코드가 틀린건지 아직도 확신이 안서는 군요....
http://sourceforge.net/tracker/index.ph ... tid=396644
I want to live in korea, making programs, but...
http://wrice.egloos.com
조순현
Posts: 115
Joined: 2004-07-02 22:14
Location: 네오위즈 게임즈
Contact:

Post by 조순현 »

SingletonWithLongevity 방법 말고 다른 방법도 있는 것 같아서 소개합니다.

쌀밥님이 알려 주신 http://sourceforge.net/tracker/index.ph ... tid=396644에서 static initialization order fiasco와 관련된 문제라는 것을 알게 되었습니다. Web에서 그 용어를 잠깐 검색해 보았지만, 이 문제에 알맞은 해법은 찾을 수 없었습니다. 그런데 그 중에 <C++ FAQs Second Edition> 책을 참고하라는 글이 있어서 그 책을 보았더니, 16.14 절부터 16.18 절까지가 이 문제와 관련된 내용을 다루고 있었습니다. 그 책을 보기 힘든 분을 위해, 이 문제와 관련된 부분 중의 일부를 제 맘대로 정리해 보았습니다.

그 책에선 nifty counter technique이라는 것을 소개하고 있습니다. Nifty counter technique은 소멸자와 생성자에서 계수를 증감시킴으로써, 처음으로 사용될 때에 생성하고 마지막으로 사용되고 난 이후에 소멸시키는 방법입니다. 이 방법의 단점은 계수기의 생성과 소멸로 인해서 성능에 악영향을 줄 수 있다는 것입니다.

다음은 그 책의 236쪽에서 인용한 source code입니다. Fred.hpp를 포함하는 모든 source file이 fredInit라는 정적 객체를 갖게 된다는 것이 핵심이며, 그 외의 설명은 주석만으로도 충분할 것 같아서 생략합니다. (그리고 책의 설명을 모두 적기 귀찮아서)

다음은 "Fred.hpp" header file입니다.

Code: Select all

class Fred {
public:
    Fred() throw();
protected:
    class Init; // This declares nested class Fred::Init
    friend Init; // So Fred::Init can access Fred::wilma_
    static Wilma* wilma_; // This is now a static pointer
};

class Fred::Init {
public:
    Init() throw() { if (count_++ == 0) wilma_ = new Wilma(); }
    ~Init() throw() { if (--count_ == 0) delete wilma_; }
private:
    static unsigned count_;
};

static Fred::Init fredInit; // The key: a static Fred::Init object defined in the header file

inline Fred::Fred() throw()
{
    cout << "Fred ctor\n";
    wilma_->f(); // This is now safe
}
다음은 "Fred.cpp" source file입니다.

Code: Select all

#include "Fred.hpp"

unsigned Fred::Init::count_ = 0;
Wilma* Fred::wilma_ = NULL;
위의 방법을 memory 관리자의 단일체에 적용하면, 문제를 해결할 수 있지 않을까 생각합니다. 즉 Wilma를 memory 관리자로 바꾸고, Fred를 memory 관리자의 단일체로 바꾸면 될 것 같군요. 귀찮아서 시험은 안 해 봤습니다. 8)
쉬운 것은 올바르다.
쌀밥
Posts: 1058
Joined: 2003-02-02 20:23
Location: THQ Inc.
Contact:

Post by 쌀밥 »

좋은 답변 감사합니다.

짧지만, 내부적으로 많은 트릭이 사용된 코드군요...

저도 돌려보지는 않았지만 제 생각에는 잘 동작할 것 같아 보입니다...

fredInit 는 cpp 파일 갯수만큼 생성되지만, Wilma 는 한번만 생성되겠군요...

내일 출근하면 한번 해봐야겠네요...
I want to live in korea, making programs, but...
http://wrice.egloos.com
Yeol
Posts: 50
Joined: 2005-06-24 15:40
Contact:

Post by Yeol »

Nifty counter technique 상당히 재미있는 기법이네요.
이 기법을 사용하는 싱글톤 객체들이 소멸자에서 서로 호출하지만 않는다면 싱글톤에 대한 참조 무효화 현상이 발생하지는 않겠네요.
Loki 에서 사용하기 위해 Lifetime 단위전략으로 만들어봤습니다.

Code: Select all

typedef void (*PDESTROYSINGLETON)();

template <class T>
class NiftyCounter
{
public:
	static void ScheduleDestruction(T*, PDESTROYSINGLETON pFunc)
	{ pDestroySingleton_ = pFunc; }

	static void OnDeadReference()
	{ throw std::logic_error("Dead Reference Detected"); }

private:
	static PDESTROYSINGLETON pDestroySingleton_;

	class Init
	{
	public:
		Init() throw() { count_++; }
		~Init() throw() { if (--count_ == 0 && pDestroySingleton_ != NULL) pDestroySingleton_(); }

	private:
		static unsigned count_;
	};
	friend Init;
};

template <class T>
unsigned NiftyCounter<T>::Init::count_ = 0;

template <class T>
PDESTROYSINGLETON NiftyCounter<T>::pDestroySingleton_ = NULL;
다음과 같이 사용하면 됩니다.
정적변수도 같이 선언 해줘야 하는게 좀 번거롭지만,,,

Code: Select all

static NiftyCounter<MemoryManagerImpl>::Init memoryManagerNiftyCounter;
typedef Loki::SingletonHolder<MemoryManagerImpl, Loki::CreateUsingNew, NiftyCounter> MemoryManager;
쌀밥
Posts: 1058
Joined: 2003-02-02 20:23
Location: THQ Inc.
Contact:

Post by 쌀밥 »

밤에 자면서 생각해 봤는데, nifty 방식은... 앞서도 Yeol 님께서 말씀해주셨지만, 여러 단계의 소멸 순서를 정의할 수는 없을 것 같네요...

메모리 관리자 정도에 딱 적합한 방식인것 같습니다..
I want to live in korea, making programs, but...
http://wrice.egloos.com
쌀밥
Posts: 1058
Joined: 2003-02-02 20:23
Location: THQ Inc.
Contact:

Post by 쌀밥 »

연휴( ? ) 중에 Loki 가 0.1 버전을 내 놓았군요...

버그들이 좀 정리된 의미있는 버전인것 같습니다..
I want to live in korea, making programs, but...
http://wrice.egloos.com
Locked