GO 反射對(duì)性能的影響分析
寫在前面
今天在公司寫了一段代碼,判斷一個(gè)變量是否為空值,由于判斷的類型太少,code review的時(shí)候同事說(shuō)還有很多類型沒(méi)有考慮到,并且提到有沒(méi)有開(kāi)源的包做這個(gè)事,于是找了一段assert.IsEmpty里面的代碼。但是這段代碼用到了反射。在code review的時(shí)候同事又提到了反射影響性能。
基于此,這里對(duì)比了一下兩種方式實(shí)習(xí)IsEmpty的性能問(wèn)題。廢話不多說(shuō),上代碼。
代碼
最開(kāi)始的代碼
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
}
}
由于目前場(chǎng)景里面只需要判斷這幾種數(shù)據(jù)類型,因此只實(shí)現(xiàn)了幾種。這種做法明細(xì)很不好,將來(lái)如果有別人用怎么辦?這是一種偷懶的做法,不是一個(gè)高級(jí)程序員應(yīng)該有的素質(zhì)。
在同事提出可能會(huì)有其他數(shù)據(jù)類型的時(shí)候,想著類型太多,窮舉容易漏,于是在網(wǎng)上找了一下開(kāi)源的包,遺憾沒(méi)有找到。但是想到了assert.Empty函數(shù),于是看了一下源代碼,找到了它的實(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())
}
}
于是把這段代碼復(fù)制過(guò)來(lái),code review的時(shí)候同事說(shuō)反射會(huì)影響性能(其實(shí)我覺(jué)得還好,不知道大家覺(jué)得它對(duì)性能影響有多大,歡迎留言討論),于是我又改了一版。結(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")
}
}
}
得到第三版。
性能分析
代碼提交之后我一直在想第一版和第二版性能到底差別有多大。其實(shí)這個(gè)時(shí)候我偷懶了,沒(méi)有做性能分析,做個(gè)Bench分析一下很簡(jiǎn)單,這件事一直在我心里,過(guò)了一天終于寫了一段代碼比對(duì)一下性能。
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)
}
}
運(yùn)行
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:每次運(yùn)行耗費(fèi)的世界。時(shí)間復(fù)雜的相差10倍
- B:每次運(yùn)行分配的字節(jié)數(shù)。可見(jiàn)非反射版不需要額外的內(nèi)存
- allocs:每次運(yùn)行分配內(nèi)存次數(shù)。
綜上可見(jiàn),性能差別挺大的。如果只看運(yùn)行時(shí)間,相差了10倍。確實(shí)反射影響性能,以后還是少用為好,最好不要在循環(huán)里面用。
最終解決辦法
最終結(jié)合第一版和第二版,寫出了第三版。
寫在后面
要善于利用工具,不會(huì)的就去學(xué)習(xí),不能偷懶。
今天看到「字節(jié)跳動(dòng)技術(shù)團(tuán)隊(duì)」一個(gè)直播,幾位掘金小冊(cè)大佬在分享自己是如何寫文章、如何學(xué)習(xí)的,感受挺深。
寫文章要:
- 1.列提綱
- 2.利用碎片化的時(shí)間積累、記錄
- 3.利用周末大片的時(shí)間思考文章
- 4.文章寫完自己對(duì)知識(shí)的認(rèn)知又提升了一個(gè)層次,利人利己
寫文章不要:
- 1.太在意工具。有的人寫文章之前在想用什么打草稿、列提綱、寫思維導(dǎo)圖,在你想這個(gè)的時(shí)候內(nèi)心其實(shí)已經(jīng)在打退堂鼓了。你應(yīng)該直接打開(kāi)一個(gè)文本編輯器或者控制臺(tái)等任何能寫文字的地方,甚至微信都行
- 2.不要在意有多少人會(huì)閱讀你的文章。寫完了你自己也會(huì)有新的認(rèn)識(shí)
- 3.定時(shí)清理收藏夾。不要只放到收藏夾里,里面的東西要定時(shí)整理、清理
以上就是GO 反射對(duì)性能的影響分析的詳細(xì)內(nèi)容,更多關(guān)于GO 反射性能分析的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
一文帶你熟悉Go語(yǔ)言中的分支結(jié)構(gòu)
這篇文章主要和大家分享一下Go語(yǔ)言中的分支結(jié)構(gòu)(if?-?else-if?-?else、switch),文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Go語(yǔ)言有一定的幫助,需要的可以參考一下2022-11-11
Golang中的信號(hào)(Signal)機(jī)制詳解
Signal 是一種操作系統(tǒng)級(jí)別的事件通知機(jī)制,進(jìn)程可以響應(yīng)特定的系統(tǒng)信號(hào),這些信號(hào)用于指示進(jìn)程執(zhí)行特定的操作,如程序終止、掛起、恢復(fù)等,Golang 的標(biāo)準(zhǔn)庫(kù) os/signal 提供了對(duì)信號(hào)處理的支持,本文將詳細(xì)講解 Golang 是如何處理和響應(yīng)系統(tǒng)信號(hào)的,需要的朋友可以參考下2024-01-01
深入探究Golang中flag標(biāo)準(zhǔn)庫(kù)的使用
在本文中,我們將深入探討 flag 標(biāo)準(zhǔn)庫(kù)的實(shí)現(xiàn)原理和使用技巧,以幫助讀者更好地理解和掌握該庫(kù)的使用方法,文中的示例代碼講解詳細(xì),感興趣的可以了解一下2023-04-04
Golang Mutex實(shí)現(xiàn)互斥的具體方法
Mutex是Golang常見(jiàn)的并發(fā)原語(yǔ),在開(kāi)發(fā)過(guò)程中經(jīng)常使用到,本文主要介紹了Golang Mutex實(shí)現(xiàn)互斥的具體方法,具有一定的參考價(jià)值,感興趣的可以了解一下2023-04-04

