• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            Fork me on GitHub
            隨筆 - 215  文章 - 13  trackbacks - 0
            <2017年3月>
            2627281234
            567891011
            12131415161718
            19202122232425
            2627282930311
            2345678


            專注即時通訊及網(wǎng)游服務(wù)端編程
            ------------------------------------
            Openresty 官方模塊
            Openresty 標準模塊(Opm)
            Openresty 三方模塊
            ------------------------------------
            本博收藏大部分文章為轉(zhuǎn)載,并在文章開頭給出了原文出處,如有再轉(zhuǎn),敬請保留相關(guān)信息,這是大家對原創(chuàng)作者勞動成果的自覺尊重!!如為您帶來不便,請于本博下留言,謝謝配合。

            常用鏈接

            留言簿(1)

            隨筆分類

            隨筆檔案

            相冊

            Awesome

            Blog

            Book

            GitHub

            Link

            搜索

            •  

            積分與排名

            • 積分 - 216756
            • 排名 - 118

            最新評論

            閱讀排行榜

            作者:達達
            鏈接:https://zhuanlan.zhihu.com/p/20010926
            來源:知乎
            著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。

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

            黑魔法導論

            為了讓大家在學習了基礎(chǔ)黑魔法之后能有所悟,在必要的時候能創(chuàng)造出本文傳授之外的屬于自己的魔法,這里需要先給大家打好基礎(chǔ)。

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

            在Go語言中,Slice本質(zhì)是什么呢?是一個reflect.SliceHeader結(jié)構(gòu)體和這個結(jié)構(gòu)體中Data字段所指向的內(nèi)存。String本質(zhì)是什么呢?是一個reflect.StringHeader結(jié)構(gòu)體和這個結(jié)構(gòu)體所指向的內(nèi)存。

            在Go語言中,指針的本質(zhì)是什么呢?是unsafe.Pointer和uintptr。

            當你清楚了它們的本質(zhì)之后,你就可以隨意的玩弄它們,嘿嘿嘿。

            第一式 - 獲得Slice和String的內(nèi)存數(shù)據(jù)

            讓我小試身手,你有一個CGO接口要調(diào)用,需要你把一個字符串數(shù)據(jù)或者字節(jié)數(shù)組數(shù)據(jù)從Go這邊傳遞到C那邊,比如像這個:mysql/conn.go at master · funny/mysql · GitHub

            查了各種教程和文檔,它們都告訴你要用C.GoString或C.GoBytes來轉(zhuǎn)換數(shù)據(jù)。

            但是,當你調(diào)用這兩個函數(shù)的時候,發(fā)生了什么事情呢?這時候Go復制了一份數(shù)據(jù),然后再把新數(shù)據(jù)的地址傳給C,因為Go不想冒任何風險。

            你的C程序只是想一次性的用一下這些數(shù)據(jù),也不得不做一次數(shù)據(jù)復制,這對于一個性能癖來說是多麼可怕的一個事實!

            這時候我們就需要一個黑魔法,來做到不拷貝數(shù)據(jù)又能把指針地址傳遞給C。

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

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

            有了*reflect.StringHeader,你很快就取到了Data字段指向的內(nèi)存地址,它就是Go保護著不想給你看到的隱秘所在,你把這個地址偷偷告訴給了C,于是C就愉快的偷看了Go的隱私。

            第二式 - 把[]byte轉(zhuǎn)成string

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

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

            先上個實驗吧:

            package labs28
            
            import "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的數(shù)據(jù)造個string給Go。接著做了兩組Benchmark,分別測試了普通的類型轉(zhuǎn)換和偽造string的效率。

            結(jié)果如下:

            $ go test -bench="."
            PASS
            Benchmark_Normal    20000000            63.4 ns/op
            Benchmark_ByteString    2000000000           0.55 ns/op
            ok      github.com/idada/go-labs/labs28 2.486s
            

            喲西,顯然Go這次又為了穩(wěn)定性做了些復制數(shù)據(jù)之類的事情了!這讓性能癖怎么能忍受!

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

            出招:

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

            我們?nèi)〉絒]byte的指針,這次Go又告訴你它是*byte不是*string,你告訴它滾犢子這是unsafe.Pointer,Go這下又老實了,接著你很自在的把*byte轉(zhuǎn)成了*string,因為你知道reflect.StringHeader和reflect.SliceHeader的結(jié)構(gòu)體只相差末尾一個字段,兩者的內(nèi)存是對齊的,沒必要再取Data字段了,直接轉(zhuǎn)吧。

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

            第三式 - 結(jié)構(gòu)體和[]byte互轉(zhuǎn)

            有一天,你想把一個簡單的結(jié)構(gòu)體轉(zhuǎn)成二進制數(shù)據(jù)保存起來,這時候你想到了encoding/gob和encoding/json,做了一下性能測試,你想到效率有沒有可能更高點?

            于是你又試了encoding/binady,性能也還可以,但是你還不滿意。但是瓶頸在哪里呢?你恍然大悟,最高效的辦法就是完全不解析數(shù)據(jù)也不產(chǎn)生數(shù)據(jù)啊!

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

            type MyStruct struct {
            	A int
            	B int
            }
            
            var sizeOfMyStruct = int(unsafe.Sizeof(MyStruct{}))
            
            func MyStructToBytes(s *MyStruct) []byte {
            	var x reflect.SliceHeader
            	x.Len = sizeOfMyStruct
            	x.Cap = sizeOfMyStruct
            	x.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字段指向你的結(jié)構(gòu)體,但是Go又告訴你不可以,你像往常那樣把Go提到一邊,你得到了unsafe.Pointer,但是這次Go有不死心,它告訴你Data是uintptr,unsafe.Pointer不是uintptr,你大腳把它踢開,怒吼道:unsafe.Pointer就是uintptr,你少拿這些概念糊弄我,Go屁顛屁顛的跑開了,現(xiàn)在你一馬平川的來到了函數(shù)的出口,Go竟然已經(jīng)在哪里等著你了!你上前三下五除二把它踢得遠遠的,順利的把手頭的SliceHeader轉(zhuǎn)成了[]byte。

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

            第四式 - 用CGO優(yōu)化GC

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

            可是你是個性能癖,你把一堆數(shù)據(jù)都放在內(nèi)存里方便快速訪問,你這時候很想再踢Go的屁股,但是你沒辦法,畢竟你還在Go的世界里,你現(xiàn)在得替它擦屁股了,你似乎看到Go躲在一旁偷笑。

            你想到你手頭有CGO,可以輕易的用C申請到Go世界外的內(nèi)存,Go的GC不會掃描這部分內(nèi)存。

            你還想到你可以用unsafe.Pointer將C的指針轉(zhuǎn)成Go的結(jié)構(gòu)體指針。于是一大批常駐內(nèi)存對象被你用這種方式轉(zhuǎn)成了Go世界的黑戶,Go的GC一下子輕松了下來。

            但是你手頭還有很多Slice,于是你就利用C申請內(nèi)存給SliceHeader來構(gòu)造自己的Slice,于是你旗下的Slice紛紛轉(zhuǎn)成了Go世界的黑戶,Go的GC終于平靜了。

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

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

            于是你廢了一番功夫斬斷了所有關(guān)聯(lián),世界暫時寧靜了下來。

            但是你已經(jīng)很累了,這時候你想起一句話:

            為無為,則無不治
            posted on 2017-05-04 10:35 思月行云 閱讀(169) 評論(0)  編輯 收藏 引用 所屬分類: Golang
            久久天天躁狠狠躁夜夜2020一 | 国产V综合V亚洲欧美久久| 无码人妻久久一区二区三区免费| 无码国内精品久久人妻蜜桃| 久久亚洲高清观看| 性做久久久久久久久| 激情伊人五月天久久综合| 四虎亚洲国产成人久久精品| 久久99精品久久久久婷婷| 久久最新免费视频| 久久精品国产亚洲网站| 中文字幕久久久久人妻| 91精品免费久久久久久久久| 国内精品久久久久久久久电影网| 69久久夜色精品国产69| 久久婷婷五月综合国产尤物app| 久久精品人人做人人爽电影| 99久久精品免费看国产一区二区三区| 93精91精品国产综合久久香蕉| 久久无码国产专区精品| 亚洲国产成人久久综合一区77| 色综合久久中文综合网| 色欲av伊人久久大香线蕉影院| 久久夜色精品国产亚洲av| 亚洲综合精品香蕉久久网97| 中文无码久久精品| 无码日韩人妻精品久久蜜桃| 国产精品久久新婚兰兰| 色老头网站久久网| 色99久久久久高潮综合影院| 99热都是精品久久久久久| 91麻豆精品国产91久久久久久| 97久久精品无码一区二区天美| 色婷婷综合久久久久中文一区二区| yy6080久久| 国产69精品久久久久9999APGF| 久久精品人妻中文系列| 亚洲国产精品无码久久久秋霞2| 亚洲精品无码久久千人斩| 久久久久久久久波多野高潮| 国产成年无码久久久免费|