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 相关文章推荐
Go语言-为什么返回值为接口类型,却返回结构体
Apr 24 Golang
golang goroutine顺序输出方式
Apr 29 Golang
Golang 如何实现函数的任意类型传参
Apr 29 Golang
解决golang结构体tag编译错误的问题
May 02 Golang
golang switch语句的灵活写法介绍
May 06 Golang
Golang并发操作中常见的读写锁详析
Aug 30 Golang
深入理解go缓存库freecache的使用
Feb 15 Golang
Go并发4种方法简明讲解
Apr 06 Golang
Golang获取List列表元素的四种方式
Apr 20 Golang
Golang 实现 WebSockets 之创建 WebSockets
Apr 24 Golang
详解Go语言中配置文件使用与日志配置
Jun 01 Golang
GO中sync包自由控制并发示例详解
Aug 05 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
PHP实现动态web服务器方法
2015/07/29 PHP
PHP面向对象之里氏替换原则简单示例
2018/04/08 PHP
实现复选框全选/全不选切换
2006/12/23 Javascript
extjs form textfield的隐藏方法
2008/12/29 Javascript
仅Firefox中链接A无法实现模拟点击以触发其默认行为
2011/07/31 Javascript
JavaScript 基础篇之运算符、语句(二)
2012/04/07 Javascript
JS实现图片预加载无需等待
2012/12/21 Javascript
SOSO地图API使用(一)在地图上画圆实现思路与代码
2013/01/15 Javascript
javascript制作坦克大战全纪录(1)
2014/11/27 Javascript
详解jQuery事件
2017/01/13 Javascript
Node.js 使用递归实现遍历文件夹中所有文件
2017/09/18 Javascript
mui框架移动开发初体验详解
2017/10/11 Javascript
详解基于Koa2开发微信二维码扫码支付相关流程
2018/05/16 Javascript
layui table设置前台过滤转义等方法
2018/08/17 Javascript
vue-router beforeEach跳转路由验证用户登录状态
2018/12/26 Javascript
Vue核心概念Getter的使用方法
2019/01/18 Javascript
vue 使用原生组件上传图片的实例
2020/09/08 Javascript
python动态监控日志内容的示例
2014/02/16 Python
Python中使用urllib2防止302跳转的代码例子
2014/07/07 Python
python 数据的清理行为实例详解
2017/07/12 Python
Python实现二维数组输出为图片
2018/04/03 Python
在cmd命令行里进入和退出Python程序的方法
2018/05/12 Python
python实现雪花飘落效果实例讲解
2019/06/18 Python
Python实现二叉树的最小深度的两种方法
2019/09/30 Python
Jupyter Notebook打开任意文件夹操作
2020/04/14 Python
Django Model中字段(field)的各种选项说明
2020/05/19 Python
tensorflow 动态获取 BatchSzie 的大小实例
2020/06/30 Python
西班牙英格列斯百货官网:El Corte Inglés
2016/09/25 全球购物
瑞士设计师家具和家居饰品网上商店:Bruno Wickart
2019/03/18 全球购物
2014年会计人员工作总结
2014/12/10 职场文书
先进个人事迹材料范文
2014/12/30 职场文书
2015毕业设计工作总结
2015/07/24 职场文书
团结主题班会
2015/08/13 职场文书
OpenCV 图像梯度的实现方法
2021/07/25 Python
Python OpenCV超详细讲解读取图像视频和网络摄像头
2022/04/02 Python
MySQL外键约束(Foreign Key)案例详解
2022/06/28 MySQL