PHP FFI呼叫go,居然比go還快

2022-05-22 15:21:12 字數 2964 閱讀 3684

這次發現了乙個golang的分詞庫gse,試試匯出為動態庫,用ffi載入。

由於之前對cgo不熟悉,以為go可以很方便的匯出到c,沒想到一開始就把我難倒。

panic: runtime error: cgo result has go pointer

一開始直接在go裡返回了string,沒想到報錯了,原來go不允許匯出含有指標的資料結構

go type not supported in export: struct

後來想,要不匯出string 的指標,但是如果只有指標位址,沒有長度,遍歷肯定會出錯,於是構造了乙個結構體,儲存指標位址和長度,沒想到還是不行。

這期間,由於工作忙(主要是懶),斷斷續續的看了一下cgo相關的內容,先跑通了c呼叫go,於是再試著用ffi,很快也跑通了。

在go裡,匯出乙個函式到c動態庫,其實非常簡單,需要在import c包,並在匯出的函式加上export 函式名,加上乙個空的main 函式即可,如:

package main

import (

"c")//必須和函式同名

//export plusone

func plusone(num int) int

func main()

編譯方法如下:

go build -buildmode=c-shared -o libdemo.so demo.go

就會自動生成 so 和 libdemo.h 標頭檔案,開啟libdemo.h,可以看到裡面是各種go 資料型別的定義,摘除部分如下:

typedef signed char goint8;

typedef unsigned char gouint8;

typedef short goint16;

typedef unsigned short gouint16;

typedef int goint32;

typedef unsigned int gouint32;

typedef long long goint64;

typedef unsigned long long gouint64;

可以看到還包含了乙個另外的標頭檔案#include,可以使用gcc -e -p libdemo.h -o libdemo_unfold.h展開stddef.h合併到乙個到頭檔案,然後複製我們需要的型別定義即可。

由於go的string,slice匯出後,都是乙個結構體,不是乙個簡單型別,這裡我們先看看string。

typedef struct gostring;

可以看到string有乙個char* 指標,和乙個表示長度的n,可以說明go的string是不帶'\n'的,和c的字串不同。然而一開始,我居然還特意加了'\n',然後給n也加1,結果發現不對,在go那邊加上輸出後,才發現出錯了。

對於這種結構體,用載入動態庫的ffi例項呼叫 new 方法。即$gostr = $ffi->new('gostring',0),注意new的第二個引數要傳0,表示這個物件php不用管理記憶體。在這個地方,我又掉坑里了。

然後要給p和n賦值,對於n,比較簡單,直接給字串長度,但是對於p,就比較麻煩。

翻看php文件,發現有個memcpy方法,於是試了一下,成功的實現了php和go之間傳string。

完整的**如下:

function makegostr(ffi $ffi, string $str): ffi\cdata

在上面的**裡,既有ffi的靜態方法,也有例項方法,它們之間的區別在於,靜態方法只有常用的資料型別,如果int,char;例項方法,才能呼叫載入的so裡面的型別。

下面我說一下三種呼叫思路,建議第一種,這裡就不貼**了,完整的**看github。

由於go不能返回slice string,那麼換個思路,把陣列拼接成字串,然後返回c.char。這種方式最簡單,而且在後面的跑分測試裡發現,也是最有效率的。

複雜的資料結構,可以序列化為string 然後返回c.char

既然不能返回,那麼我們修改傳入的引數是否可以呢。通過測試發現確實可行。

這就是一開始我的想法,這種方法有點麻煩,而且速度也不佔優。

make lib,生成go的動態庫,然後make php_testmake go_test檢視對比。

go的

testcut: goseg_test.go:18 cutchar 2000 次用時:41.511794ms

testcut: goseg_test.go:26 cutpointer 2000 次用時:45.24684ms

testcut: goseg_test.go:34 cutslice 2000 次用時:42.537337ms

php的

cutchar 2000 次用時:0.027052 s

cutslice 2000 次用時:0.038451 s

cutpointer 2000 次用時:0.038257 s

可以發現php居然比go的還快,比cjieba快了不知道多少倍,看來以後一些耗cpu的方式,可以用go來開發動態庫,給php用,比通過介面呼叫可以快很多。

假如go和php呼叫需要5ms,這樣2000次就是 10s了。可以發現ffi是介面呼叫的0.04/10 = 0.004,是幾百倍數量級的提公升。當然實際情況更複雜,但是效能提公升可是顯而易見的。

當然前提是選擇乙個效能高的ffi外部庫才行,如果比php還慢,那就不必了。

另外ffi可以預載入,鳥哥的部落格寫的很詳細了,大家可以去看看。

Go入門 方法呼叫

package main import container vector func main k2 vector.intvector k3 new vector.intvector k1.push 2 k2.push 3 k3.push 4 k1,k2,k3的型別分別是什麼?k1 的型別是vecto...

go 如何鏈式呼叫

go 的錯誤處理機制是返回錯誤,但是鏈式呼叫只能有乙個返回值,如果做到返回乙個值並且能夠處理錯誤資訊呢,可以用go 支援函式式程式設計實現。模擬htttp傳送請求的過程,逐步完成對request請求體的封裝然後呼叫傳送方法 1,定義了乙個request結構體模擬request請求 2,定義了乙個函式...

Go語言呼叫dll

user32 syscall.newlazydll imobiledevice.dll messageboxw user32.newproc idevice event subscribe messageboxw.call uintptr c.test uintptr s imobiledevice...