最近由于工作需要,由于選型幀同步匆赃,服務(wù)器需要跑客戶端代碼淤毛,我們后端是go語(yǔ)言,客戶端是c++
記錄一下在這個(gè)過程中遇到的問題炸庞,和解決問題思路钱床。與最后的成果。
- 首先學(xué)習(xí)cgo
學(xué)習(xí)一個(gè)組件很簡(jiǎn)單埠居,學(xué)習(xí)cgo的基礎(chǔ)就可以查牌,既然我是使用,并不需要研究cgo的多么深滥壕。
cgo需要在go文件中加入import "C"
,并且需要在import "C"
上面緊貼著寫上自己的c代碼
例如
package main
/*
#cgo LDFLAGS: -L -lc -ldl
#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>
#include "library.h" //非標(biāo)準(zhǔn)c頭文件纸颜,所以用引號(hào)
*/
import "C"
這里記錄一下 #cgo LDFLAGS: 的作用吧,這個(gè)的作用就是設(shè)置gcc的一些參數(shù)绎橘。-L和gcc中的LD_LIBRARY是一樣的東西胁孙,-l后面接上動(dòng)態(tài)庫(kù)。
假如說称鳞,在同級(jí)目錄下涮较,有一個(gè)libaaa.so 那么我需要這樣設(shè)置
#cgo LDFLAGS: -L ./ -laaa
這里我加上了-ldl 我要加載操作系統(tǒng)中某些庫(kù),因?yàn)楹竺嫖矣玫搅薲lopen動(dòng)態(tài)加載so庫(kù)
如果你出現(xiàn)過dlopen' ‘dlsym’ undefined reference
你可以加上-ldl 如果過你還沒找到冈止,你就去找出現(xiàn)沒找到的包所在的系統(tǒng)位置狂票,然后查看系統(tǒng)環(huán)境變量LD_LIBRARY_PATH里面是否有當(dāng)前目錄就可以。(-ldl要放在最后面)
接下來(lái)熙暴,我們就要用了闺属,我寫了一個(gè)library.h與我的go文件相同的位置
這樣,我直接寫#include"文件名"
就可以了
在這里簡(jiǎn)單介紹下cgo的代碼使用
cgo封裝了c中所有結(jié)構(gòu)體周霉。有很多文章都說過他的類型轉(zhuǎn)換問題了掂器。
其實(shí)很簡(jiǎn)單,直接C.對(duì)應(yīng)的類型
就可以
例如
// cgo.go
cs := C.CString("XXX")
defer C.free(unsafe.Pointer(cs))
C.InitConfigData( cs)
// library.c
void Init(char* resPath)
{
俱箱。国瓮。。
}
這樣是直接可以調(diào)用的
這里需要注意的是狞谱,go的內(nèi)存是有垃圾回收的巍膘,但是并不回收c的內(nèi)存屬性。所以芋簿,這里所有的入?yún)⒈仨毷荂的類型峡懈。
而且初始化完了必須要加defer()自己手動(dòng)釋放。
cgo調(diào)用c++
這里有很多方式調(diào)用与斤,我選用的是cgo-->c-->c++的路線
因?yàn)榉究担腸++代碼是動(dòng)態(tài)庫(kù)荚恶,由于客戶端程序員不給我封裝c的 .h文件,(因?yàn)楣ぷ髁刻罅字В昧撕芏郼++std庫(kù)的東西谒撼,所以要全部封出來(lái)還需要很大的代碼量)
我自己寫的這個(gè)library.c就是從程序里依賴c++動(dòng)態(tài)庫(kù),動(dòng)態(tài)加載到內(nèi)存中雾狈。
具體不詳細(xì)寫了廓潜,直接上代碼
typedef struct tagUServerMgr* (*GetInstanceFunc)();
static GetInstanceFunc getInstance = NULL;
// 省略好多行
//打開動(dòng)態(tài)鏈接庫(kù)
handle = dlopen(lib_path, RTLD_NOW);
if (!handle) {
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}
//清除之前存在的錯(cuò)誤
dlerror();
getInstance = (GetInstanceFunc)dlsym(handle, "GetInstance");
if ((error = dlerror()) != NULL) {
fprintf(stderr, "%s\n", error);
exit(EXIT_FAILURE);
}
if (getInstance==NULL){
exit(EXIT_FAILURE);
}
// 省略好多行
這里代碼,我就是用RTLD_NOW的方式加載動(dòng)態(tài)庫(kù)善榛。然后獲取里面的getInstance方法辩蛋。之后,我go代碼直接可以C.GetInstance()
就可以調(diào)用移盆。我個(gè)人覺得挺方便的悼院。
cgo的性能測(cè)試,
在客戶端跑幀的時(shí)候咒循,測(cè)試過cgo的性能据途。
從go程序調(diào)用,到c方法入口叙甸,期間傳基礎(chǔ)數(shù)據(jù)類型颖医。測(cè)試下來(lái)平均調(diào)用一次消耗0.3ms。如果你的業(yè)務(wù)不需要1毫秒一下的效率裆蒸,可以放心大膽的使用熔萧。對(duì)復(fù)雜數(shù)據(jù)類型解析,會(huì)慢光戈,但是沒有明顯變化,在我的業(yè)務(wù)下遂赠,轉(zhuǎn)換一個(gè)1kb的數(shù)據(jù)久妆。大約在0.5ms調(diào)用一次,包括入?yún)⒊鰠r(shí)間跷睦。
cgo遇到的問題
問題:在測(cè)試中發(fā)現(xiàn)筷弦,當(dāng)我go程序sleep后,cgo調(diào)用的c方法效率會(huì)明顯下降抑诸。但是如果不sleep會(huì)效率能夠達(dá)到需求烂琴。由于,業(yè)務(wù)要求是幀同步蜕乡,所以我用sleep來(lái)卡ms時(shí)間
(經(jīng)過了與客戶端半天的爭(zhēng)吵奸绷,客戶端寫了個(gè)自測(cè)程序,發(fā)現(xiàn)沒有問題所以我懷疑是我這邊的問題)
解決思路:可能是time.sleep的操作层玲。
解決:更換成為time.tinker等time下其他的時(shí)間控制
結(jié)果:相同結(jié)果号醉,只要每次調(diào)用中間sleep30ms 就會(huì)變慢反症。
解決思路:如果只sleep1ms會(huì)不會(huì)變慢。
解決:分別測(cè)試了1ms畔派,2ms铅碍,5ms
結(jié)果:發(fā)現(xiàn)1ms也會(huì)變慢。問題清晰了許多线椰,似乎就是sleep的問題
解決思路:如果用卡時(shí)間戳的概念胞谈,模擬sleep的睡的時(shí)間是否能夠滿足需求,不斷地for循環(huán)憨愉,每次都判斷是否過了30ms烦绳,過了,就執(zhí)行一次莱衩,沒過就等下一次判斷爵嗅。
解決:寫了個(gè)for的例子。測(cè)試下
結(jié)果:發(fā)現(xiàn)的確快了笨蚁,是time.sleep的問題
- 目前為止:代碼層面已經(jīng)解決了睹晒,可能之后業(yè)務(wù)中也是使用for但是for有好幾個(gè)不好的地方,
- 只要跑就是cpu打滿括细。在go中伪很,會(huì)默認(rèn)分配1個(gè)攜程可以沾滿1個(gè)cpu。
- 會(huì)出現(xiàn)很多空轉(zhuǎn)for奋单,因?yàn)槲颐看握{(diào)用幀更新最小是1ms锉试,如果一個(gè)cpu一直在工作,那么最多處理1000幀更新請(qǐng)求览濒,那么一個(gè)玩家是一秒需要跑30幀呆盖,在這33個(gè)玩家正正好好不沖突的情況下,全部沒有網(wǎng)絡(luò)延遲贷笛,沒有io限制应又,等等,1個(gè)cpu最多支持33個(gè)玩家同時(shí)玩乏苦。但是這33不是不沖突的情況下株扛,假如說,某幾納秒所有人都在等下一個(gè)幀時(shí)間到來(lái)汇荐,那么cpu就會(huì)空轉(zhuǎn)洞就。浪費(fèi)cpu資源。
- 1個(gè)玩家也會(huì)沾滿cpu所有資源
這些問題掀淘,可能留到后期優(yōu)化了旬蟋,各位有什么好的建議可以可以私信我。
接下來(lái)查看go中的sleep是否對(duì)接下來(lái)的程序執(zhí)行效率會(huì)變慢革娄。
查看sleep的源碼發(fā)現(xiàn)咖为,sleep是采用系統(tǒng)中斷來(lái)喚醒睡著的程序秕狰。sleep這行代碼,做了比較重要的事情是
- 更改了系統(tǒng)終端表躁染,操作系統(tǒng)會(huì)不斷查看系統(tǒng)終端表來(lái)發(fā)中斷鸣哀,從而喚醒任務(wù)
- 把當(dāng)前線程從cpu任務(wù)隊(duì)列拿出,意思是當(dāng)前線程資源會(huì)從cpu中拿走吞彤。這也就是為什么sleep完了后會(huì)執(zhí)行變慢我衬,可能會(huì)有加載數(shù)據(jù)這段時(shí)間吧。