面向數據編程
1. 面向對象編程有哪些問題
1. 過度封裝
使用OOP時,會把一些復雜的問題分拆抽象成較簡單的獨立對象,通過對象的互相調用去實現方案。但是,由於對象包含自己封裝的數據,一個問題的數據集會被分散在不同的內存區域。互相調用時很可能會出現數據的cache miss的情況。
2. 多態
在C++的一般的多態實現中,會使用到虛函數表。虛函數表是通過加入一次間接層來實現動態派送。但在調用的時候需要讀取虛函數表,增加cache miss的可能性。基本上要支持動態派送,無論用虛函數表、函數指針都會形成這個問題,但如果類的數目極多,把函數指針如果和數據放在一起有時候可放緩問題。
3. 數據布局
雖然OOP本身並無限制數據的布局方式,但基本上絕大部分OOP語言都是把成員變數連續包裹在一段內存中。甚至使用C去編程的時候,也通常會使用到OOP或Object-based的思考方式,把一些相關的數據放置於一個struct之內:
structParticle{
Vector3position;
Vector4velocity;
Vector4color;
floatage;
//...
};
即使不使用多態,我們幾乎不加思索地會使用這種數據布局方式。我們通常會以為,由於各個成員變數都緊湊地放置在一起,這種數據布局通常對緩存友好。然而,實際上,我們需要考慮數據的存取模式(access pattern)。
在OOP中,通過封裝,一個類的各種功能會被實現為多個成員函數,而每個成員函數實際上可能只會存取少量的成員變數。這可能形式非常嚴重的問題,例如:
for(Particle*p=begin;p!=end;++p)
p->position+=p->velocity*dt;//或p->SimulateMotion(dt);
在這種模式下,實階上只存取了兩個成員變數,但其他成員變數也會載入緩存造成浪費。當然,如果在迭代的時候能存取盡量多的成員變數,這個問題可能並不存在,但實際上是很困難的。
如果採用傳統的OOP編程範式及實現方式,數據布局的問題幾乎沒有解決方案。所以在[1]里,作者提出,在某些情況下,應該放棄OOP方式,以數據的存取及布局為編程的考慮重中,稱作面向數據編程(data-oriented programming, DOP)。