Go語言·聽說你想讓程序運(yùn)行的更快?

遷移自CSDN:http://blog.csdn.net/erlib/article/details/51219512

原文:http://bravenewgeek.com/so-you-wanna-go-fast/

到現(xiàn)在為止,我已經(jīng)忘記了我在寫什么,但我確定這篇文章是關(guān)于Go語言的。這主要是一篇,關(guān)于運(yùn)行速度,而不是開發(fā)速度的文章——這兩種速度是有區(qū)別的。

我曾經(jīng)和很多聰明的人一起工作。我們很多人都對(duì)性能問題很癡迷,我們之前所做的是嘗試逼近能夠預(yù)期的(性能)的極限。應(yīng)用引擎有一些非常嚴(yán)格的性能要求,所以我們才會(huì)做出改變。自從使用了Go語言之后,我們已經(jīng)學(xué)習(xí)到了很多提升性能以及讓Go在系統(tǒng)編程中正常運(yùn)轉(zhuǎn)的方法。

Go的簡單和原生并發(fā)使其成為一門非常有吸引力的后端開發(fā)語言,但更大的問題是它如何應(yīng)對(duì)延遲敏感的應(yīng)用場景?是否值得犧牲語言的簡潔性使其速度更快?讓我們來一起看一下Go語言性能優(yōu)化的幾個(gè)方面:語言特性、內(nèi)存管理、并發(fā),并根據(jù)這些做出合適的優(yōu)化決策。所有這里介紹的測試代碼都在這里.

Channels

Channel在Go語言中受到了很多的關(guān)注,因?yàn)樗且粋€(gè)方便的并發(fā)工具,但是了解它對(duì)性能的影響也很重要。在大多數(shù)場景下它的性能已經(jīng)“足夠好”了,但是在某些延時(shí)敏感的場景中,它可能會(huì)成為瓶頸。Channel并不是什么黑魔法。在Channel的底層實(shí)現(xiàn)中,使用的還是鎖。在沒有鎖競爭的單線程應(yīng)用中,它能工作的很好,但是在多線程場景下,性能會(huì)急劇下降。我們可以很容易的使用無鎖隊(duì)列ring buffer來替代channel的功能。

第一個(gè)性能測試對(duì)比了單線程buffer channel和ring buffer(一個(gè)生產(chǎn)者和一個(gè)消費(fèi)者)。先看看單核心的情況(GOMAXPROCS = 1)

BenchmarkChannel 3000000 512 ns/op
BenchmarkRingBuffer 20000000 80.9 ns/op

