Go語言黑魔法

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

今天我要教大家一些無用技能,也可以叫它奇技淫巧或者黑魔法。用得好可以提升效能,用得不好就會招來惡魔,嘿嘿。

黑魔法導論

為了讓大家在學習了基礎黑魔法之後能有所悟,在必要的時候能創造出本文傳授之外的屬於自己的魔法,這裡需要先給大家打好基礎。

學習Go語言黑魔法之前,需要先看清Go世界的本質,你才能獲得像Neo一樣的能力。

在Go語言中,Slice本質是什麼呢?是一個reflect.SliceHeader結構體和這個結構體中Data欄位所指向的記憶體。String本質是什麼呢?是一個reflect.StringHeader結構體和這個結構體所指向的記憶體。

在Go語言中,指標的本質是什麼呢?是unsafe.Pointer和uintptr。

當你清楚了它們的本質之後,你就可以隨意的玩弄它們,嘿嘿嘿。

第一式 - 獲得Slice和String的記憶體資料

讓我小試身手,你有一個CGO介面要調用,需要你把一個字串資料或者位元組數組資料從Go這邊傳遞到C那邊,比如像這個:mysql/conn.go at master · funny/mysql · GitHub

查了各種教程和文檔,它們都告訴你要用C.GoString或C.GoBytes來轉換資料。

但是,當你調用這兩個函數的時候,發生了什麼事情呢?這時候Go複製了一份資料,然後再把新資料的地址傳給C,因為Go不想冒任何風險。

你的C程式只是想一次性的用一下這些資料,也不得不做一次資料複製,這對於一個效能癖來說是多麽可怕的一個事實!

這時候我們就需要一個黑魔法,來做到不拷貝資料又能把指標地址傳遞給C。

// returns &s[0], which is not allowed in gofunc stringPointer(s string) unsafe.Pointer {p := (*reflect.StringHeader)(unsafe.Pointer(&s))return unsafe.Pointer(p.Data)}// returns &b[0], which is not allowed in gofunc bytePointer(b []byte) unsafe.Pointer {p := (*reflect.SliceHeader)(unsafe.Pointer(&b))return unsafe.Pointer(p.Data)}

以上就是黑魔法第一式,我們先去到Go字串的指標,它本質上是一個*reflect.StringHeader,但是Go告訴我們這是一個*string,我們告訴Go它同時也是一個unsafe.Pointer,Go說好吧它是,於是你得到了unsafe.Pointer,接著你就躲過了Go的監視,偷偷的把unsafe.Pointer轉成了*reflect.StringHeader。

有了*reflect.StringHeader,你很快就取到了Data欄位指向的記憶體位址,它就是Go保護著不想給你看到的隱秘所在,你把這個地址偷偷告訴給了C,於是C就愉快的偷看了Go的隱私。

第二式 - 把[]byte轉成string

你肯定要笑,要把[]byte轉成string還不簡單?Go語言初學者都會的類型轉換文法:[]byte(str)。

但是你知道這麼做的代價嗎?既然我們能隨意的玩弄SliceHeader和StringHeader,為什麼我們不能造個string給Go呢?Go的內部會不會就是這麼做的呢?

先上個實驗吧:

package labs28import "testing"import "unsafe"func Test_ByteString(t *testing.T) {var x = []byte("Hello World!")var y = *(*string)(unsafe.Pointer(&x))var z = string(x)if y != z {t.Fail()}}func Benchmark_Normal(b *testing.B) {var x = []byte("Hello World!")for i := 0; i < b.N; i ++ {_ = string(x)}}func Benchmark_ByteString(b *testing.B) {var x = []byte("Hello World!")for i := 0; i < b.N; i ++ {_ = *(*string)(unsafe.Pointer(&x))}}

這個實驗先證明了我們可以用[]byte的資料造個string給Go。接著做了兩組Benchmark,分別測試了普通的類型轉換和偽造string的效率。

結果如下:

$ go test -bench="."PASSBenchmark_Normal    20000000            63.4 ns/opBenchmark_ByteString    2000000000           0.55 ns/opok      github.com/idada/go-labs/labs28 2.486s

喲西,顯然Go這次又為了穩定性做了些複製資料之類的事情了!這讓效能癖怎麼能忍受!

