ProudNet 유틸리티

콜렉션 사용하기

STL, ATL 등도 이미 콜렉션 클래스들(std.map, std.vector, CFastArray, CAtlArray, CAtlMap 등)을 제공합니다. 하지만 ProudNet은 STL과 ATL을 모두 사용하기 곤란한 상황에서나, 고속의 성능을 요구하는 상황에서 효과적인 클래스들을 제공합니다

빠른 메모리 관리자

ProudNet은 고성능 메모리 관리자가 내장되어 있어 개발자는 고성능 메모리 관리자를 이용하여 응용 프로그램의 처리 성능을 가속할 수 있습니다.

ProudNet에서 지원하는 메모리 관리자는 크게 다음과 같습니다.

- Lookaside allocator

Lookaside allocator 는 통상적인 memory pool 기법을 응용합니다. 만약 항상 동일한 크기의 메모리를 자주 할당/해제해야 한다면 Lookaside allocator를 사용하는 것이 좋습니다.

주요 메카니즘은 아래와 같습니다.

  • 새로운 메모리 블럭을 할당할 때, 새로운 시스템 메모리가 할당된다.

  • 메모리 블럭을 해제할 때, 해제된 블럭은 lookaside allocator에 반환된다.

  • 다시 메모리 블럭을 할당할 때 lookaside allocator에 반환되었던 메모리 블럭이 재활용된다.

이 과정은 매우 빠른 속도로 실행됩니다. OS 환경에서의 메모리 할당 속도보다 훨씬 빠릅니다.

하지만 단점도 존재합니다.

  • Lookaside allocator 는 항상 같은 크기의 메모리만 할당할 수 있습니다.

  • Lookaside allocator 로 할당된 메모리 블럭은 Lookaside allocator 가 파괴되기 전에 모두 해제되어야 합니다.

Lookaside allocator 를 쓰는 방법은 다음과 같습니다.

  • 먼저 Proud.CLookasideAllocator.New 메서드로 객체를 생성합니다. 전역 객체로 생성해도 됩니다.

  • Proud.CLookasideAllocator.Alloc 메서드로 메모리 블럭을 할당합니다.

  • 해제는 Proud.CLookasideAllocator.Free 로 합니다. Realloc은 따로 존재하지 않습니다.

  • 모든 메모리 블럭을 해제한 후 Proud.CLookasideAllocator 객체를 파괴합니다.

- Fast Heap

ProudNet의 Fast heap Lookaside allocator 보다 약간 더 느리지만 OS 환경에서의 메모리 할당/해제 속도보다 훨씬 빠르고, 다양한 크기의 메모리 블럭을 할당/해제가 가능합니다.

ProudNet의 Fast heap의 구현 클래스는 Proud.CFastHeap 입니다. Proud.CFastHeap 또한 모든 메모리 블럭이 파괴된 후 Proud.CFastHeap 객체를 제거할 수 있습니다.

Fast heap을 쓰는 방법은 다음과 같습니다.

  • 먼저 Proud.CFastHeap.New 메서드로 Fast heap 객체를 생성합니다. 전역 객체로 생성해도 됩니다.

  • Proud.CFastHeap.Alloc 메서드로 메모리 블럭을 할당합니다.

  • 해제는 Proud.CFastHeap.Free로 합니다. CFastHeap.Realloc 으로 메모리 블럭을 재할당 할 수 있습니다.

  • 모든 메모리 블럭을 해제한 후 Proud.CFastHeap 객체를 파괴합니다.

C++ 클래스의 기본 할당자로 지정하기

Fast heap 이나 Lookaside allocator 를 C++ 클래스에서 가속을 쉽게 받는 방법은 C++의 operator new, delete 메서드를 오버라이드하는 것입니다.

이렇게 하면 C++ 클래스를 new, delete 연산자로 생성, 파괴할 때 시스템의 메모리 heap 대신 Fast heap이나 Lookaside allocator가 사용됩니다.

아래 예시에서, 클래스가 operator newdelete로 인스턴스화 할 때 고성능 메모리 관리자가 대신 메모리를 할당/해제하게 합니다. 이들은 각각 다른 장단점이 존재하니 적절히 선택하여 사용하시기 바랍니다.

