范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

1。14版本defer性能大幅度提升,内部实现了开放编码优化

  你的留言,是我坚持分享的动力。如果你有任何疑问,可以点击关注,点赞、留言提问。
  GO中的defer会在当前函数返回前执行传入的函数,常用于关闭文件描述符,关闭链接及解锁等操作。
  Go语言中使用defer时会遇到两个常见问题:defer的调用时机和执行顺序defer调用函数使用传值的方法会执行函数,导致不符预期的结果
  接下来我们来详细处理这两个问题。一、defer
  官方有段对defer的解释:
  每次defer语句执行时,会把函数"压栈",函数的参数会被拷贝下来,当外层函数退出时,defer函数按照定义的逆序执行,如果defer执行的函数为nil,那么会在最终调用函数产生panic。
  这里我们先来一道经典的面试题func main() { 	a,b := 1,2 	defer cal("1",a,cal("10",a,b)) 	a = 0 	defer cal("2",a,cal("20",a,b)) }  func cal(index string, a, b int) int { 	ret := a + b 	fmt.Println(index,a,b,ret) 	return ret }
  你觉得这个会打印什么?
  输出结果:10 1 2 3 20 0 2 2 2 0 2 2 1 1 3 4
  这里是遵循先入后出的原则,同时保留当前变量的值。
  把这道题简化一下:func main() { 	var i = 1 	defer fmt.Println(i)  	i = 4 }
  输出结果1
  上述代码输出似乎不符合预期,这个现象出现的原因是什么呢?经过分析,我们发现调用defer关键字会立即拷贝函数中引用的外部参数,所以fmt.Println(i)的这个i是在调用defer的时候就已经赋值了,所以会直接打印1。
  想要解决这个问题也很简单,只需要向defer关键字传入匿名函数func main() { 	var i = 1 	defer func() { 		fmt.Println(i) 	}()  	i = 4 }二、数据结构// 占用48字节的内存,link把所有的_defer串成一个链表。 // _defer 只是一个header,结构紧跟的是延迟函数的参数和返回值的空间,大小由 siz 决定 type _defer struct { 	// 参数和返回值的内存大小 	siz     int32  	started bool 	// 区分该结构是在栈上分配还是在堆上分配 	heap bool 	openDefer bool 	// sp 计数器值,栈指针 	sp uintptr // sp at time of defer 	// pc 计数器值,程序计数器 	pc uintptr // pc at time of defer 	// defer 传入的函数地址,也就是延后执行的函数 	fn     *funcval // can be nil for open-coded defers     // 触发延迟调用的结构体 	_panic *_panic       // 下一个要被执行的延迟函数 	link   *_defer   }
  这里把一些垃圾回收使用的字段忽略了。2.1、执行机制
  中间代码生成阶段cmd/compile/internal/gc/ssa.go会处理程序中的defer,该函数会根据条件不同,使用三种机制来处理该关键字func (s *state) stmt(n *Node) { 	... 	switch n.Op { 	case ODEFER: 		if Debug_defer > 0 { 			var defertype string 			if s.hasOpenDefers { 				defertype = "open-coded" 			} else if n.Esc == EscNever { 				defertype = "stack-allocated" 			} else { 				defertype = "heap-allocated" 			} 			Warnl(n.Pos, "%s defer", defertype) 		} 		if s.hasOpenDefers {              // 开放编码 			s.openDeferRecord(n.Left)  		} else {              // 堆分配 			d := callDefer 			if n.Esc == EscNever {                  // 栈分配 				d = callDeferStack 			} 			s.call(n.Left, d) 		} 	} }
  开放编码、堆分配和栈分配是defer关键字的三种方法,而Go1.14加入的开放编码,使得关键字开销可以忽略不计。2.2、堆分配
  call方法会为所有函数和方法调用生成中间代码,工作内容:获取需要执行的函数名、闭包指针、代码指针和函数调用的接收方获取栈地址并将函数或方法写入栈中调用newValue1A及相关函数生成函数调用的中间代码如果当前函数的调用函数是defer,那么会单独生成相关的结束代码块获取函数的返回值并结束当前调用func (s *state) call(n *Node, k callKind) *ssa.Value { 	var call *ssa.Value 	if k == callDeferStack {         // 在栈上初始化defer结构体         ...     } else {         ...         switch {         case k == callDefer: 			call = s.newValue1A(ssa.OpStaticCall, types.TypeMem, deferproc, s.mem())             ...         }         call.AuxInt = stksize     }     s.vars[&memVar] = call }
  defer关键字在运行时会调用deferproc,这个函数实现在src/runtime/panic.go里,接受两个参数:参数的大小和闭包所在的地址。
  编译器不仅将defer关键字转成deferproc函数,还会通过以下三种方式为所有调用defer的函数末尾插入deferreturn的函数调用
  1、在cmd/compile/internal/gc/walk.go的walkstmt函数中,在遇到ODEFFER节点时会执行Curfn.Func.SetHasDefer(true),设置当前函数的hasdefer属性
  2、在ssa.go的buildssa会执行s.hasdefer = fn.Func.HasDefer()更新hasdefer
  3、在exit中会根据hasdefer在函数返回前插入deferreturn的函数调用func (s *state) exit() *ssa.Block { 	if s.hasdefer { 		if s.hasOpenDefers { 			... 		} else { 			s.rtcall(Deferreturn, true, nil) 		} 	}     ... }2.2.1、创建延迟调用
  runtime.deferproc为defer创建了一个runtime._defer结构体、设置它的函数指针fn、程序计数器pc和栈指针sp并将相关参数拷贝到相邻的内存空间中func deferproc(siz int32, fn *funcval) { // arguments of fn follow fn 	gp := getg() 	if gp.m.curg != gp { 		// go code on the system stack can"t defer 		throw("defer on system stack") 	}  	// 获取caller的sp寄存器和pc寄存器值 	sp := getcallersp() 	argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn) 	callerpc := getcallerpc()  	// 分配 _defer 结构 	d := newdefer(siz) 	if d._panic != nil { 		throw("deferproc: d.panic != nil after newdefer") 	} 	// _defer 结构初始化 	d.link = gp._defer 	gp._defer = d 	d.fn = fn 	d.pc = callerpc 	d.sp = sp 	switch siz { 	case 0: 		// Do nothing. 	case sys.PtrSize: 		*(*uintptr)(deferArgs(d)) = *(*uintptr)(unsafe.Pointer(argp)) 	default: 		memmove(deferArgs(d), unsafe.Pointer(argp), uintptr(siz)) 	}  	return0() }
  最后调用的return0是唯一一个不会触发延迟调用的函数,可以避免deferreturn的递归调用。
  newdefer的分配方式是从pool缓存池中获取:从调度器的deferpool中取出结构体并将该结构体加入到当前goroutine的缓存池中从goroutine的deferpool中取出结构体通过mallocgc从堆上创建一个新的结构体func newdefer(siz int32) *_defer { 	var d *_defer 	sc := deferclass(uintptr(siz)) 	gp := getg() // 获取当前的goroutine 	// 从pool缓存池中分配,如果没有则mallocgc从堆上分配内存 	if sc < uintptr(len(p{}.deferpool)) { 		pp := gp.m.p.ptr() 		if len(pp.deferpool[sc]) == 0 && sched.deferpool[sc] != nil { 			for len(pp.deferpool[sc]) < cap(pp.deferpool[sc])/2 && sched.deferpool[sc] != nil { 					d := sched.deferpool[sc] 					sched.deferpool[sc] = d.link 					d.link = nil 					pp.deferpool[sc] = append(pp.deferpool[sc], d) 				} 		} 		if n := len(pp.deferpool[sc]); n > 0 { 			d = pp.deferpool[sc][n-1] 			pp.deferpool[sc][n-1] = nil 			pp.deferpool[sc] = pp.deferpool[sc][:n-1] 		} 	} 	if d == nil { 		total := roundupsize(totaldefersize(uintptr(siz))) 			d = (*_defer)(mallocgc(total, deferType, true)) 	} 	d.siz = siz 	d.heap = true 	return d }
  这三种方式取到的结构体_defer,都会被添加到链表的队头,这也是为什么defer按照后进先出的顺序执行。2.2.2、执行延迟调用func deferreturn(arg0 uintptr) {     // 获取当前的goroutine 	gp := getg()        // 拷贝延迟函数到变量d上 	d := gp._defer  	// 如果延迟函数不存在则返回 	if d == nil { 		return 	} 	// 获取caller函数的sp寄存器 	sp := getcallersp() 	if d.sp != sp {          // 说明这个_defer 不是在该caller函数注册的 		return 	}  	switch d.siz { 	case 0: 		// Do nothing. 	case sys.PtrSize: 		*(*uintptr)(unsafe.Pointer(&arg0)) = *(*uintptr)(deferArgs(d)) 	default: 		memmove(unsafe.Pointer(&arg0), deferArgs(d), uintptr(d.siz)) 	}     // 获取要调用的函数 	fn := d.fn              // 重置函数 	d.fn = nil        // 把下一个_defer结构体依附到goroutine上 	gp._defer = d.link      // 释放_defer(主要是堆回收,因为栈上的随函数执行完会自动回收) 	freedefer(d)        	_ = fn.fn     // 调用该函数 	jmpdefer(fn, uintptr(unsafe.Pointer(&arg0)))  }
  deferreturn就是从链表的队头取出并调用jmpdefer传入需要执行的函数和参数。
  该函数只有在所有延迟函数都执行后才会返回。2.3、栈分配
  如果我们能够将部分结构体分配到栈上就可以节约内存分配带来的额外开销。
  在call函数中有在栈上分配func (s *state) call(n *Node, k callKind) *ssa.Value { 	var call *ssa.Value 	if k == callDeferStack {         t := deferstruct(stksize)         ...         call = s.newValue1A(ssa.OpStaticCall, types.TypeMem, deferprocStack, s.mem()) 		if stksize < int64(Widthptr) { 			stksize = int64(Widthptr) 		} 		call.AuxInt = stksize     }     ... }
  在运行期间deferprocStack只需要设置一些未在编译期间初始化的字段,就可以将栈上的_defer追加到函数的链表上。func deferprocStack(d *_defer) { 	gp := getg() 	if gp.m.curg != gp { 		// go code on the system stack can"t defer 		throw("defer on system stack") 	} 	// siz 和 fn在进入到这个函数之前已经赋值 	d.started = false 	// 标明是栈的内存 	d.heap = false 	d.openDefer = false 	// 获取到caller函数的sp寄存器值和pc寄存器 	d.sp = getcallersp() 	d.pc = getcallerpc() 	d.framepc = 0 	d.varp = 0 	*(*uintptr)(unsafe.Pointer(&d._panic)) = 0 	*(*uintptr)(unsafe.Pointer(&d.fd)) = 0 	*(*uintptr)(unsafe.Pointer(&d.link)) = uintptr(unsafe.Pointer(gp._defer)) 	*(*uintptr)(unsafe.Pointer(&gp._defer)) = uintptr(unsafe.Pointer(d))  	return0() }
  除了分配的位置和堆的不同,其他的大致相同。2.4、开放编码
  Go语言在1.14中通过开放编码实现defer关键字,使用代码内联优化defer关键的额外开销并引入函数数据funcdata管理panic的调用,该优化可以将 defer 的调用开销从 1.13 版本的 ~35ns 降低至 ~6ns 左右。
  然而开放编码作为一种优化 defer 关键字的方法,它不是在所有的场景下都会开启的,开放编码只会在满足以下的条件时启用:函数的 defer 数量少于或者等于 8 个;函数的 defer 关键字不能在循环中执行;函数的 return 语句与 defer 语句的乘积小于或者等于 15 个;2.4.1、启动优化const maxOpenDefers = 8  func walkstmt(n *Node) *Node {     ...     switch n.Op {     case ODEFER: 		Curfn.Func.SetHasDefer(true) 		Curfn.Func.numDefers++ 		if Curfn.Func.numDefers > maxOpenDefers { 			Curfn.Func.SetOpenCodedDeferDisallowed(true) 		} 		if n.Esc != EscNever { 			Curfn.Func.SetOpenCodedDeferDisallowed(true) 		} 		fallthrough     } }
  如果函数中defer关键字的数量多于8个或者defer处于循环中,那么就会禁用开放编码优化。func buildssa(fn *Node, worker int) *ssa.Func {     ... 	s.hasOpenDefers = Debug["N"] == 0 && s.hasdefer && !s.curfn.Func.OpenCodedDeferDisallowed()     if s.hasOpenDefers && 		s.curfn.Func.numReturns*s.curfn.Func.numDefers > 15 { 		s.hasOpenDefers = false 	}     ... }
  可以看到这里,判断编译参数不用-N,返回语句的数量和defer数量的乘积小于15,会启用开放编码优化。2.4.2、延迟记录
  延迟比特deferBitsTemp和延迟记录是使用开放编码实现defer的两个最重要的结构,一旦使用开放编码,buildssa会在栈上初始化大小为8个比特的deferBitsfunc buildssa(fn *Node, worker int) *ssa.Func { 	if s.hasOpenDefers { 	deferBitsTemp := tempAt(src.NoXPos, s.curfn, types.Types[TUINT8]) 	s.deferBitsTemp = deferBitsTemp 	startDeferBits := s.entryNewValue0(ssa.OpConst8, types.Types[TUINT8]) 	s.vars[&deferBitsVar] = startDeferBits 	s.deferBitsAddr = s.addr(deferBitsTemp, false) 	s.store(types.Types[TUINT8], s.deferBitsAddr, startDeferBits) 	s.vars[&memVar] = s.newValue1Apos(ssa.OpVarLive, types.TypeMem, deferBitsTemp, s.mem(), false) 	} }
  延迟比特中的每一个比特位都表示该位对应的defer关键字是否需要被执行。延迟比特的作用就是标记哪些defer关键字在函数中被执行,这样就能在函数返回时根据对应的deferBits确定要执行的函数。
  而deferBits的大小为8比特,所以该优化的条件就是defer的数量小于8.
  而执行延迟调用的时候仍在deferreturnfunc deferreturn(arg0 uintptr) {     if d.openDefer { 		done := runOpenDeferFrame(gp, d) 		if !done { 			throw("unfinished open-coded defers in deferreturn") 		} 		gp._defer = d.link 		freedefer(d) 		return 	} }
  这里做了特殊的优化,在runOpenDeferFrame执行开放编码延迟函数
  1、从结构体_defer读取deferBits,执行函数等信息
  2、在循环中依次读取执行函数的地址和参数信息,并通过deferBits判断是否要执行
  3、调用reflectcallSave执行函数func runOpenDeferFrame(gp *g, d *_defer) bool { 	done := true 	fd := d.fd 	deferBitsOffset, fd := readvarintUnsafe(fd) 	nDefers, fd := readvarintUnsafe(fd) 	deferBits := *(*uint8)(unsafe.Pointer(d.varp - uintptr(deferBitsOffset)))  	for i := int(nDefers) - 1; i >= 0; i-- { 		// 读取函数funcdata地址和参数信息 		var argWidth, closureOffset, nArgs uint32 		argWidth, fd = readvarintUnsafe(fd) 		closureOffset, fd = readvarintUnsafe(fd) 		nArgs, fd = readvarintUnsafe(fd) 		if deferBits&(1<
蜂窝板iPad值得买吗?贵有贵的道理,看你是否真的需要苹果有句广告词说的好,你的下一台电脑何必是电脑呢?现在看来,iPad取代电脑还是需要很长一段时间。但是这不妨碍iPad,更加深入的走入人们的生活。在人们的工作和学习中扮演更加必不可BenEnglish52虚拟人只是悬浮的人体躯干,没有腿BenEnglish52。hrThemetaverseishere,anditsnotonlytransforminghowweseetheworldbuthowwepartici向日葵开机插座C1Pro体验小产品大作为,远程开机就是这么简单不知道大家有没有碰到过人在外面,又临时有事需要用到家里电脑上文件的经历?这个时候你是会选择请假回家还是会选择让家里人给你处理下?不过不管你用上面那种方法,感觉都挺费事的。今天楼主就发布新款iPhoneSE和iPadAir苹果选定3月8日?据彭博社的MarkGurman报道,苹果计划在3月份的活动上发布升级版的iPhoneSE,和新的iPadAir。最新消息显示,据说苹果计划在美国时间3月8日(星期二)举行春季发布会实用至上用心做产品,用脚做宣传,低调的散热王者本期是IT品牌故事的第二期,仅介绍品牌,非产品评测推荐。1。一台电脑,怎么样才算有品位曾经有人问我,一台电脑,怎么样才算有品位。我说,装一个有品位的散热器。大家可能会疑惑,这么小的华为P60Pro完美归来,5G回归,鸿蒙OS加持,屏幕影像再提升当下期待值最高的手机厂商,当属华为手机,非常完美的国民品牌。纵观整个国产手机市场,能够抗衡三星及苹果的也仅有华为。在手机领域,华为是当之无愧的王者,如果没有技术限制,早已成为全球最聊一聊iPhone快充是否伤电池经常有网友聊起这个快充是否伤电池的话题,今天就简单聊一下。先说结论快充相对与祖传5V1A充电器来说是会降低电池的寿命,因为快充的充电电流大,电池温度高快充会降低电池寿命的幅度到底多一架乌克兰无人机在白俄罗斯境内坠毁内置索尼A5100微单IT之家2月5日消息,据微博航空视频大VArmstrong大空军之翼消息,日前一架乌克兰无人机在白俄罗斯境内坠毁,搭载了一部索尼微单作为图像传感器。据索尼相机爆料网站SonyAlp家访SUBDEATH的数播静电耳机系统这套系统上对比用ROON和内存播放软件ExtremeDirect播放本地音乐,我觉得声音差异是明显的,而且毫无疑问我会选择后者即便ROON的操控界面明显有优势。并不是说在TAIKO华为升级鸿蒙系统后,只需打开两个开关,垃圾文件会自动识别清理今天和大家分享一下,只要我们打开华为手机里面隐藏的这一个功能,它就有一个自动清理垃圾文件,这里呢会自动识别垃圾文件,在14天后呢还会永久的删除。但是这个功能呢隐藏的比较深,很多朋友别再听信谣言!老iPhone系统根本没养老版本,可放心升级其实不知从什么时候开始,网络上逐渐有人开始说,旧的iPhone不要更新系统。原因无非是会变卡,续航会变短,信号会变差,会失去当前版本的所谓的优势等等等。每当看到这种消息时,都想立马
洗碗机洗碗和人工工洗碗,哪个更省心?1。水电成本洗碗机洗碗和人工洗碗差不多。2。人工费像大型餐饮,星级酒店等洗碗工都得好几个,而且还不好招工,如果用洗碗机,用人数量减少一半,人工成本会节省很多。3。餐具损耗人工洗碗时好物推荐南卡A2ANC降噪耳机百元性价比之选本次产品为厂家送测,感谢南卡的大力支持!提到南卡,我们首先想到的应该是南卡家的骨传导蓝牙耳机,不过这次,南卡也是推出了新产品南卡A2,这是南卡首款主动降噪的产品,而能上我的好物推荐好物推荐漫步者W820NB头戴式耳机没有人比我更懂降噪漫步者这个品牌,近些年可以说是蓝牙耳机领域的一匹黑马了,但是却很少有人知道漫步者已经做了20多年的声学领域了,甚至还是一个上市公司,而他们是做音箱起家的,之后我们有机会也可以去介绍南卡T2蓝牙耳机评测开箱评测南卡T2蓝牙耳机双动力好音质南卡今年出了很多新品,有不用耳朵听音乐的骨传导CC,有圆滑的半入耳式LitePro,有带主动降噪ANC的A1,可以说每一款耳机都有着自己的特点和开箱评测SanagA7S气传导耳机新技术新方向本产品为厂家送测,感谢Sanag的大力支持!Sanag虽然是英国品牌,但如今在国内市场也是频频发力,甚至请了花样滑冰世界冠军于小雨做形象大使,可见其品牌对国内市场的重视程度,而一向自动洗碗机常见功能及优势随着生活条件的提高,洗碗机逐渐被家庭餐饮行业接受和使用,越来越多的家用洗碗机和商用用洗碗机都进入了成千上万的家庭后厨,解决用餐后洗碗的问题。商用洗碗机,通常是商业目的,用于餐厅,食洗碗机带烘干功能的优势对于一些需要洗很多餐具的洗碗间来说,要操作方便,节省成本,而且还确保餐具卫生安全,还有一个关键的配置不能少,这是干燥系统!聪明的用户可以在购买洗碗机选择干式的功能,或洗碗机烘干系统如何看待全红婵对游戏防沉迷认证感到为难,称每天只能打1小时?尊重少年人天性,别让鲁迅的后悔重演。风筝北京的冬季,地上还有积雪,灰黑色的秃树枝丫叉于晴朗的天空中,而远处有一二风筝浮动,在我是一种惊异和悲哀。故乡的风筝时节,是春二月,倘听到沙沙自动洗碗机的功能及优点随着生活条件的改善,洗碗机逐渐被家庭和餐饮业所接受和使用。越来越多的家用洗碗机和商用洗碗机进入千家万户和厨房,解决饭后洗碗的问题。商用洗碗机,通常用于商业用途,用于餐馆食堂宾馆等餐商用自动洗碗机的功能介绍洗碗机是一种自动清洗碗筷盘子碟子刀叉等餐具的装置。市场上的全自动洗碗机可分为家用和商用两种。家用全自动洗碗机只适合家庭使用,主要有柜式台式水槽一体式和一体式。商用洗碗机按结构可分为比亚迪汉DM到店实拍,推荐购买四驱性能版豪华型比亚迪今年的重磅车型应该要说汉系列。车型有两种动力总成,分别是插电混动(汉DM)和纯电版(汉EV)。今天我们实拍是刚刚到店的汉DM车型,实拍为四驱性能版豪华型21。98万元,另外还