正如你所看到的,ring buffer大約能快6倍(如果你不熟悉Go的性能測試工具,中間的數(shù)字表示執(zhí)行次數(shù),最后一個(gè)數(shù)組表示每次執(zhí)行花費(fèi)的時(shí)間)。接下來,我們?cè)倏聪抡{(diào)整GOMAXPROCS = 8的情況。

BenchmarkChannel-8 3000000 542 ns/op
BenchmarkRingBuffer-8 10000000 182 ns/op

ring buffer快了近三倍

Channel通常用于給worker分配任務(wù)。在下面的測試中,我們對(duì)比一下多個(gè)reader讀取同一個(gè)channel或者ring buffer的情況。設(shè)置GOMAXPROCS = 1 測試結(jié)果表明channel在單核程應(yīng)用中性能表現(xiàn)尤其的好。

BenchmarkChannelReadContention 10000000 148 ns/op
BenchmarkRingBufferReadContention 10000 390195 ns/op

然而,ring buffer在多核心的情況下速度更快些:

BenchmarkChannelReadContention-8 1000000 3105 ns/op
BenchmarkRingBufferReadContention-8 3000000 411 ns/op

最后,我們來看看多個(gè)reader和多個(gè)writer的場景。從下面的對(duì)比同樣能夠看到ring buffer在多核心時(shí)更好些。

BenchmarkChannelContention 10000 160892 ns/op
BenchmarkRingBufferContention 28068 34344 ns/op
BenchmarkChannelContention-8 5000 314428 ns/op
BenchmarkRingBufferContention-8 10000 182557 ns/op

ring buffer只使用CAS操作達(dá)到線程安全。我們可以看到,在決定選擇channel還是ring buffer時(shí)很大程度上取決于系統(tǒng)的核數(shù)。對(duì)于大多數(shù)系統(tǒng), GOMAXPROCS> 1,所以無鎖的ring buffer往往是一個(gè)更好的選擇。Channel在多核心系統(tǒng)中則是一個(gè)比較糟糕的選擇。

defer

defer是提高可讀性和避免資源未釋放的非常有用的關(guān)鍵字。例如,當(dāng)我們打開一個(gè)文件進(jìn)行讀取時(shí),我們需要在結(jié)束讀取時(shí)關(guān)閉它。如果沒有defer關(guān)鍵字,我們必須確保在函數(shù)的每個(gè)返回點(diǎn)之前關(guān)閉文件。

func findHelloWorld(filename string) error {  
        file, err := os.Open(filename)  
        if err != nil {  
                return err  
        }  
          
        scanner := bufio.NewScanner(file)  
        for scanner.Scan() {  
                if scanner.Text() == "hello, world!" {  
                        file.Close()  
                        return nil  
                }  
        }  
  
        file.Close()  
        if err := scanner.Err(); err != nil {  
                return err  
        }  
          
        return errors.New("Didn't find hello world")  
}  

這樣很容易出錯(cuò),因?yàn)楹苋菀自谌魏我粋€(gè)return語句前忘記關(guān)閉文件。defer則通過一行代碼解決了這個(gè)問題。

func findHelloWorld(filename string) error {  
        file, err := os.Open(filename)  
        if err != nil {  
                return err  
        }  
        defer file.Close()  
          
        scanner := bufio.NewScanner(file)  
        for scanner.Scan() {  
                if scanner.Text() == "hello, world!" {  
                        return nil  
                }  
        }  
  
        if err := scanner.Err(); err != nil {  
                return err  
        }  
          
        return errors.New("Didn't find hello world")  
}  

乍一看,人們會(huì)認(rèn)為defer明可能會(huì)被編譯器完全優(yōu)化掉。如果我只是在函數(shù)的開頭使用了defer語句,編譯器確實(shí)可以通過在每一個(gè)return語句之前插入defer內(nèi)容來實(shí)現(xiàn)。但是實(shí)際情況往往更復(fù)雜。比如,我們可以在條件語句或者循環(huán)中添加defer。第一種情況可能需要編譯器找到應(yīng)用defer語句的條件分支. 編譯器還需要檢查panic的情況,因?yàn)檫@也是函數(shù)退出執(zhí)行的一種情況。通過靜態(tài)編譯提供這個(gè)功能(defer)似乎(至少從表面上看)是不太可能的。

derfer并不是一個(gè)零成本的關(guān)鍵字,我們可以通過性能測試來看一下。在下面的測試中,我們對(duì)比了一個(gè)互斥鎖在循環(huán)體中加鎖后,直接解鎖以及使用defer語句解鎖的情況。

BenchmarkMutexDeferUnlock-8 20000000 96.6 ns/op
BenchmarkMutexUnlock-8 100000000 19.5 ns/op

使用defer幾乎慢了5倍。平心而論,77ns也許并不那么重要,但是在一個(gè)循環(huán)中它確實(shí)對(duì)性能產(chǎn)生了影響。通常要由開發(fā)者在性能和代碼的易讀性上做權(quán)衡。優(yōu)化從來都是需要成本的(譯者注:在Go1.9種defer性能大概提升為50-60ns/op)。

Json與反射

Reflection通常是緩慢的,應(yīng)當(dāng)避免在延遲敏感的服務(wù)中使用。JSON是一種常用的數(shù)據(jù)交換格式,但Go的encoding/json庫依賴于反射來對(duì)json進(jìn)行序列化和反序列化。使用ffjson(譯者注:easyjson會(huì)更快),我們可以通過使用代碼生成的方式來避免反射的使用,下面是性能對(duì)比。

