忙碌了兩個多月,這篇有可出現在 Gems 系列的 "In-Game Memory Profiler" 終於完成。製作這類 Profiler 已經不知多少次了,但這次是最滿意的;可以媲美需付款的產品 (其實類似的 C++ memory profiler 產品真的少之又少),甚至更好。就是它,讓我知道 Bullet 每次 step simulation 都有好幾千個即用即棄的 memory allocation/de-allocation;後來我把它的 default pool size 增大,問題也就解決了。
2009年9月17日星期四
2009年9月5日星期六
弱指針的重要性
雖然有許多有關智能指針 (Smart pointer) 的文章和範例,但關於弱指針的就相對寥寥無幾。縱然有所提及,也只是草草幾筆說弱指針是用來避免 "循環引用 (Cyclic reference)" 云云。就此問題我嘗試用一些例子去補充說明弱指針何時用,為什麼用。
首先讓我們看一個遊戲引擎中典型的資料管理系統例子:
因為要節省內存的需求,Texture (紋理) 被設計成能夠給多個 Model (三維模型) 共享。而 Texture 的存在目的,就是留給 Model 作為繪畫時使用;明顯地,Model 可用 Smart Pointer 來引用 Texture;相對地 NameToTextureMap (方便用名稱來作資料搜尋) 該使用甚麼來引用 Texture 就沒有那麼明顯了。先看看使用不同的指針會有什麼的後果吧。
總結來說,智能指針旨不在於提供程序員不加思索就可使用的工具,它是一種能幫助你正確地實行擁有權共享的工具。就算是聲稱擁有自動內存管理的語言如 Java, C#, Python, Ruby 等等都一定會有弱指針這個概念,否則內存洩漏是很難避免的。假若你在開發 C++ 程序的時候遇到許多關於物件擁有權的問題,請記得弱指針的存在。
首先讓我們看一個遊戲引擎中典型的資料管理系統例子:
因為要節省內存的需求,Texture (紋理) 被設計成能夠給多個 Model (三維模型) 共享。而 Texture 的存在目的,就是留給 Model 作為繪畫時使用;明顯地,Model 可用 Smart Pointer 來引用 Texture;相對地 NameToTextureMap (方便用名稱來作資料搜尋) 該使用甚麼來引用 Texture 就沒有那麼明顯了。先看看使用不同的指針會有什麼的後果吧。
- 裸指針 - 這將會是災難性的選擇,因為一旦再沒有 Model 引用 Texture, NameToTextureMap 就會在毫無知會的情況下指向一件非法的物件。
- 智能指針 - 為了解決裸指針的問題,選擇智能指針像是合理的做法。不過請大家想一想,一旦 NameToTextureMap 引用了 Texture 以後,何時才可刪除引用呢?假如不去刪除引用,Texture 的生命期就會與 NameToTextureMap 同步化,失去了原先使用智能指針作為純粹被 Model 共享的意義。
總結來說,智能指針旨不在於提供程序員不加思索就可使用的工具,它是一種能幫助你正確地實行擁有權共享的工具。就算是聲稱擁有自動內存管理的語言如 Java, C#, Python, Ruby 等等都一定會有弱指針這個概念,否則內存洩漏是很難避免的。假若你在開發 C++ 程序的時候遇到許多關於物件擁有權的問題,請記得弱指針的存在。
2009年9月3日星期四
Syntax highlight in Notepad++
從互聯網找到個給 Squirrel 語言著色的 Notepad++ config 檔案,也可作為日後 MCore3D Studio 結合 Scintilla Control 的藍圖。把以下的 Xml 儲存為 "userDefineLang.xml" 再放入 Notepad++ 的安置目錄就行了。
<NotepadPlus>
<UserLang name="Squirrel" ext="nut">
<Settings>
<Global caseIgnored="no" />
<TreatAsSymbol comment="yes" commentLine="yes" />
<Prefix words1="no" words2="no" words3="no" words4="no" />
</Settings>
<KeywordLists>
<Keywords name="Delimiters">"00"00</Keywords>
<Keywords name="Folder+"></Keywords>
<Keywords name="Folder-"></Keywords>
<Keywords name="Operators">- ! ( ) , . : ; ? [ ] { } + < = ></Keywords>
<Keywords name="Comment">1/* 2*/ 0//</Keywords>
<Keywords name="Words1">break case catch class clone continue const default delegate delete else enum extends for function if in null resume return switch this throw try typeof parent yield constructor instanceof true false static do while foreach</Keywords>
<Keywords name="Words2">local</Keywords>
<Keywords name="Words3">ARGS</Keywords>
<Keywords name="Words4"></Keywords>
</KeywordLists>
<Styles>
<WordsStyle name="DEFAULT" styleID="11" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" />
<WordsStyle name="FOLDEROPEN" styleID="12" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" />
<WordsStyle name="FOLDERCLOSE" styleID="13" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" />
<WordsStyle name="KEYWORD1" styleID="5" fgColor="0000FF" bgColor="FFFFFF" fontName="" fontStyle="1" />
<WordsStyle name="KEYWORD2" styleID="6" fgColor="006262" bgColor="FFFFFF" fontName="" fontStyle="1" />
<WordsStyle name="KEYWORD3" styleID="7" fgColor="007777" bgColor="FFFFFF" fontName="" fontStyle="3" />
<WordsStyle name="KEYWORD4" styleID="8" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" />
<WordsStyle name="COMMENT" styleID="1" fgColor="008000" bgColor="FFFFFF" fontName="" fontStyle="2" />
<WordsStyle name="COMMENT LINE" styleID="2" fgColor="008000" bgColor="FFFFFF" fontName="" fontStyle="2" />
<WordsStyle name="NUMBER" styleID="4" fgColor="FF8000" bgColor="FFFFFF" fontName="" fontStyle="0" />
<WordsStyle name="OPERATOR" styleID="10" fgColor="000080" bgColor="FFFFFF" fontName="" fontStyle="1" />
<WordsStyle name="DELIMINER1" styleID="14" fgColor="7E7E7E" bgColor="FFFFFF" fontName="" fontStyle="2" />
<WordsStyle name="DELIMINER2" styleID="15" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" />
<WordsStyle name="DELIMINER3" styleID="16" fgColor="000000" bgColor="FFFFFF" fontName="" fontStyle="0" />
</Styles>
</UserLang>
</NotepadPlus>
2009年8月29日星期六
Mesa 3D
2009年8月5日星期三
Restrict 指針
一般時候我們的編譯器都不知道兩個指針所指的位置是否相同或者有所重疊,大大降低了它的優化效果。就拿 Matrix3 * Vector3 為例:
很簡單的三行代碼,然而它隱藏了一個效能上的問題。堂 result 被賦予了新的值後,編譯器認為 v 的值也可能被更改了 (v 和 result 也是 reference,pointer 的一類)。因此原本可以留在 register 裡的 v.x, v.y 和 v.z 被迫從新由記憶體裡閱讀回來。看看VC2008 編譯成的機器碼吧:
這時候使用 restrict 就可幫上編譯器把。請注意,VC2008 的 __restrict 只對指針生效:
從新編譯後的機器碼:
可以看到記憶體閱讀操作減少了。
到最後,其實使用局部變量也可達到類似效果:
void Matrix::MulVector(const Vec3& v, Vec3& result) {
result.x = m00 * v.x + m01 * v.y + m02 * v.z;
result.y = m10 * v.x + m11 * v.y + m12 * v.z;
result.z = m20 * v.x + m21 * v.y + m22 * v.z;
}
很簡單的三行代碼,然而它隱藏了一個效能上的問題。堂 result 被賦予了新的值後,編譯器認為 v 的值也可能被更改了 (v 和 result 也是 reference,pointer 的一類)。因此原本可以留在 register 裡的 v.x, v.y 和 v.z 被迫從新由記憶體裡閱讀回來。看看VC2008 編譯成的機器碼吧:
result.x = m00 * v.x + m01 * v.y + m02 * v.z;
mov eax,dword ptr [esp+4]
movss xmm0,dword ptr [ecx+8]
mulss xmm0,dword ptr [eax+8]
movss xmm1,dword ptr [ecx+4]
mulss xmm1,dword ptr [eax+4]
mov edx,dword ptr [esp+8]
addss xmm0,xmm1
movss xmm1,dword ptr [eax]
mulss xmm1,dword ptr [ecx]
addss xmm0,xmm1
movss dword ptr [edx],xmm0
result.y = m10 * v.x + m11 * v.y + m12 * v.z;
movss xmm0,dword ptr [ecx+0Ch]
mulss xmm0,dword ptr [eax]
movss xmm1,dword ptr [ecx+14h]
mulss xmm1,dword ptr [eax+8]
addss xmm0,xmm1
movss xmm1,dword ptr [ecx+10h]
mulss xmm1,dword ptr [eax+4]
addss xmm0,xmm1
movss dword ptr [edx+4],xmm0
result.z = m20 * v.x + m21 * v.y + m22 * v.z;
movss xmm0,dword ptr [ecx+18h]
mulss xmm0,dword ptr [eax]
movss xmm1,dword ptr [ecx+20h]
mulss xmm1,dword ptr [eax+8]
addss xmm0,xmm1
movss xmm1,dword ptr [ecx+1Ch]
mulss xmm1,dword ptr [eax+4]
addss xmm0,xmm1
movss dword ptr [edx+8],xmm0
這時候使用 restrict 就可幫上編譯器把。請注意,VC2008 的 __restrict 只對指針生效:
void Matrix::MulVector(const Vec3& v_, Vec3& result_) {
const Vec3* __restrict v = &v_;
Vec3* __restrict ret = &result_;
result->x = m00 * v->x + m01 * v->y + m02 * v->z;
result->y = m10 * v->x + m11 * v->y + m12 * v->z;
result->z = m20 * v->x + m21 * v->y + m22 * v->z;
}
從新編譯後的機器碼:
result->x = m00 * v->x + m01 * v->y + m02 * v->z;
mov eax,dword ptr [esp+4]
movss xmm1,dword ptr [eax+4]
movss xmm0,dword ptr [eax+8]
movss xmm2,dword ptr [eax]
movss xmm3,dword ptr [ecx+4]
movss xmm4,dword ptr [ecx+8]
mov eax,dword ptr [esp+8]
mulss xmm3,xmm1
mulss xmm4,xmm0
addss xmm3,xmm4
movaps xmm4,xmm2
mulss xmm4,dword ptr [ecx]
addss xmm3,xmm4
result->y = m10 * v->x + m11 * v->y + m12 * v->z;
movss xmm4,dword ptr [ecx+10h]
movss dword ptr [eax],xmm3
movss xmm3,dword ptr [ecx+0Ch]
mulss xmm3,xmm2
mulss xmm4,xmm1
addss xmm3,xmm4
movss xmm4,dword ptr [ecx+14h]
mulss xmm4,xmm0
addss xmm3,xmm4
movss dword ptr [eax+4],xmm3
result->z = m20 * v->x + m21 * v->y + m22 * v->z;
movss xmm3,dword ptr [ecx+18h]
mulss xmm3,xmm2
movss xmm2,dword ptr [ecx+1Ch]
mulss xmm2,xmm1
movss xmm1,dword ptr [ecx+20h]
addss xmm3,xmm2
mulss xmm1,xmm0
addss xmm3,xmm1
movss dword ptr [eax+8],xmm3
可以看到記憶體閱讀操作減少了。
到最後,其實使用局部變量也可達到類似效果:
void Matrix::MulVector(const Vec3& v, Vec3& result) {
const float x = v.x;
const float y = v.y;
const float z = v.z;
result.x = m00 * x + m01 * y + m02 * z;
result.y = m10 * x + m11 * y + m12 * z;
result.z = m20 * x + m21 * y + m22 * z;
}
2009年6月21日星期日
2009年6月17日星期三
於特定線程設置中斷點
正在開發一個 memory profiler,為了針對多線程部分的除錯,我須要設置一個只對特定線程生效的中斷點。在 Visual Studio 中,有兩個非常類似的方法可達到目的:
訂閱:
文章 (Atom)






