欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-06-25 12:26:30
前言select作为go chan通信的重要监听工具,有着很广泛的使用场景。select的使用主要是搭配通信case使用,表面上看,只是简单的select及case搭配,实际上根据case的数量及类型...

前言

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编译的资料请关注其它相关文章!

相关标签: go select 编译