BenchmarkJSONReflectionMarshal-8 200000 7063 ns/op
BenchmarkJSONMarshal-8 500000 3981 ns/op

BenchmarkJSONReflectionUnmarshal-8 200000 9362 ns/op
BenchmarkJSONUnmarshal-8 300000 5839 ns/op

(ffjson)生成的JSON序列化和反序列化比基于反射的標(biāo)準(zhǔn)庫速度快38%左右。當(dāng)然,如果我們對(duì)編解碼的性能要求真的很高,我們應(yīng)該避免使用JSON。MessagePack是序列化代碼一個(gè)更好的選擇。在這次測試中我們使用msgp庫跟JSON的做了對(duì)比。

BenchmarkMsgpackMarshal-8 3000000 555 ns/op
BenchmarkJSONReflectionMarshal-8 200000 7063 ns/op
BenchmarkJSONMarshal-8 500000 3981 ns/op

BenchmarkMsgpackUnmarshal-8 20000000 94.6 ns/op
BenchmarkJSONReflectionUnmarshal-8 200000 9362 ns/op
BenchmarkJSONUnmarshal-8 300000 5839 ns/op

這里的差異是顯著的。即使是跟(ffjson)生成的代碼相比,MessagePack仍然快很多。

如果我們真的很在意微小的優(yōu)化,我們還應(yīng)該避免使用interface類型, 它需要在序列化和反序列化時(shí)做一些額外的處理。在一些動(dòng)態(tài)調(diào)用的場景中,運(yùn)行時(shí)調(diào)用也會(huì)增加一些額外 開銷。編譯器無法將這些調(diào)用替換為內(nèi)聯(lián)調(diào)用。

BenchmarkJSONReflectionUnmarshal-8 200000 9362 ns/op
BenchmarkJSONReflectionUnmarshalIface-8 200000 10099 ns/op

我們?cè)倏纯凑{(diào)用查找,即把一個(gè)interface變量轉(zhuǎn)換為它真實(shí)的類型。這個(gè)測試調(diào)用了同一個(gè)struct的同一個(gè)方法。區(qū)別在于第二個(gè)變量是一個(gè)指向結(jié)構(gòu)體的一個(gè)指針。

BenchmarkStructMethodCall-8 2000000000 0.44 ns/op
BenchmarkIfaceMethodCall-8 1000000000 2.97 ns/op

排序是一個(gè)更加實(shí)際的例子,很好的顯示了性能差異。在這個(gè)測試中,我們比較排序1,000,000個(gè)結(jié)構(gòu)體和1,000,000個(gè)指向相同結(jié)構(gòu)體的interface。對(duì)結(jié)構(gòu)體進(jìn)行排序比對(duì)interface進(jìn)行排序快63%。

BenchmarkSortStruct-8 10 105276994 ns/op
BenchmarkSortIface-8 5 286123558 ns/op

總之,如果可能的話避免使用JSON。如果確實(shí)需要用JSON,生成序列化和反序列化代碼。一般來說,最好避免依靠反射和interface,而是編寫使用的具體類型。不幸的是,這往往導(dǎo)致很多重復(fù)的代碼,所以最好以抽象的這個(gè)代碼生成。再次,權(quán)衡得失。

內(nèi)存管理

Go實(shí)際上不暴露堆或直接堆棧分配給用戶。事實(shí)上,“heap”和“stack”這兩個(gè)詞沒有出現(xiàn)在Go語言規(guī)范的任何地方。這意味著有關(guān)棧和堆東西只在技術(shù)上實(shí)現(xiàn)相關(guān)。實(shí)際上,每個(gè)goroutine確實(shí)有著自己堆和棧。編譯器確實(shí)難逃分析,以確定對(duì)象是在棧上還是在堆中分配。

不出所料的,避免堆分配可以成為優(yōu)化的主要方向。通過在棧中分配空間(即多使用A{}的方式創(chuàng)建對(duì)象,而不是使用new(A)的方式),我們避免了昂貴的malloc調(diào)用,如下面所示的測試。

BenchmarkAllocateHeap-8 20000000 62.3 ns/op 96 B/op 1 allocs/op
BenchmarkAllocateStack-8 100000000 11.6 ns/op 0 B/op 0 allocs/op

自然,通過指針傳值比通過對(duì)象傳世要快,因?yàn)榍罢咝枰獜?fù)制唯一的一個(gè)指針,而后者需要復(fù)制整個(gè)對(duì)象。下面測試結(jié)果中的差異幾乎是可以忽略的,因?yàn)檫@個(gè)差異很大程度上取決于被拷貝的對(duì)象的類型。注意,可能有一些編譯器對(duì)對(duì)這個(gè)測試進(jìn)行一些編譯優(yōu)化。

