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
goland 恢复已更改文件的操作
Apr 28 Golang
go结构体嵌套的切片数组操作
Apr 28 Golang
基于Go Int转string几种方式性能测试
Apr 28 Golang
解决Golang中goroutine执行速度的问题
May 02 Golang
golang 比较浮点数的大小方式
May 02 Golang
解决golang 关于全局变量的坑
May 06 Golang
Golang中channel的原理解读(推荐)
Oct 16 Golang
golang操作rocketmq的示例代码
Apr 06 Golang
Go语言 详解net的tcp服务
Apr 14 Golang
golang生成并解析JSON
Apr 14 Golang
go goth封装第三方认证库示例详解
Aug 14 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+DBM的同学录程序(2)
2006/10/09 PHP
php 传值赋值与引用赋值的区别
2010/12/29 PHP
php 获取今日、昨日、上周、本月的起始时间戳和结束时间戳的方法
2013/09/28 PHP
PHP二维数组排序的3种方法和自定义函数分享
2014/04/09 PHP
PHP模板引擎smarty详细介绍
2015/05/26 PHP
PHP+Ajax异步带进度条上传文件实例
2016/11/01 PHP
制作高质量的JQuery Plugin 插件的方法
2010/04/20 Javascript
jquery 插件实现多行文本框[textarea]自动高度
2015/03/04 Javascript
JS中Location使用详解
2015/05/12 Javascript
jquery easyUI中ajax异步校验用户名
2016/08/19 Javascript
JS排序之快速排序详解
2017/04/08 Javascript
Nodejs实现多房间简易聊天室功能
2017/06/20 NodeJs
vue+express+jwt持久化登录的方法
2019/06/14 Javascript
JS多个异步请求 按顺序执行next实现解析
2019/09/16 Javascript
Javascript实现鼠标点击冒泡特效
2019/12/24 Javascript
详解微信小程序(Taro)手动埋点和自动埋点的实现
2021/03/02 Javascript
python文件和目录操作方法大全(含实例)
2014/03/12 Python
详解Python 2.6 升级至 Python 2.7 的实践心得
2017/04/27 Python
Python网络编程使用select实现socket全双工异步通信功能示例
2018/04/09 Python
python实现windows壁纸定期更换功能
2019/01/21 Python
opencv3/C++ 平面对象识别&amp;透视变换方式
2019/12/11 Python
Tensorflow读取并输出已保存模型的权重数值方式
2020/01/04 Python
Python3标准库之functools管理函数的工具详解
2020/02/27 Python
基于python3.7利用Motor来异步读写Mongodb提高效率(推荐)
2020/04/29 Python
详解Python高阶函数
2020/08/15 Python
Python之字典添加元素的几种方法
2020/09/30 Python
联想加拿大官方网站:Lenovo Canada
2018/04/05 全球购物
有影响力的人、名人和艺术家的官方商品:Represent
2019/11/26 全球购物
自主招生自荐信
2013/12/08 职场文书
高中运动会广播稿
2014/01/21 职场文书
工厂保安员岗位职责
2014/01/31 职场文书
自动化毕业生专业自荐书范文
2014/02/04 职场文书
企业管理毕业生求职信范文
2014/03/07 职场文书
普通党员群众路线教育实践活动心得体会
2014/11/04 职场文书
《葡萄沟》教学反思
2016/02/23 职场文书
图解排序算法之希尔排序Java实现
2021/06/26 Java/Android