go select编译期的优化处理逻辑使用场景分析
程序员文章站
2022-03-05 21:29:37
前言select作为go chan通信的重要监听工具,有着很广泛的使用场景。select的使用主要是搭配通信case使用,表面上看,只是简单的select及case搭配,实际上根据case的数量及类型...
前言
select
作为go
chan
通信的重要监听工具,有着很广泛的使用场景。select
的使用主要是搭配通信case使用,表面上看,只是简单的select
及case
搭配,实际上根据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.一般的caseselectgo
总结
最后,以一张图进行简单总结。
以上就是go select编译期的优化处理逻辑使用场景分析的详细内容,更多关于go select编译的资料请关注其它相关文章!
上一篇: 一看到她就紧张
下一篇: 真我GTneo对比真我V15哪个值得买?