GO 反射對性能的影響分析
寫在前面
今天在公司寫了一段代碼,判斷一個變量是否為空值,由于判斷的類型太少,code review的時候同事說還有很多類型沒有考慮到,并且提到有沒有開源的包做這個事,于是找了一段assert.IsEmpty里面的代碼。但是這段代碼用到了反射。在code review的時候同事又提到了反射影響性能。
基于此,這里對比了一下兩種方式實習IsEmpty的性能問題。廢話不多說,上代碼。
代碼
最開始的代碼
func IsEmpty(val interface{}) bool { if val == nil { return true } switch v := val.(type) { case int: return v == int(0) case int8: return v == int8(0) case int16: return v == int16(0) case int32: return v == int32(0) case int64: return v == int64(0) case string: return v == "" default: return false } }
由于目前場景里面只需要判斷這幾種數(shù)據(jù)類型,因此只實現(xiàn)了幾種。這種做法明細很不好,將來如果有別人用怎么辦?這是一種偷懶的做法,不是一個高級程序員應該有的素質(zhì)。
在同事提出可能會有其他數(shù)據(jù)類型的時候,想著類型太多,窮舉容易漏,于是在網(wǎng)上找了一下開源的包,遺憾沒有找到。但是想到了assert.Empty函數(shù),于是看了一下源代碼,找到了它的實現(xiàn)方法。
// isEmpty gets whether the specified object is considered empty or not. func isEmpty(object interface{}) bool { // get nil case out of the way if object == nil { return true } objValue := reflect.ValueOf(object) switch objValue.Kind() { // collection types are empty when they have no element case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice: return objValue.Len() == 0 // pointers are empty if nil or if the value they point to is empty case reflect.Ptr: if objValue.IsNil() { return true } deref := objValue.Elem().Interface() return isEmpty(deref) // for all other types, compare against the zero value default: zero := reflect.Zero(objValue.Type()) return reflect.DeepEqual(object, zero.Interface()) } }
于是把這段代碼復制過來,code review的時候同事說反射會影響性能(其實我覺得還好,不知道大家覺得它對性能影響有多大,歡迎留言討論),于是我又改了一版。結(jié)合了上面兩種方法。先判斷是不是基礎(chǔ)數(shù)據(jù)類型,如果不是再用反射。
const ( IntZero = int(0) Int8Zero = int8(0) Int16Zero = int16(0) Int32Zero = int32(0) Int64Zero = int64(0) UintZero = uint(0) Uint8Zero = uint8(0) Uint16Zero = uint16(0) Uint32Zero = uint32(0) Uint64Zero = uint64(0) Float32Zero = float32(0) Float64Zero = float64(0) StringZero = "" ) func IsEmpty(data interface{}) bool { if data == nil { return false } switch v := data.(type) { case bool: return false case *bool: return v == nil case int: return v == IntZero case *int: return v == nil || *v == IntZero case int8: return v == Int8Zero case *int8: return v == nil || *v == Int8Zero case int16: return v == Int16Zero case *int16: return v == nil || *v == Int16Zero case int32: return v == Int32Zero case *int32: return v == nil || *v == Int32Zero case int64: return v == Int64Zero case *int64: return v == nil || *v == Int64Zero case uint: return v == UintZero case *uint: return v == nil || *v == UintZero case uint8: return v == Uint8Zero case *uint8: return v == nil || *v == Uint8Zero case uint16: return v == Uint16Zero case *uint16: return v == nil || *v == Uint16Zero case uint32: return v == Uint32Zero case *uint32: return v == nil || *v == Uint32Zero case uint64: return v == Uint64Zero case *uint64: return v == nil || *v == Uint64Zero case float32: return v == Float32Zero case *float32: return v == nil || *v == Float32Zero case float64: return v == Float64Zero case *float64: return v == nil || *v == Float64Zero case string: return v == StringZero case *string: return v == nil || *v == StringZero default: kind := reflect.TypeOf(data).Kind() if kind == reflect.Ptr { dataKind := reflect.ValueOf(data).Elem().Kind() if dataKind == reflect.Invalid || dataKind == reflect.Struct { return reflect.ValueOf(data).IsNil() } else { return false } } else if kind == reflect.Slice || kind == reflect.Map { // slice return reflect.ValueOf(data).Len() == 0 } else if kind == reflect.Struct { // struct return false } else { panic("not support type. you can support by yourself") } } }
得到第三版。
性能分析
代碼提交之后我一直在想第一版和第二版性能到底差別有多大。其實這個時候我偷懶了,沒有做性能分析,做個Bench分析一下很簡單,這件事一直在我心里,過了一天終于寫了一段代碼比對一下性能。
func BenchmarkTestIsEmpty(t *testing.B) { v1 := false v2 := true var v3 *bool v4 := int(0) var v5 *int v6 := int64(0) var v7 *int64 v8 := "" var v9 *string v10 := "test" v11 := float64(0.00) v12 := float64(0.01) testCases := []TestCase{ {input: v1, want: false}, {input: &v1, want: false}, {input: v2, want: false}, {input: &v2, want: false}, {input: v3, want: true}, {input: v4, want: true}, {input: &v4, want: true}, {input: v5, want: true}, {input: int(1), want: false}, {input: v6, want: true}, {input: &v6, want: true}, {input: v7, want: true}, {input: int64(1), want: false}, {input: v8, want: true}, {input: &v8, want: true}, {input: v9, want: true}, {input: v10, want: false}, {input: &v10, want: false}, {input: v11, want: true}, {input: &v11, want: true}, {input: v12, want: false}, {input: &v12, want: false}, } for i, testCase := range testCases { result := IsEmpty(testCases[i].input) assert.Equal(t, testCase.want, result) } } func BenchmarkTestIsEmptyV1(t *testing.B) { v1 := false v2 := true var v3 *bool v4 := int(0) var v5 *int v6 := int64(0) var v7 *int64 v8 := "" var v9 *string v10 := "test" v11 := float64(0.00) v12 := float64(0.01) testCases := []TestCase{ {input: v1, want: true}, {input: &v1, want: true}, {input: v2, want: false}, {input: &v2, want: false}, {input: v3, want: true}, {input: v4, want: true}, {input: &v4, want: true}, {input: v5, want: true}, {input: int(1), want: false}, {input: v6, want: true}, {input: &v6, want: true}, {input: v7, want: true}, {input: int64(1), want: false}, {input: v8, want: true}, {input: &v8, want: true}, {input: v9, want: true}, {input: v10, want: false}, {input: &v10, want: false}, {input: v11, want: true}, {input: &v11, want: true}, {input: v12, want: false}, {input: &v12, want: false}, } for i, testCase := range testCases { result := IsEmptyV1(testCases[i].input) assert.Equal(t, testCase.want, result) } }
運行
go test -bench=. -benchmem
結(jié)果
goos: darwin
goarch: amd64
pkg: common/util
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkTestIsEmpty-12 120820 9635 ns/op 0 B/op 0 allocs/op
BenchmarkTestIsEmptyV1-12 103664 11582 ns/op 72 B/op 8 allocs/op
PASS
ok common/util 5.149s
- ns:每次運行耗費的世界。時間復雜的相差10倍
- B:每次運行分配的字節(jié)數(shù)??梢姺欠瓷浒娌恍枰~外的內(nèi)存
- allocs:每次運行分配內(nèi)存次數(shù)。
綜上可見,性能差別挺大的。如果只看運行時間,相差了10倍。確實反射影響性能,以后還是少用為好,最好不要在循環(huán)里面用。
最終解決辦法
最終結(jié)合第一版和第二版,寫出了第三版。
寫在后面
要善于利用工具,不會的就去學習,不能偷懶。
今天看到「字節(jié)跳動技術(shù)團隊」一個直播,幾位掘金小冊大佬在分享自己是如何寫文章、如何學習的,感受挺深。
寫文章要:
- 1.列提綱
- 2.利用碎片化的時間積累、記錄
- 3.利用周末大片的時間思考文章
- 4.文章寫完自己對知識的認知又提升了一個層次,利人利己
寫文章不要:
- 1.太在意工具。有的人寫文章之前在想用什么打草稿、列提綱、寫思維導圖,在你想這個的時候內(nèi)心其實已經(jīng)在打退堂鼓了。你應該直接打開一個文本編輯器或者控制臺等任何能寫文字的地方,甚至微信都行
- 2.不要在意有多少人會閱讀你的文章。寫完了你自己也會有新的認識
- 3.定時清理收藏夾。不要只放到收藏夾里,里面的東西要定時整理、清理
以上就是GO 反射對性能的影響分析的詳細內(nèi)容,更多關(guān)于GO 反射性能分析的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Golang運行報錯找不到包:package?xxx?is?not?in?GOROOT的解決過程
這篇文章主要給大家介紹了關(guān)于Golang運行報錯找不到包:package?xxx?is?not?in?GOROOT的解決過程,文中通過圖文介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2022-07-07Golang中基礎(chǔ)的命令行模塊urfave/cli的用法說明
這篇文章主要介紹了Golang中基礎(chǔ)的命令行模塊urfave/cli的用法說明,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12Go?實現(xiàn)?WebSockets之創(chuàng)建?WebSockets
這篇文章主要介紹了Go?實現(xiàn)?WebSockets之創(chuàng)建?WebSockets,文章主要探索?WebSockets,并簡要介紹了它們的工作原理,并仔細研究了全雙工通信,想了解更多相關(guān)內(nèi)容的小伙伴可以參考一下2022-04-04Golang?中的?strconv?包常用函數(shù)及用法詳解
strconv是Golang中一個非常常用的包,主要用于字符串和基本數(shù)據(jù)類型之間的相互轉(zhuǎn)換,這篇文章主要介紹了Golang中的strconv包,需要的朋友可以參考下2023-06-06