BenchmarkPassByReference-8 1000000000 2.35 ns/op
BenchmarkPassByValue-8 200000000 6.36 ns/op

然而,heap空間分配的最大的問題在于GC(垃圾回收)。如果我們生成了很多生命周期很短的對(duì)象,我們會(huì)觸發(fā)GC工作。在這種場景中對(duì)象池就派上用場了。在下面的測試用,我們比較了使用堆分配與使用sync.Pool的情況。對(duì)象池提升了5倍的性能。

BenchmarkConcurrentStructAllocate-8 5000000 337 ns/op
BenchmarkConcurrentStructPool-8 20000000 65.5 ns/op

需要指出的是,Go的sysc.Pool在垃圾回收過程中也會(huì)被回收。使用sync.Pool的作用是復(fù)用垃圾回收操作之間的內(nèi)存。我們也可以維護(hù)自己的空閑對(duì)象列表使對(duì)象不被回收,但這樣可能就讓垃圾回收失去了應(yīng)有的作用。Go的pprof工具在分析內(nèi)存使用情況時(shí)非常有用的。在盲目做內(nèi)存優(yōu)化之前一定要使用它來進(jìn)行分析。

親和緩存

當(dāng)性能真的很重要時(shí),你必須開始硬件層次的思考。著名的一級(jí)方程式車手杰基·斯圖爾特曾經(jīng)說過,“要成為一個(gè)賽車手你不必成為一名工程師,但你必須有機(jī)械知識(shí)。”深刻理解一輛汽車的內(nèi)部工作原理可以讓你成為一個(gè)更好的駕駛員。同樣,理解計(jì)算機(jī)如何工作可以使你成為一個(gè)更好的程序員。例如,內(nèi)存如何布局的?CPU緩存如何工作的?硬盤如何工作的?

內(nèi)存帶寬仍然是現(xiàn)代CPU的有限資源,因此緩存就顯得極為重要,以防止性能瓶頸?,F(xiàn)在的多核處理器把數(shù)據(jù)緩存在cache line中,大小通常為64個(gè)字節(jié),以減少開銷較大的主存訪問。為了保證cache的一致性,對(duì)內(nèi)存的一個(gè)小小的寫入都會(huì)讓cache line被淘汰。對(duì)相鄰地址的讀操作就無法命中對(duì)應(yīng)的cache line。這種現(xiàn)象叫做false sharing。 當(dāng)多個(gè)線程訪問同一個(gè)cache line中的不同數(shù)據(jù)時(shí)這個(gè)問題就會(huì)變得很明顯。

想象一下,Go語言中的一個(gè)struct是如何在內(nèi)存中存儲(chǔ)的,我們用之前的ring buffer 作為一個(gè)示例,結(jié)構(gòu)體可能是下面這樣:

type RingBuffer struct {  
    queue          uint64  
    dequeue        uint64  
    mask, disposed uint64  
    nodes          nodes  
}  

