這學期數據庫課程,最後的大程是寫一個MiniSQL的數據庫實現,要求很簡單,建刪表,建刪單值索引,支持主鍵和unique定義,支持最簡單的select,只要支持3個類型:int,float,char(0~255)。最開始,考慮到數據庫的運行時肯定類型的特色,選擇了運行時強大的C#,還能順便集成進Linq。可是一週後發現C#操做對象二進制結構的能力幾乎爲0,在寫BufferManager的時候也發現徹底不能自由的控制對象生命週期,而且IDisposable的實現也過於迷,與強大運行時和linq相比,這些弱項是我沒法接受的,隨即果斷轉到C++。html
C++的RAII提供了精確的對象生命週期和全部權控制,這使我很愉快地寫出了能精確控制每個內存塊的BufferManager,智能指針,尤爲是unique_ptr在其中起到了巨大的做用,還順便了解了C++11更加精細的對象左右值的概念,更加熟悉了對象全部權和全部權轉移的界定和控制。C++足夠接近底層的指針和內存操做,也使得我能直接將一個對象指針reinterpret_cast成byte*,隨後直接用二進制模式寫回文件(固然這裏面我手動控制了對象不能有指針和堆內存),也能夠從文件中讀入一段二進制內容,直接將首地址reinterpret_cast成對象指針,而後直接使用。模板在其中也幫了很大的忙。算法
索引的部分,最開始使用了模板B+樹,隨後發現數據庫須要運行時類型,模板這種編譯時靜態類型的東西不能知足個人需求,因而讓模板繼承自公用的基類,擦除模板類型,基類的虛函數參數使用byte*,擦除參數類型,經過虛函數調用找到正確的派生類後再由派生類恢復參數的類型參與運算,這樣算是一種應急的walk around,畢竟當時的時間不容許我把模板容器改爲運行時動態類型容器,也沒有時間再造一個動態類型輪子。在把運行時的值轉爲靜態的模板類型時,費了很大的勁,主要是須要實現相似於數據庫
template<typename T> TreeBase* make_tree(); switch(type) { case Int: return make_tree<int>(); case Float: return make_tree<float>(); case Char: switch(size) { case 1: return make_tree<array<char, 1>>(); case 2: return make_tree<array<char, 2>>(); //..... case 255: return make_tree<array<char, 255>>(); } break; }
相似這樣的把動態的值轉爲靜態類型的東西,最噁心的部分是那個switch(size)的部分,要case 1~255,手寫的話確定噁心的要死,因而我寫了個模板來作二分搜索,最多6層函數調用就能找到正確的值。糊了這麼多,仍是爲了填當初寫了個靜態的模板B+樹的坑。B+樹debug的過程當中,使用了VS Debugger提供的natvis可視化工具,來自定義我本身寫的類在監視窗口中的顯示方式,這東西確實爽,看東西方便了很多。數組
模板B+樹的實現內部坑更多,當初的設計是既然全部類型都是靜態的,那就能夠靜態的肯定一個樹的節點的度,而且可使得節點大小正好撐滿4KB空間,實現起來出了坑,由於迷以內存對齊佔據了空間,使用(4KB – sizeof(非指針成員))/sizeof(指針大小)的方式肯定的樹節點,老是會使得大小超過4KB,幸好提早猜到了可能出現這樣的問題,加了static_assert,否則又不知道要debug到哪裏去,話說回來,最後用了#pragma pack(1)這種坑爹貨,關了內存對齊,勉強繞過了這個問題。下面的坑就是來自樹節點自己,因爲我要把整個節點直接二進制寫入文件,因此至少須要保證節點是POD類型(雖而後面發現這樣作其實畫蛇添足,還增長複雜度),這也就致使了節點內不能聽任何成員函數,因而我又寫了一個包裝類,持有一個樹節點,而後在這個包裝類裏寫須要的成員函數,因爲樹操做須要同時讀多個節點,每一個節點是一個block,爲了防止讀後面節點時前面要用的節點被回收的狀況出現,我設計了在包裝類在包裝節點時自動給節點加鎖的機制,這有些相似於shared_ptr的引用計數,隨後包裝類的鎖計數控制又出了坑,這讓我明白原來不寫的move ctor編譯器是會自動生成,而不是把move操做變爲copy,所以這裏有點懷疑老版C++代碼中若是作了相似引用計數的機制,是否能直接在C++11環境下正常運行。函數
解釋器的部分手寫了tokenizer,用了vczh輪子叔在他博客裏給tinymoe寫tokenizer時手寫狀態機的方法,語法分析是單純的遞歸降低同時解釋執行。工具
剩下的部分就不具有太多的技術含量,只是單純的業務邏輯,惟一的坑點就是我在這個數據庫裏寫了三份管理字符串到數字映射的東西,居然沒想到用hash來作,失敗,失敗。性能
最後的測試和運行階段,跑了一遍VS自帶的性能分析工具,真的找到了限制性能的地方,是在lru算法替換文件塊的部分,被替換塊的選擇出了坑,致使塊交換頻率太高,拉低了程序性能,優化以後,插入1W條數據的同時檢查3個unique鍵並插入索引,只須要20秒多一點,仍是比較滿意的。測試
寫這個東西的過程當中仍是發現了C++標準庫有不少缺乏的東西的,好比序列化(這個在將來有了static reflection應該會有所改善,固然如今也有不少序列化開源庫),好比花式拼接字符串(sstream太慢),好比二進制文件流(boost有個buffer_stream,可是太老),好比運行時肯定規模而且存儲空間連續的二(多)維數組(new是不能new int[m][n]的,n必須是字面值),還有char*和string轉換時的麻煩等。優化
一我的handle這樣一個最終寫了5000多行的東西,中間用到了各類本身學到的C++技術,嘗試了各類從前沒用過的工具好比natvis,profiler,還在debug B+樹的時候刷了夜,解鎖了一大堆成就,雖然最後數據庫的分數不是很高(我猜多半是我期中期末考得太爛了,大程目測分不低),仍是感受頗有收穫的,說了這麼多,最後以一句vczh輪子叔曾經說過的話作結尾吧。ui
你須要花時間作什麼,取決於這個問題是否是夠難,是否是剛恰好你能夠作出來,再難一點點你就作不出來了。只要你保持這種訓練方法長達十年,想不牛逼都難。