스마트 포인터

ProudNet은 스마트 포인터 Proud.RefCount 클래스를 보유하고 있습니다.

스마트 포인터란, 생성된 객체을 참조하는 변수들이 존재하는 한 해당 객체의 존재 자체를 보장하는 역할을 합니다. 그리고 해당 객체을 참조하는 변수들이 더 이상 존재하지 않을 때만 그 객체는 파괴됩니다.

또한 개발자가 만든 버그로 인해 이미 파괴된 객체를 참조하는 문제(dangling)나 미처 객체를 파괴하지 않는 문제(leak)를 해소하는 역할을 하기도 합니다.

스마트 포인터 변수는 복사될 때마다 객체 참조 카운트가 1씩 증가하는데 아래 그림에서 Object Instance는 참조 카운트가 0이 될 때까지 존재하게 됩니다.

객체를 참조하는 스마트 포인터가 여러 군데 있는 상태에서 강제로 객체를 파괴하고 싶을 때가 있습니다.

예를 들어, 열려있는 파일 핸들을 보유한 객체를 스마트 포인터에서 참조하고 있을 경우, 당장 그 파일 핸들을 보유한 객체를 파괴해야 하는 때가 있을 것입니다.

하지만 스마트 포인터가 여기저기서 그 객체를 참조하고 있다면 객체가 파괴되는 시점을 알 수 없기 때문에 난관에 봉착할 수 있습니다. 이러한 경우를 위해 Dispose Pattern을 구사하여 여러 군데에서 참조하고 있는 객체의 파괴를 명시적으로 수행할 수 있습니다.

- Dispose Pattern

Dispose Pattern 은 한 개 이상의 스마트 포인터가 참조하는 객체가 파괴될 시점을 정확히 몰라도 객체를 명시적으로 파괴하는 효과를 얻기 위한 프로그램 패턴입니다.

스마트 클래스로 다뤄질 객체에 Dispose Pattern 을 구사하려면 객체의 멤버 변수로서 '자기 상태'를 가지는데, 이는 이미 객체가 파괴되어 사용 불가 상태인지를 의미해야 합니다. 만약 자기 상태가 '이미 파괴된 상태'라면 객체 참조 시 에러를 발생시키고, 그렇지 않은 경우 정상 수행을 하도록 만들어야 합니다.

아래는 Dispose Pattern을 구사한 예입니다.

Dispose Pattern 은 Java나 C# 등 스마트 포인터Garbage Collector 가 있는 프로그래밍 언어에서도 다뤄지고 있습니다.

스레드 유틸리티

ProudNet은 몇 가지 스레드 유틸리티 클래스를 제공합니다.

문자열 클래스

ProudNet은 문자열 클래스 Proud.String, Proud.StringA 는 문자열을 ATL이나 STL의 문자열 클래스처럼 간편하게 문자열을 다룰 수 있게 해줍니다.

.Net Framework를 사용하는 프로그램은 System.string이라는 심볼을 가지고 있습니다. 따라서 .Net Framework를 혼용하는 경우 System 혹은 Proud 중 하나의 네임스페이스를 명시해야 할 수도 있습니다.

예를 들면 아래와 같습니다.

Proud::String a;  // 빈 문자열
a = L"123";         // 문자열에 값 넣기.
puts(a);            // a 자체가 직접 문자열 버퍼를 제공한다.
a += L"abc";        // 문자열에 다른 문자열 덧붙이기
if(L"123abc" == a)  // 문자열의 내용 비교 방법
{
    a.Replace(L"123", "def");   // 문자열 내용 치환
}

- 문자열 만들기 기능(format)

Proud.StringTsprintf() 처럼 문자열 만들기 기능을 제공합니다.

Proud::String a;
a.Format(L"%d %d %s", 1, 2, L"hahaha");
// 이제 a = "1 2 hahaha"가 됩니다.

- 문자열 처리 성능

Copy-on-write

// Proud.StringT의 copy-on-write 기능은 문자열이 꼭 필요한 경우에만 사본이 떠집니다. 
// 그 전에는 문자열 데이터를 서로 공유하게 됩니다.
 