我現在手頭有個[]byte,但是我想用strconv.Atoi()把它轉成字面含義對應的整數值,竟然需要發生一次資料拷貝把它轉成string,比如像這樣:mysql/types.go at master · funny/mysql · GitHub,這實在不能忍啊!

出招:

// convert b to string without copyfunc byteString(b []byte) string {return *(*string)(unsafe.Pointer(&b))}

我們取到[]byte的指標,這次Go又告訴你它是*byte不是*string,你告訴它滾犢子這是unsafe.Pointer,Go這下又老實了,接著你很自在的把*byte轉成了*string,因為你知道reflect.StringHeader和reflect.SliceHeader的結構體只相差末尾一個欄位,兩者的記憶體是對其的,沒必要再取Data欄位了,直接轉吧。

於是,世界終於安寧了,嘿嘿。

第三式 - 結構體和[]byte互轉

有一天,你想把一個簡單的結構體轉成位元據儲存起來,這時候你想到了encoding/gob和encoding/json,做了一下效能測試,你想到效率有沒有可能更高點?

於是你又試了encoding/binady,效能也還可以,但是你還不滿意。但是瓶頸在哪裡呢?你恍然大悟,最高效的辦法就是完全不解析資料也不產生資料啊!

怎麼做?是時候使用這個黑魔法了:

type MyStruct struct {A intB int}var sizeOfMyStruct = int(unsafe.Sizeof(MyStruct{}))func MyStructToBytes(s *MyStruct) []byte {var x reflect.SliceHeaderx.Len = sizeOfMyStructx.Cap = sizeOfMyStructx.Data = uintptr(unsafe.Pointer(s))return *(*[]byte)(unsafe.Pointer(&x))}func BytesToMyStruct(b []byte) *MyStruct {return (*MyStruct)(unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(&b)).Data,))}

這是個曲折但又熟悉的故事。你造了一個SliceHeader,想把它的Data欄位指向你的結構體,但是Go又告訴你不可以,你像往常那樣把Go提到一邊,你得到了unsafe.Pointer,但是這次Go有不死心,它告訴你Data是uintptr,unsafe.Pointer不是uintptr,你大腳把它踢開,怒吼道:unsafe.Pointer就是uintptr,你少拿這些概念糊弄我,Go屁顛屁顛的跑開了,現在你一馬平川的來到了函數的出口,Go竟然已經在哪裡等著你了!你上前三下五除二把它踢得遠遠的,順利的把手頭的SliceHeader轉成了[]byte。

過了一陣子,你拿到了一個[]byte,你知道需要把它轉成MyStruct來讀取其中的資料。Go這時候已經完全不是你的對手了,它已經洗好屁股在函數入口等你,你一行代碼就解決了它。

第四式 - 用CGO最佳化GC

你已經是Go世界的Neo,Go跟本沒辦法拿你怎麼樣。但是有一天Go的GC突然抽風了,原來這貨是不管對象怎麼用的,每次GC都給來一遍人口普查,導致系統暫停時間很長。

可是你是個效能癖,你把一堆資料都放在記憶體裡方便快速存取,你這時候很想再踢Go的屁股,但是你沒辦法,畢竟你還在Go的世界裡,你現在得替它擦屁股了,你似乎看到Go躲在一旁偷笑。

你想到你手頭有CGO,可以輕易的用C申請到Go世界外的記憶體,Go的GC不會掃描這部分記憶體。

你還想到你可以用unsafe.Pointer將C的指標轉成Go的結構體指標。於是一大批常駐記憶體對象被你用這種方式轉成了Go世界的黑戶,Go的GC一下子輕鬆了下來。

但是你手頭還有很多Slice,於是你就利用C申請記憶體給SliceHeader來構造自己的Slice,於是你旗下的Slice紛紛轉成了Go世界的黑戶,Go的GC終於平靜了。

但好景總是不長久,有一天Go世界突然崩潰了,只留下一句話:Segmentation Fault。你一下慫了,怎麼段錯誤了?

經過一個通宵排查,你發現你管轄的黑戶對象竟然偷偷的跟Go世界的其它合法居民搞在一起,當Go世界以為某個居民已經消亡時,用GC回收了它的住所,但是你的地下世界卻認為它還活著,還繼續訪問它。

於是你廢了一番功夫斬斷了所有關聯,世界暫時寧靜了下來。

但是你已經很累了,這時候你想起一句話:

為無為,則無不治
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.