queue和dequeue字段分別用于確定生產(chǎn)者和消費(fèi)者的位置。這些字段的大小都是8byte,同時(shí)被多個(gè)線程并發(fā)訪問和修改來實(shí)現(xiàn)隊(duì)列的插入和刪除操作,因?yàn)檫@些字段在內(nèi)存中是連續(xù)存放的,它們僅僅使用了16byte的內(nèi)存,它們很可能被存放在同一個(gè)cache line中。因此修改其中的任何一個(gè)字段都會(huì)導(dǎo)致其它字段緩存被淘汰,也就意味著接下來的讀取操作將會(huì)變慢。也就是說,在ring buffer中添加和刪除元素會(huì)導(dǎo)致很多的CPU緩存失效。

我們可以給結(jié)構(gòu)體的字段直接增加padding.每一個(gè)padding都跟一個(gè)CPU cache line一樣大,這樣就能確保ring buffer的字段被緩存在不同的cache line中。下面是修改后的結(jié)構(gòu)體:

type RingBuffer struct {  
    _padding0      [8]uint64  
    queue          uint64  
    _padding1      [8]uint64  
    dequeue        uint64  
    _padding2      [8]uint64  
    mask, disposed uint64  
    _padding3      [8]uint64  
    nodes          nodes  
}  

實(shí)際運(yùn)行時(shí)會(huì)有多少區(qū)別呢?跟其他的優(yōu)化一樣,優(yōu)化效果取決于實(shí)際場景。它跟CPU的核數(shù)、資源競爭的數(shù)量、內(nèi)存的布局有關(guān)。雖然有很多的因素要考慮,我們還是要用數(shù)據(jù)來說話。我們可以用添加過padding和沒有padding的ring buffer來做一個(gè)對(duì)比。

首先,我們測試一個(gè)生產(chǎn)者和一個(gè)消費(fèi)者的情況,它們分別運(yùn)行在一個(gè)gorouting中.在這個(gè)測試中,兩者的差別非常小,只有不到15%的性能提升:

BenchmarkRingBufferSPSC-8 10000000 156 ns/op
BenchmarkRingBufferPaddedSPSC-8 10000000 132 ns/op

但是,當(dāng)我們有多個(gè)生產(chǎn)者和多個(gè)消費(fèi)者時(shí),比如各100個(gè),區(qū)別就會(huì)得更加明顯。在這種情況下,填充的版本快了約36%。

BenchmarkRingBufferMPMC-8 100000 27763 ns/op
BenchmarkRingBufferPaddedMPMC-8 100000 17860 ns/op

False sharing是一個(gè)非?,F(xiàn)實(shí)的問題。根據(jù)并發(fā)和內(nèi)存爭的情況,添加Padding以減輕其影響。這些數(shù)字可能看起來微不足道的,但它已經(jīng)起到優(yōu)化作用了,特別是在考慮到在時(shí)鐘周期的情況下。

無鎖共享

無鎖的數(shù)據(jù)結(jié)構(gòu)對(duì)充分利用多核心是非常重要的??紤]到Go致力于高并發(fā)的使用場景,它不鼓勵(lì)使用鎖。它鼓勵(lì)更多的使用channel而不是互斥鎖。

這就是說,標(biāo)準(zhǔn)庫確實(shí)提供了常用的內(nèi)存級(jí)別的原子操作, 如atomic包。它提供了原子比較并交換,原子指針訪問。然而,使用原子包在很大程度上是不被鼓勵(lì)的:

We generally don’t want sync/atomic to be used at all…Experience has shown us again and again that very very few people are capable of writing correct code that uses atomic operations…If we had thought of internal packages when we added the sync/atomic package, perhaps we would have used that. Now we can’t remove the package because of the Go 1 guarantee.

實(shí)現(xiàn)無鎖有多困難?是不是只要用一些CAS實(shí)現(xiàn)就可以了?在了解了足夠多的知識(shí)后,我認(rèn)識(shí)到這絕對(duì)是一把雙刃劍。無鎖的代碼實(shí)現(xiàn)起來可能會(huì)非常復(fù)雜。atomic和unsafe包并不易用。而且,編寫線程安全的無鎖代碼非常有技巧性并且很容易出錯(cuò)。像ring buffer這樣簡單的無鎖的數(shù)據(jù)結(jié)構(gòu)維護(hù)起來還相對(duì)簡單,但是其它場景下就很容易出問題了。