Proud::String a = L"abc"; // a는 문자열 'abc'를 소유
Proud::String b = a; // b는 a와 같은 문자열 데이터를 공유
Proud::String c = b; // 이제 a,b,c는 모두 같은 문자열 데이터를 공유
c = L"bcd"; // a,b는 여전히 문자열 데이터 'abc'를 공유하고 있으나 c는 더 이상 공유를 하지 않고 'bcd'를 별도로 소유
b = c; // b는 a와의 'abc' 공유를 포기하고 c가 가진 'bcd'를 공유

문자열 길이 측정

Proud.StringT.GetLength 는 호출 즉시 미리 측정되었던 문자열 길이를 리턴합니다. 즉, strlen() 과 다릅니다.

Proud.StringT 는 int나 float와 마찬가지로 thread safe 하지 않습니다. 따라서 여러 스레드에서 동시에 같은 문자열 객체를 접근하는 것은 (모든 스레드가 읽기만 하는 경우를 제외하고) 안전하지 않습니다.

이 점은 ATL이나 STL의 문자열 클래스와 마찬가지입니다.

Timer Queue (타이머 큐)

Timer Queue 스레드 풀에서 tick event를 수행하는 모듈로 Windows XP, 2000 이후 버전의 운영체제에서는 Windows Timer Queue라는 API를 제공하고 있습니다.

일정 시간마다 사용자가 지정한 함수를 실행시키되, 그 함수는 스레드 풀의 스레드 중 하나에서 실행됩니다. 만약 모든 스레드가 뭔가를 실행중인 경우(running state) 함수의 실행은 스레드 중 과거 작업이 완료되는 스레드가 등장할 때까지 보류됩니다.

Timer Queue 에서 호출하는 유저 함수는 스레드 풀에 있는 스레드 중 하나가 선택되는데, 만약 앞서 실행중이던 유저 함수가 실행이 완료되지 않은 상태이더라도 할 일이 없는 스레드(idle state)가 있는 경우 그 스레드가 선택되어서 유저 함수를 실행합니다.

다음과 같은 작업 리스트가 있다고 가정합시다.

검은 화살표는 0.1초이며 A,B,C,D,E는 매 0.1초마다 해야 할 작업 항목입니다. A,D는 0.1초 이내에 끝나며, B는 딱 0.1초에 끝나며, C,E는 0.1초 안에 끝나지 못하는 작업입니다.

서버 메인 루프 방식인 경우 이들 작업 항목은 다음 그림처럼 수행됩니다. 한 개의 스레드에서 모든 작업 항목을 실행하기 때문에 D,E는 제때 시작하지 못합니다.

그러나 Timer Queue 방식에서는 D를 실행하기 위해 또 다른 스레드가 동원되며 E는 앞서 C를 완료한 스레드에서 제때에 실행되고 있습니다.

제 때 필요한 작업을 실행하되 필요한 경우 스레드를 더 동원합니다.

Timer Queue 는 유저 함수가 동시에 두 개 이상의 스레드에서 실행되고 있을 수 있는 특징 때문에 주로 서버 프로그램에서 사용됩니다.

병렬성을 가능하게 해주는 대신 병렬성이 가지고 있는 위험성을 감수해야 하므로 사용 전에 이 기능이 반드시 필요한지 판단하시기 바랍니다.

Timer Queue 를 잘못 사용할 경우 서버 프로세스의 스레드가 폭발적으로 증가하여 성능에 역효과를 줄 수 있습니다. Timer Queue 를 꼭 써야 하는 이유가 없다면 Proud.CTimerThread 또는 서버에서 타이머 루프, RMI, 이벤트 처리하기 를 사용하시기 바랍니다.

- 타이머 큐 사용 방법

Proud.CTimerQueue 클래스를 접근해야 합니다. 이 클래스는 singleton입니다.

일정 시간마다 호출될 함수와 호출 주기를 Proud::NewTimerParam 구조체에 설정하여 Proud.CTimerQueue.NewTimer 에 인자로 넣어주면 Proud.CTimerQueueTimer 객체를 받게 됩니다. 그리고 Proud.CTimerQueueTimer 객체를 파괴하기 전까지는 지정한 유저 함수가 일정 시간마다 실행됩니다.

