製作遊戲時常常需要測試記憶體的使用量以及效能的評估
通常這種動作稱之為Profiling

這種通常會有強大的工具來輔助取得這些資訊
甚至更多的資訊
但是這種強大的工具通常會中斷遊戲的測試
通常都是要針對某一項Bug做測試時才會使用
就像使用DirectX的PIX

當然我們也是可以做一些工作在Real Time時偵測
首要的重點就是
我們是Real Time的,所以不能在做Profiling時影響到太多的效能
因此我們不可能做出像一般Profiling工具的功能這般強大

現在就說說一般效能測試會做的兩件事

1. Timer
效能測試最簡單最常用的方式就是檢視一段程式碼需要多少時間
這方式非常簡單,並不需要多高深的技巧

example
float fBegin = GetTickCount();
.......................
.......................
.......................
float fDelta = GetTickCount() - fBegin;


如此就可以知道中間的程式碼花了多少系統時間,很簡單吧
這種方法通稱為夾擊法

2. Memory
記憶體的偵測也同樣是用夾擊法

example
MEMORYSTATUS InitialMemoryStatus, EndMemoryStatus;
GlobalMemoryStatus(&InitialMemoryStatus);
...........................
...........................
...........................
GlobalMemoryStatus(&EndMemoryStatus);
int iSize = (int)InitialMemoryStatus.dwAvailPhys - (int)EndMemoryStatus.dwAvailPhys;

首先先呼叫GlobalMemoryStatus紀錄一段程式碼之前的記憶體狀況
之後在程式碼最後再呼叫一次GlobalMemoryStatus來記錄執行後的記憶體狀況
dwAvailPhys為可利用的實體記憶體大小

如果程式碼當中有配置任何記憶體那麼EndMemoryStatus.dwAvailPhys所表示可利用的實體記憶體大小將會比InitialMemoryStatus.dwAvailPhys來的小
因此由(int)InitialMemoryStatus.dwAvailPhys - (int)EndMemoryStatus.dwAvailPhys就可以知道這段程式碼使用了多少記憶體

這裡將dwAvailPhys轉為int是因為dwAvailPhys是unsigned的,如果你這段程式碼是在做釋放記憶體的動作將會變成EndMemoryStatus.dwAvailPhys得到的結果比InitialMemoryStatus.dwAvailPhys大,如此一相減會造成異位,得到的資訊將會是錯誤的
所以才會轉型為int,並且用一個int的iSize來記錄,如果為負的就表示釋放了多少記憶體



以上這兩個方法一點也不難
但是會不會太麻煩了
如果每個想偵測的地方都要夾這兩段程式碼
當遊戲開發到後期,程式碼多到上萬行
尤其到後面效能是會越來越差,需要測試與調整的地方會越來越多

有鑑於此
以上這個做法是要修改的

我想大家應該都有用過Assert這個東西吧
Assert是一個巨集,他會判斷是否為真,如果是錯誤的就會跳出錯誤訊息
他有另一個特性是,只有Debug版才會執行,而Release版卻不會

以下是我自己寫的範例
#ifdef _ENABLE_DETECT_MEMORY

#define DETECTMEMORY(function, str, index) \
index = ksSystem::StartDetectMemory(); \
function; \
ksSystem::EndDetectMemory(str, index);

#define DETECTMEMORY_START(id) id = ksSystem::StartDetectMemory();
#define DETECTMEMORY_END(string,id) ksSystem::EndDetectMemory(string, id);

#else

#define DETECTMEMORY(function, str, index) function;
#define DETECTMEMORY_START(id)
#define DETECTMEMORY_END(string,id)

#endif

#ifdef _PROFORMANCE

static float fBegin = 0.0f;

#define PROFORMANCE(function, x) \
fBegin = GetTickCount(); \
function; \
x = GetTickCount() - fBegin;

#else

#define PROFORMANCE(function, x) \
function;

#endif

用#define來把整個要偵測記憶體與效能的的程式碼片段夾擊

使用方法很簡單,只要呼叫此macro
DETECTMEMORY(ksResourceManager::Create(), "Create ResourceManager", index);
ksResourceManager::Create()此函式裡包含了許多程式碼在這裡執行,但是用#define的方式就可以對此函式偵測其記憶體花費了多少。

以下是其他程式碼的片段
int ksSystem::StartDetectMemory() {
GlobalMemoryStatus(&ms_StartDetectMemory);
for (int i = 0; i < ksMEMORYDATACOUNT; ++i) {
if (!m_MemoryDataArray[i].m_bUsed) {
m_MemoryDataArray[i].m_bUsed = true;
m_MemoryDataArray[i].m_uiSize = (unsigned int)ms_StartDetectMemory.dwAvailPhys;
return i;
}
}
return -1;
}

unsigned int ksSystem::EndDetectMemory(const char * OutputMsg, int index) {
GlobalMemoryStatus(&ms_EndDetectMemory);
int memory = 0;
if (index == -1)
{
memory = (int)ms_StartDetectMemory.dwAvailPhys - (int)
ms_EndDetectMemory.dwAvailPhys;
}
else if (index < ksMEMORYDATACOUNT)
{
m_MemoryDataArray[index].m_bUsed = false;
memory = (int)m_MemoryDataArray[index].m_uiSize - (int)
ms_EndDetectMemory.dwAvailPhys;
}
else
return 0;

if (OutputMsg != NULL)
{
Log("-----------------------------------------------------------------------------------------\n");
if (memory < 0)
Log("%s Released Memory : %d MegaBytes, %d KiloBytes, %d Bytes\n", OutputMsg, memory / (int)MEGABYTE, memory / (int)KILOBYTE, memory);
else
Log("%s Created Memory : %d MegaBytes, %d KiloBytes, %d Bytes\n", OutputMsg, memory / MEGABYTE, memory / KILOBYTE, memory);
Log("-----------------------------------------------------------------------------------------\n");
}
return memory;
}

範例是我針對我目前的專案所設計的
不見得適用其他地方
大家多多參考就好
如果有要改進的地方
希望大家不吝賜教


讓我們一起大喊

彼得哥萬歲~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

arrow
arrow
    全站熱搜

    SnakeEater 發表在 痞客邦 留言(0) 人氣()