go select编译期的优化处理逻辑使用场景分析


Posted in Golang onJune 28, 2021

前言

select作为Go chan通信的重要监听工具,有着很广泛的使用场景。select的使用主要是搭配通信case使用,表面上看,只是简单的selectcase搭配,实际上根据case的数量及类型,在编译时select会进行优化处理,根据不同的情况调用不同的底层逻辑。

select的编译处理

select编译时的核心处理逻辑如下:

func walkselectcases(cases *Nodes) []*Node {
	ncas := cases.Len()
	sellineno := lineno

	// optimization: zero-case select
	// 针对没有case的select优化
	if ncas == 0 {
		return []*Node{mkcall("block", nil, nil)}
	}

	// optimization: one-case select: single op.
	// 针对1个case(单个操作)select的优化
	if ncas == 1 {
		cas := cases.First()
		setlineno(cas)
		l := cas.Ninit.Slice()
		if cas.Left != nil { // not default: 非default case
			n := cas.Left // 获取case表达式
			l = append(l, n.Ninit.Slice()...)
			n.Ninit.Set(nil)
			switch n.Op {
			default:
				Fatalf("select %v", n.Op)

			case OSEND: // Left <- Right
				// already ok
				// n中已包含left/right
			
			case OSELRECV, OSELRECV2: // OSELRECV(Left = <-Right.Left) OSELRECV2(List = <-Right.Left)
				if n.Op == OSELRECV || n.List.Len() == 0 { // 左侧有0或1个接收者
					if n.Left == nil { // 没有接收者
						n = n.Right // 只需保留右侧
					} else { // 
						n.Op = OAS // 只有一个接收者,更新Op为OAS
					}
					break
				}

				if n.Left == nil { // 检查是否表达式或赋值
					nblank = typecheck(nblank, ctxExpr|ctxAssign)
					n.Left = nblank
				}

				n.Op = OAS2 // OSELRECV2多个接收者
				n.List.Prepend(n.Left) // 将left放在前面
				n.Rlist.Set1(n.Right) 
				n.Right = nil
				n.Left = nil
				n.SetTypecheck(0)
				n = typecheck(n, ctxStmt)
			}

			l = append(l, n)
		}

		l = append(l, cas.Nbody.Slice()...) // case内的处理
		l = append(l, nod(OBREAK, nil, nil)) // 添加break
		return l
	}

	// convert case value arguments to addresses.
	// this rewrite is used by both the general code and the next optimization.
	var dflt *Node
	for _, cas := range cases.Slice() {
		setlineno(cas)
		n := cas.Left
		if n == nil {
			dflt = cas
			continue
		}
		switch n.Op {
		case OSEND:
			n.Right = nod(OADDR, n.Right, nil)
			n.Right = typecheck(n.Right, ctxExpr)

		case OSELRECV, OSELRECV2:
			if n.Op == OSELRECV2 && n.List.Len() == 0 {
				n.Op = OSELRECV
			}

			if n.Left != nil {
				n.Left = nod(OADDR, n.Left, nil)
				n.Left = typecheck(n.Left, ctxExpr)
			}
		}
	}

	// optimization: two-case select but one is default: single non-blocking op.
	if ncas == 2 && dflt != nil {
		cas := cases.First()
		if cas == dflt {
			cas = cases.Second()
		}

		n := cas.Left
		setlineno(n)
		r := nod(OIF, nil, nil)
		r.Ninit.Set(cas.Ninit.Slice())
		switch n.Op {
		default:
			Fatalf("select %v", n.Op)

		case OSEND:
			// if selectnbsend(c, v) { body } else { default body }
			ch := n.Left
			r.Left = mkcall1(chanfn("selectnbsend", 2, ch.Type), types.Types[TBOOL], &r.Ninit, ch, n.Right)

		case OSELRECV:
			// if selectnbrecv(&v, c) { body } else { default body }
			ch := n.Right.Left
			elem := n.Left
			if elem == nil {
				elem = nodnil()
			}
			r.Left = mkcall1(chanfn("selectnbrecv", 2, ch.Type), types.Types[TBOOL], &r.Ninit, elem, ch)

		case OSELRECV2:
			// if selectnbrecv2(&v, &received, c) { body } else { default body }
			ch := n.Right.Left
			elem := n.Left
			if elem == nil {
				elem = nodnil()
			}
			receivedp := nod(OADDR, n.List.First(), nil)
			receivedp = typecheck(receivedp, ctxExpr)
			r.Left = mkcall1(chanfn("selectnbrecv2", 2, ch.Type), types.Types[TBOOL], &r.Ninit, elem, receivedp, ch)
		}

		r.Left = typecheck(r.Left, ctxExpr)
		r.Nbody.Set(cas.Nbody.Slice())
		r.Rlist.Set(append(dflt.Ninit.Slice(), dflt.Nbody.Slice()...))
		return []*Node{r, nod(OBREAK, nil, nil)}
	}

	if dflt != nil {
		ncas--
	}
	casorder := make([]*Node, ncas)
	nsends, nrecvs := 0, 0

	var init []*Node

	// generate sel-struct
	lineno = sellineno
	selv := temp(types.NewArray(scasetype(), int64(ncas)))
	r := nod(OAS, selv, nil)
	r = typecheck(r, ctxStmt)
	init = append(init, r)

	// No initialization for order; runtime.selectgo is responsible for that.
	order := temp(types.NewArray(types.Types[TUINT16], 2*int64(ncas)))

	var pc0, pcs *Node
	if flag_race {
		pcs = temp(types.NewArray(types.Types[TUINTPTR], int64(ncas)))
		pc0 = typecheck(nod(OADDR, nod(OINDEX, pcs, nodintconst(0)), nil), ctxExpr)
	} else {
		pc0 = nodnil()
	}

	// register cases
	for _, cas := range cases.Slice() {
		setlineno(cas)

		init = append(init, cas.Ninit.Slice()...)
		cas.Ninit.Set(nil)

		n := cas.Left
		if n == nil { // default:
			continue
		}

		var i int
		var c, elem *Node
		switch n.Op {
		default:
			Fatalf("select %v", n.Op)
		case OSEND:
			i = nsends
			nsends++
			c = n.Left
			elem = n.Right
		case OSELRECV, OSELRECV2:
			nrecvs++
			i = ncas - nrecvs
			c = n.Right.Left
			elem = n.Left
		}

		casorder[i] = cas

		setField := func(f string, val *Node) {
			r := nod(OAS, nodSym(ODOT, nod(OINDEX, selv, nodintconst(int64(i))), lookup(f)), val)
			r = typecheck(r, ctxStmt)
			init = append(init, r)
		}

		c = convnop(c, types.Types[TUNSAFEPTR])
		setField("c", c)
		if elem != nil {
			elem = convnop(elem, types.Types[TUNSAFEPTR])
			setField("elem", elem)
		}

		// TODO(mdempsky): There should be a cleaner way to
		// handle this.
		if flag_race {
			r = mkcall("selectsetpc", nil, nil, nod(OADDR, nod(OINDEX, pcs, nodintconst(int64(i))), nil))
			init = append(init, r)
		}
	}
	if nsends+nrecvs != ncas {
		Fatalf("walkselectcases: miscount: %v + %v != %v", nsends, nrecvs, ncas)
	}

	// run the select
	lineno = sellineno
	chosen := temp(types.Types[TINT])
	recvOK := temp(types.Types[TBOOL])
	r = nod(OAS2, nil, nil)
	r.List.Set2(chosen, recvOK)
	fn := syslook("selectgo")
	r.Rlist.Set1(mkcall1(fn, fn.Type.Results(), nil, bytePtrToIndex(selv, 0), bytePtrToIndex(order, 0), pc0, nodintconst(int64(nsends)), nodintconst(int64(nrecvs)), nodbool(dflt == nil)))
	r = typecheck(r, ctxStmt)
	init = append(init, r)

	// selv and order are no longer alive after selectgo.
	init = append(init, nod(OVARKILL, selv, nil))
	init = append(init, nod(OVARKILL, order, nil))
	if flag_race {
		init = append(init, nod(OVARKILL, pcs, nil))
	}

	// dispatch cases
	dispatch := func(cond, cas *Node) {
		cond = typecheck(cond, ctxExpr)
		cond = defaultlit(cond, nil)

		r := nod(OIF, cond, nil)

		if n := cas.Left; n != nil && n.Op == OSELRECV2 {
			x := nod(OAS, n.List.First(), recvOK)
			x = typecheck(x, ctxStmt)
			r.Nbody.Append(x)
		}

		r.Nbody.AppendNodes(&cas.Nbody)
		r.Nbody.Append(nod(OBREAK, nil, nil))
		init = append(init, r)
	}

	if dflt != nil {
		setlineno(dflt)
		dispatch(nod(OLT, chosen, nodintconst(0)), dflt)
	}
	for i, cas := range casorder {
		setlineno(cas)
		dispatch(nod(OEQ, chosen, nodintconst(int64(i))), cas)
	}

	return init
}

