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中切片copy复制和等号复制的区别介绍
Apr 27 Golang
golang 接口嵌套实现复用的操作
Apr 29 Golang
如何解决goland,idea全局搜索快捷键失效问题
Apr 03 Golang
Golang数据类型和相互转换
Apr 12 Golang
Golang流模式之grpc的四种数据流
Apr 13 Golang
golang连接MySQl使用sqlx库
Apr 14 Golang
Go获取两个时区的时间差
Apr 20 Golang
Golang bufio详细讲解
Apr 21 Golang
Go语言测试库testify使用学习
Jul 23 Golang
Go语言编译原理之变量捕获
Aug 05 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
【动漫杂谈】关于《请在T台上微笑》
2020/03/03 日漫
php 删除记录同时删除图片文件的实现代码
2010/05/12 PHP
PHP中使用Imagick操作PSD文件实例
2015/01/26 PHP
CI框架扩展系统核心类的方法分析
2016/05/23 PHP
php处理多图上传压缩代码功能
2018/06/13 PHP
php简单计算权重的方法示例【适合抽奖类应用】
2019/06/10 PHP
javascript parseInt与Number函数的区别
2010/01/21 Javascript
Js切换功能的简单方法
2010/11/23 Javascript
javascript中不提供sleep功能如何实现这个功能
2014/05/27 Javascript
JS中attr和prop属性的区别以及优先选择示例介绍
2014/06/30 Javascript
JavaScript通过元素的ID和name设置样式
2014/07/08 Javascript
在JavaScript中使用开平方根的sqrt()方法
2015/06/15 Javascript
JS遍历数组及打印数组实例分析
2016/01/21 Javascript
node.js cookie-parser之parser.js
2016/06/06 Javascript
jQuery命名空间与闭包用法示例
2017/01/12 Javascript
js实现百度搜索提示框
2017/02/05 Javascript
jQuery实现腾讯信用界面(自制刻度尺)样式
2017/08/15 jQuery
vue中$set的使用(结合在实际应用中遇到的坑)
2018/07/10 Javascript
使用JavaScript解析URL的方法示例
2019/03/01 Javascript
ES6 Set结构的应用实例分析
2019/06/26 Javascript
Linux下为不同版本python安装第三方库
2016/08/31 Python
Python中optparser库用法实例详解
2018/01/26 Python
matplotlib subplots 设置总图的标题方法
2018/05/25 Python
python读取有密码的zip压缩文件实例
2019/02/08 Python
Django框架使用内置方法实现登录功能详解
2019/06/12 Python
python打包exe开机自动启动的实例(windows)
2019/06/28 Python
Python 70行代码实现简单算式计算器解析
2019/08/30 Python
Python3如何判断三角形的类型
2020/04/12 Python
Python如何爬取b站热门视频并导入Excel
2020/08/10 Python
HTML5 canvas基本绘图之绘制五角星
2016/06/27 HTML / CSS
经典优秀个人求职自荐信格式
2013/09/25 职场文书
预备党员党校学习自我评价分享
2013/11/12 职场文书
工厂车间标语
2014/06/19 职场文书
创建绿色社区汇报材料
2014/08/22 职场文书
自书遗嘱范文
2015/08/07 职场文书
2016感恩母亲节校园广播稿
2015/12/17 职场文书