一种以动态库的方式使用资源表的方案
這段時間研究了一下資源表的優化方案,算是有了一些成果,在此記錄下來。
先交代一下背景吧:我們的服務器把資源表放在共享內存上。這么做的原因主要是,進程core掉后再拉起時不需要重新再構建一遍資源表(構建資源表主要就是構建索引查詢的數據結構,比如構建一個哈希表用于根據HeroID查詢英雄配置這種)。然后,考慮到同一個機器上可能部署多個進程,于是自然就想到,能否有一種機制能夠讓一個機器上的多個進程共享同一塊資源表的內存?
一個比較直觀的想法就是,讓多個進程掛在同一塊資源表共享內存。這樣做確實是可以達到內存共享的目的,但是需要考慮資源表reload的情況。reload時會對這塊內存做寫操作,而我們知道,一寫多讀是會有并發問題。因此這種方案還需要再引入一種機制來處理并發問題。常見的比如是雙緩沖區,構建好后再一次性切換到新的資源表內存上。了解到了一些項目組確實也是這么做的,具體就不細說了。
而我想到的方法是這樣的:使用代碼生成技術,將資源表完全的導成c++的代碼。所有用到于查詢的數據結構都是通過自動代碼生成的,并且在編譯期就被靜態構建好了。進程運行時直接載入這塊內存即可,不需要像以前那樣在起服時跑資源表構建的流程。因此,這塊內存也不需要再手動放共享內存上了。core之后再拉起進程,最多就是資源表的內存被卸載了,并且會到下次用到時通過觸發缺頁中斷被再次載入進內存。為了做同一機器上的內存共享,可以把資源表打包成一個動態庫。于是共享內存的這塊工作自然而然就推給了操作系統去做。
這種做法的優點有幾個。而其中我覺得最重要的是,這是一種一次性解決問題的做法(這也是我在前一篇博客中所提到的:P)。基本上資源表的大部分代碼都被以是自動生成。并且當某個進程要使用某些資源表時,直接加載這個動態庫即可。甚至,加載動態庫的代碼都可以寫在框架層,默認為每個進程都加載資源表。在以前我們在其它進程(除了gamesvr之外的其它進程,如場景進程)中如果要使用資源表是很麻煩的:要在一個指定的文件中定義要使用到了哪些資源表,并且如果資源表之間有依賴關系,也需要把依賴的資源表也配置進來。而現在,我們可以為所有進程都載入資源表,并且還不需要擔心帶來內存增長的風險。
其次,不需要再為資源表的內存預估一個上限的經驗值。比如說定義了一個長度為1000的數組來存裝備配置,當策劃同學配了超過1000個裝備時,就要手動修改這代碼修改這個經驗值(因為之前是把資源表放共享內存,因此需要內存預分配)。因為資源表都是被靜態構建的,數組大小完全可以通過自動代碼生成來做。再者,也不需要自己手寫建索引的代碼,全部都通過工具自動生成。比如技能配置表可能會有根據ID和Level查詢的需求,現在只需要在導表時指定主鍵,便可以自動的生成這些索引的代碼。
靜態構建數據結構還有一個優勢是,理論上可以使用一種更優的內存結構來存儲。一般來說,資源表內存再載入后就不會再作修改了,針對這種場景我們可以設計一種比哈希表更快、更省內存的數據結構來做索引。比如通過minima?perfect hash方式為每個主鍵生成不會沖突的哈希函數。并且由于不會動態擴容,因此也不需要使用稀疏的哈希表來存儲(一個緊湊的數組即可)?;舅悸肪褪前延嬎阌蛇\行期推到編譯期,甚至可以是推到生成代碼的時候。
還有就是reload過程非常簡單,卸載舊的動態庫加載新的動態庫即可。由于資源表不放共享內存,因此也不需要之前那樣為新增的內容預留一些內存空間,也不需要考慮共享內存上的結構體兼容性問題。
最后一點就是實現非常簡單。所有代碼加起來還不足1000行,包括了一個用python寫的代碼靜態代碼生成工具,和c++的資源表管理器代碼等。
?
在實現過程中也遇到了幾個有意思的問題,在這里也一并記錄一下。
(1)首先一點,linux動態庫是只會把.text段的內存共享。而我們這里要共享的主要是數據,怎么辦呢?首先要明白的是,動態庫之所以不共享.data段的內存,是因為這部分數據可能在被載入進程后修改(包括可寫的數據、以及重定位表等結構),因此必須是每個進程維護一份私有的內存。但我們這里沒有修改數據的情況,因此是完全可以被共享內存的。解決方法很簡單:用const來修飾資源表數組即可。這樣做這塊內存會被放到.rodata中。并且在鏈接階段,linker會合并所有.rodata到.text段。
P.S. 通過readelf -l?命令查看elf文件的program?header就能看到哪些section是被合并到.text段,哪些被合并到.data段
?
(2)另外,還有一個問題就是關于如何熱更新一個動態庫。之前我一直以為直接把目錄下的庫的文件替換一下是沒問題的(由于有文件的引用計數)。但是老大指出,在linux下用cp命令覆蓋實際上是會修改原來文件的內容的,并不是等價于在目錄下刪掉原來的目錄項,再創建一個新的目錄項指向新文件內容。原理就是覆蓋時cp會修改原來文件的inode,并不是創建一個新的inode(詳情可以看這里)。不過我感覺此處更像是cp命令的一個bug:在拷貝時應該考慮是否有進程正在引用原來的文件,如果有則應該考慮創建一個新的inode,而不是復寫原來的inode(類似于寫時拷貝)。不過這個問題感覺可以再深入研究一下,是不是有其它什么顧慮?
?
轉載于:https://www.cnblogs.com/adinosaur/p/11545948.html
總結
以上是生活随笔為你收集整理的一种以动态库的方式使用资源表的方案的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JS中写继承的方式
- 下一篇: AdminLTE组件之表格DataTab