select编译时会根据case的数量进行优化:

1.没有case
直接调用block

2.1个case
(1)default case,直接执行body
(2) send/recv case (block为true),按照单独执行的结果确认,可能会发生block
(3) send调用对应的chansend1
(4) recv调用对应的chanrecv1/chanrecv2

3.2个case且包含一个default case
(1) send/recv case (block为false),按照单独执行的结果确认case是否ok,!ok则执行default case,不会发生block
(2) send调用对应的selectnbsend
(3) recv调用对应的selectnbrecv/selectnbrecv2

4.一般的case
selectgo

总结

最后,以一张图进行简单总结。

go select编译期的优化处理逻辑使用场景分析

以上就是go select编译期的优化处理逻辑使用场景分析的详细内容,更多关于go select编译的资料请关注三水点靠木其它相关文章!

Golang 相关文章推荐
golang如何去除多余空白字符(含制表符)
Apr 25 Golang
golang 实现菜单树的生成方式
Apr 28 Golang
解决golang post文件时Content-Type出现的问题
May 02 Golang
Golang Gob编码(gob包的使用详解)
May 07 Golang
基于Golang 高并发问题的解决方案
May 08 Golang
go xorm框架的使用
May 22 Golang
Go语言设计模式之结构型模式
Jun 22 Golang
手把手教你导入Go语言第三方库
Aug 04 Golang
如何利用golang运用mysql数据库
Mar 13 Golang
Golang 对es的操作实例
Apr 20 Golang
Golang 实现 WebSockets 之创建 WebSockets
Apr 24 Golang
Golang并发工具Singleflight
May 06 Golang
Go 语言下基于Redis分布式锁的实现方式
Jun 28 #Golang
go语言使用Casbin实现角色的权限控制
Go语言设计模式之结构型模式
浅谈Go语言多态的实现与interface使用
Jun 16 #Golang
再次探讨go实现无限 buffer 的 channel方法
Jun 13 #Golang
Go遍历struct,map,slice的实现
Jun 13 #Golang
go web 预防跨站脚本的实现方式
Jun 11 #Golang
You might like
Laravel如何自定义command命令浅析
2019/03/23 PHP
PHP ob缓存以及ob函数原理实例解析
2020/11/13 PHP
基于jquery的一个简单的脚本验证插件
2010/04/05 Javascript
js中将具有数字属性名的对象转换为数组
2011/03/06 Javascript
JS获取页面input控件中所有text控件并追加样式属性
2013/02/25 Javascript
XMLHttpRequest处理xml格式的返回数据(示例代码)
2013/11/21 Javascript
js图片闪动特效可以控制间隔时间如几分钟闪动一下
2014/08/12 Javascript
JavaScript判断是否为数组的3种方法及效率比较
2015/04/01 Javascript
在Node.js应用中使用Redis的方法简介
2015/06/24 Javascript
js数组如何添加json数据及js数组与json的区别
2015/10/27 Javascript
jQuery实现带玻璃流光质感的手风琴特效
2015/11/20 Javascript
JavaScript中校验银行卡号的实现代码
2016/12/19 Javascript
js 单引号替换成双引号,双引号替换成单引号的实现方法
2017/02/16 Javascript
详解使用jQuery.i18n.properties实现js国际化
2018/05/04 jQuery
关于vue编译版本引入的问题的解决
2018/09/17 Javascript
如何在基于vue-cli的项目自定义打包环境
2018/11/10 Javascript
基于Vue 撸一个指令实现拖拽功能
2019/10/09 Javascript
Python 命令行参数sys.argv
2008/09/06 Python
python实现单线程多任务非阻塞TCP服务端
2017/06/13 Python
利用python打开摄像头及颜色检测方法
2018/08/03 Python
Python对CSV、Excel、txt、dat文件的处理
2018/09/18 Python
python实现五子棋小游戏
2020/03/25 Python
使用Python做垃圾分类的原理及实例代码附源码
2019/07/02 Python
Python中logging日志库实例详解
2020/02/19 Python
python实现快递价格查询系统
2020/03/03 Python
html5+css如何实现中间大两头小的轮播效果
2018/12/06 HTML / CSS
QA工程师岗位职责
2013/11/20 职场文书
老师自我鉴定范文
2013/12/25 职场文书
学校采购员岗位职责
2014/01/02 职场文书
单位提档介绍信
2014/01/17 职场文书
产假请假条
2014/04/10 职场文书
大学生通用个人自我评价
2014/04/27 职场文书
领导干部廉政自律承诺书
2014/05/26 职场文书
建筑节能汇报材料
2014/08/22 职场文书
高一军训感想
2015/08/07 职场文书
iPhone13再次曝光
2021/04/15 数码科技