Ctrie是我寫的一篇無鎖的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn),這里有詳細(xì)介紹盡管理論上很容易理解,但事實(shí)上實(shí)現(xiàn)起來非常復(fù)雜。復(fù)雜實(shí)現(xiàn)最主要的原因就是缺乏雙重CAS,它可以幫助我們自動(dòng)比較節(jié)點(diǎn)(去檢測tree上的節(jié)點(diǎn)突變),也可以幫助我們生成節(jié)點(diǎn)快照。因?yàn)闆]有硬件提供這樣的操作,需要我們自己去模擬。

第一個(gè)版本的Ctrie實(shí)現(xiàn)是非常失敗的,不是因?yàn)槲义e(cuò)誤的使用了Go的同步機(jī)制,而是因?yàn)閷?duì)Go語言做了錯(cuò)誤的假設(shè)。Ctrie中的每個(gè)節(jié)點(diǎn)都有一個(gè)和它相關(guān)聯(lián)的同伴節(jié)點(diǎn),當(dāng)進(jìn)行快照時(shí),root節(jié)點(diǎn)都會(huì)被拷貝到一個(gè)新的節(jié)點(diǎn),當(dāng)樹中的節(jié)點(diǎn)被訪問時(shí),也會(huì)被惰性拷貝到新的節(jié)點(diǎn)(持久化數(shù)據(jù)結(jié)構(gòu)),這樣的快照操作是常數(shù)耗時(shí)的。為了避免整數(shù)溢出,我們使用了在堆上分配對(duì)象來區(qū)分新老節(jié)點(diǎn)。在Go語言中,我們使用了空的struct。在Java中,兩個(gè)新生成的空object是不同的,因?yàn)樗鼈兊膬?nèi)存地址不同,所以我假定了Go的規(guī)則也是一樣的。但是,結(jié)果是殘酷的,可以參考下面的文檔

A struct or array type has size zero if it contains no fields (or elements, respectively) that have a size greater than zero. Two distinct zero-size variables may have the same address in memory.

所以悲劇的事情發(fā)生了,兩個(gè)新生成的節(jié)點(diǎn)在比較的時(shí)候是相等的,所以雙重CAS總是成功的。這個(gè)BUG很有趣,但是在高并發(fā)、無鎖環(huán)境下跟蹤這個(gè)bug簡直就是地獄。如果在使用這些方法的時(shí)候,第一次就沒有正確的使用,那么后面會(huì)需要大量的時(shí)間去解決隱藏的問題,而且也不是你第一次做對(duì)了,后面就一直是對(duì)的。

但是顯而易見,編寫復(fù)雜的無鎖算法是有意義的,否則為什么還會(huì)有人這么做呢?Ctrie跟同步map或者跳躍表比起來,插入操作更耗時(shí)一些,因?yàn)閷ぶ凡僮髯兌嗔恕trie真正的優(yōu)勢是內(nèi)存消耗,跟大多的Hash表不同,它總是一系列在tree中的keys。另一個(gè)性能優(yōu)勢就是它可以在常量時(shí)間內(nèi)完成線性快照。我們對(duì)比了在100個(gè)并發(fā)的條件下對(duì)synchronized map 和Ctrie進(jìn)行快照:

BenchmarkConcurrentSnapshotMap-8 1000 9941784 ns/op
BenchmarkConcurrentSnapshotCtrie-8 20000 90412 ns/op

在特定的訪問模式下,無鎖數(shù)據(jù)結(jié)構(gòu)可以在多線程系統(tǒng)中提供更好的性能。例如,NATS消息隊(duì)列使用基于synchronized map的數(shù)據(jù)結(jié)構(gòu)來完成訂閱匹配。如果使用無鎖的Ctrie,吞吐量會(huì)提升很多。下圖中的耗時(shí)中,藍(lán)線表示使用基于鎖的數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn),紅線表示無鎖的數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn)