로그 남기기

온라인 게임을 개발하다 보면 여러가지 실행 기록(로그)를 남기는 기능이 필요하기 마련입니다. ProudNet은 이를 위한 로그 남기기 기능을 제공합니다.

ProudNet의 로그 남기기 기능은 비동기로 실행되기에 로그를 남기겠다는 메서드를 호출하는 즉시 메서드는 리턴합니다. 또한 별도의 스레드에서 실제 로그를 파일이나 데이터베이스에 기록합니다.

- 파일에 로그 기록

Proud::CLogWriter 클래스는 파일에 로그를 기록할 수 있게 해주는 클래스 입니다.

- 데이터베이스에 로그 기록

Proud::CDbLogWriter 클래스는 DB에 로그를 기록할수 있게 해주는 클래스 입니다.

이를 사용하기 위해서는 Sample/DbmsSchema 폴더에 있는 LogTable.sql을 실행하여 LogTable을 미리 생성해주어야 합니다. DBMS 구축은 샘플 데이터베이스 구축하기 의 절차를 참고하십시오.

레이턴시 측정 기능

ProudNet은 StopWatch의 형태로 레이턴시 측정 기능을 제공합니다.

사용 가능한 버전이라면 서버의 GetLastPing이나 GetRecentPing 대신 기존의 레이턴시 측정 함수들보다 더 정확한 이 기능을 이용해주셔야 됩니다.

(1) 레이턴시 측정 시작

StartRoundTripLatencyTest 를 호출하면 레이턴시를 측정할 대상과 relay를 시작합니다.

(2) 레이턴시 측정 종료

앞서 지정한 testDuration 이전에 테스트를 중단하고자 한다면 StopRoundTripLatencyTest 함수를 호출합니다. testDuration이 지날 때까지 StopRoundTripLatencyTest 호출을 안하면 자동적으로 측정이 중단됩니다.

(3) 레이턴시 측정 값 얻어오기

PIDL 컴파일러 애드온

PIDL(ProudNet IDL) 파일의 Custom Build 설정을 손쉽게 할수있도록 도와주는 비주얼 스튜디오용 애드온 입니다.

- 설치

1. <설치경로>\ProudNet\util\PIDL-addon.vsix 실행 합니다.

2. 애드온을 설치할 Visual Stuio를 선택하고 설치(Install)를 진행합니다.

3. 설치가 정상적으로 완료되면 아래와 같이 표시 됩니다.

- 삭제

ToolsExtensions and Updates 메뉴를 통해 삭제가 가능합니다.

Visual Studio 2010의 경우 ToolsExtension Manager

- PIDL 파일 추가 창

PIDL 추가 창을 호출 합니다. 프로젝트 선택우클릭Add new PIDL file 선택

  1. PIDL 파일명을 입력합니다.

  2. PIDL 파일이 추가될 경로를 표시합니다.

  3. 외부 PIDL 파일을 추가합니다.

  4. 새로운 PIDL 파일을 생성합니다.

  5. PIDL 추가 창을 종료합니다.

- PIDL 파일 추가

  1. 추가할 PIDL 파일명 입력

  2. New PIDL 클릭

위 그림과 같이 PIDL 파일이 추가되며 해당 파일의 Custom Build 설정은 기본적으로 되어 있습니다. 기본 설정과 다른 경우 파일 속성 변경 기능을 통해 변경 가능합니다.

- PIDL 파일 속성 창

PIDL 속성 창을 호출 합니다.

PIDL 파일 선택우클릭Properties 선택

  1. PIDL 파일의 Custom Build Tool 설정을 확인/변경 합니다. PIDL Compiler Location: PIDL 컴파일러 위치를 설정합니다. PIDL Compiler Location is Relative: 입력한 경로의 절대/상대 여부 값 입니다. Output Directory (All Languages): 출력 경로를 설정 합니다. Output Directory is Relative (All Languages): 입력한 경로의 절대/상대 여부 값 입니다.