GO 反射对性能的影响分析
作者:nil 发布时间:2024-02-18 06:51:41
写在前面
今天在公司写了一段代码,判断一个变量是否为空值,由于判断的类型太少,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
}
}
由于目前场景里面只需要判断这几种数据类型,因此只实现了几种。这种做法明细很不好,将来如果有别人用怎么办?这是一种偷懒的做法,不是一个高级程序员应该有的素质。
在同事提出可能会有其他数据类型的时候,想着类型太多,穷举容易漏,于是在网上找了一下开源的包,遗憾没有找到。但是想到了assert.Empty函数,于是看了一下源代码,找到了它的实现方法。
// 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的时候同事说反射会影响性能(其实我觉得还好,不知道大家觉得它对性能影响有多大,欢迎留言讨论),于是我又改了一版。结合了上面两种方法。先判断是不是基础数据类型,如果不是再用反射。
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
结果
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:每次运行分配的字节数。可见非反射版不需要额外的内存
allocs:每次运行分配内存次数。
综上可见,性能差别挺大的。如果只看运行时间,相差了10倍。确实反射影响性能,以后还是少用为好,最好不要在循环里面用。
最终解决办法
最终结合第一版和第二版,写出了第三版。
写在后面
要善于利用工具,不会的就去学习,不能偷懒。
今天看到「字节跳动技术团队」一个直播,几位掘金小册大佬在分享自己是如何写文章、如何学习的,感受挺深。
写文章要:
1.列提纲
2.利用碎片化的时间积累、记录
3.利用周末大片的时间思考文章
4.文章写完自己对知识的认知又提升了一个层次,利人利己
写文章不要:
1.太在意工具。有的人写文章之前在想用什么打草稿、列提纲、写思维导图,在你想这个的时候内心其实已经在打退堂鼓了。你应该直接打开一个文本编辑器或者控制台等任何能写文字的地方,甚至微信都行
2.不要在意有多少人会阅读你的文章。写完了你自己也会有新的认识
3.定时清理收藏夹。不要只放到收藏夹里,里面的东西要定时整理、清理
来源:https://juejin.cn/post/7137679269785763848
猜你喜欢
- 如下所示:x = 3print(x+"nihao")这样会报错x = 3print(x,"nihao"
- 一、场景说明在面试接口自动化时,经常会问,其他接口调用的前提条件是当前用户必须是登录状态,如何处理接口依赖?在此之前我们介绍过session
- 前言go的 init函数给人的感觉怪怪的,我想不明白聪明的 google团队为何要设计出这么一个“鸡肋“的机制。实际编码中,我主张尽量不要使
- 直接看例子:#!/usr/bin/python# -*- coding: utf-8 -*-from bs4 import Beautifu
- 导语哈喽!我是木木子,又到了今日更新时刻!我们来看看写什么呢?小编有个好兄弟最近在追妹子,跟妹子打得火热!就差临门一脚了,这一jio我帮忙补
- 我们通过模拟随机漫步可以说明如何运用数组运算。通过内置的random模块以纯Python的方式实现1000步的随机漫步根据前100个随机漫步
- 安装文件准备:安装文件下载地址python-2.6.2.msihttp://www.python.org/download/wxPython
- Timedelta转换为Int或Float方式Pandas处理import pandas as pddataSet['t']
- 从而使得有些字符(尤其是宽字符)无法正确地显示,即不再是utf-8格式了。解决办法:打开输出文件时即指定编码格式,就不会出现输出文件打开以后
- 如果一些应用需要到中文字体(如果pygraphviz,不安装中文字体,中文会显示乱码),就要在image 中安装中文字体。默认 python
- 前言上项目的时候,遇见一次需求,需要把在线的 其中一个 collection 里面的数据迁移到另外一个collection下,于是就百度了看
- 1.折线图 plt.plot()常用的一些参数:颜色(color):‘c’ 青红(cyan)&
- vue-cli使用stimulsoft.reports.js(保姆级教程)第一部分:数据源准备以下是JSON数据的教程json数据结构{&q
- onactivate
- 使用场景对手机号码进行地域分析,需要查询归属地;问题描述针对数据集比较大的情况,通过脚本来处理,使用多线程的方法来加快查询速度pool =
- SELECT * from table where username like '%陈哈哈%' and hobby like
- 全文索引在 MySQL 中是一个 FULLTEXT 类型索引。FULLTEXT 索引用于 MyISAM 表,可以在 CREATE TABLE
- 如果对自然语言分类,有很多中分法,比如英语、法语、汉语等,这种分法是最常见的。在语言学里面,也有对语言的分类方法,比如什么什么语系之类的。我
- 本文实例讲述了python实现根据月份和日期得到星座的方法。分享给大家供大家参考。具体实现方法如下:#计算星座def Zodiac(mont
- 目录简介使用介绍实际体验小结简介MySQL 作为最流行的开源数据库,在各个领域都有相当广泛的应用,作为一个 MySQL DBA,经常会对数据