在特定的場景中避免使用鎖可以帶來很好的性能提升。從ring buffer和channel的對(duì)比中就可以看出無鎖結(jié)構(gòu)的明顯優(yōu)勢。然而,我們需要在編碼的復(fù)雜程度以及獲得的好處之間進(jìn)行權(quán)衡。事實(shí)上,有時(shí)候無鎖結(jié)構(gòu)并不能提供任何實(shí)質(zhì)的好處。

優(yōu)化的注意事項(xiàng)

正如我們從上面的討論所看到的,性能優(yōu)化總是有成本的。認(rèn)識(shí)和理解優(yōu)化方法僅僅是第一步。更重要的是理解應(yīng)該在何時(shí)何處取使用它們。引用 C. A. R. Hoare的一句名言,它已經(jīng)成為了適用所有編程人員的經(jīng)典格言:

The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming.

但這句話的觀點(diǎn)不是反對(duì)優(yōu)化,而是讓我們學(xué)會(huì)在速度之間進(jìn)行權(quán)衡——算法的速度、響應(yīng)速度、維護(hù)速度以及系統(tǒng)速度。這是一個(gè)很主觀的話題,而且沒有一個(gè)簡單的標(biāo)準(zhǔn)。過早進(jìn)行優(yōu)化是錯(cuò)誤的根源嗎?我是不是應(yīng)該先實(shí)現(xiàn)功能,然后再優(yōu)化?或者是不是根本就不需要優(yōu)化?沒有標(biāo)準(zhǔn)答案。有時(shí)候先實(shí)現(xiàn)功能再提升速度也是可以的。

不過,我的建議是只對(duì)關(guān)鍵的路徑進(jìn)行優(yōu)化。你在關(guān)鍵路徑上走的越遠(yuǎn),你優(yōu)化的回報(bào)就會(huì)越低以至于近乎是在浪費(fèi)時(shí)間。能夠?qū)π阅苁欠襁_(dá)標(biāo)做出正確的判斷是很重要的。不要在此之外浪費(fèi)時(shí)間。要用數(shù)據(jù)驅(qū)動(dòng)——用經(jīng)驗(yàn)說話,而不是出于一時(shí)興起。還有就是要注重實(shí)際。給一段時(shí)間不是很敏感的代碼優(yōu)化掉幾十納秒是沒有意義的。比起這個(gè)還有更多需要優(yōu)化的地方。

總結(jié)

如果你已經(jīng)讀到了這里,恭喜你,但你可能還有一些問題搞錯(cuò)了。我們已經(jīng)了解到在軟件中我們實(shí)際上有兩種速度——響應(yīng)速度和執(zhí)行速度。用戶想要的是第一種,而開發(fā)者追求的是第二種,CTO則兩者都要。目前第一種速度是最重要的,只要你想讓用戶用你的產(chǎn)品。第二種速度則是需要你排期和迭代來實(shí)現(xiàn)的。它們經(jīng)常彼此沖突。

也許更耐人尋味的是,我們討論了一些可以讓Go提升一些性能并讓它在低延時(shí)系統(tǒng)中更可用的方法。Go語言是為簡潔而生的,但是這種簡潔有時(shí)候是有代價(jià)的。就跟前面兩種速度的權(quán)衡一樣,在代碼的可維護(hù)性以及代碼性能上也需要權(quán)衡。速度往往意味著犧牲代碼的簡潔性、更多的開發(fā)時(shí)間和后期維護(hù)成本。要明智的做出選擇。

如果您喜歡這篇文章,請(qǐng)點(diǎn)擊喜歡;如果想及時(shí)獲得最新的咨詢,請(qǐng)點(diǎn)擊關(guān)注。您的支持是對(duì)作者都是最大的激勵(lì),萬分感激!By 孫飛

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容