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

作者:yuchenfw 时间:2024-05-25 15:16:43 

前言

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

来源:https://blog.csdn.net/xz_studying/article/details/118280886

标签:go,select,编译
0
投稿

猜你喜欢

  • Python高级特性 切片 迭代解析

    2022-03-28 14:32:49
  • Tensorflow: 从checkpoint文件中读取tensor方式

    2022-07-20 11:39:29
  • 用Python Turtle画棵樱花树送给自己

    2022-06-30 10:16:47
  • MySQL批量更新的四种方式总结

    2024-01-13 17:53:08
  • CTF中的PHP特性函数解析之中篇

    2023-06-11 12:56:20
  • Mysql json类型字段Java+Mybatis数据字典功能的实践方式

    2024-01-22 00:24:34
  • Python Flask框架实现简单加法工具过程解析

    2021-08-17 20:36:11
  • ORACLE11g随RHEL5系统自动启动与关闭的设置方法

    2009-08-31 12:43:00
  • 《写给大家看的设计书》阅读笔记之对比原则

    2009-07-15 10:14:00
  • vue百度地图 + 定位的详解

    2024-06-07 15:20:37
  • python模拟登录并且保持cookie的方法详解

    2023-09-06 06:07:16
  • python一行输入n个数据问题

    2023-09-11 21:50:48
  • 5招带你轻松优化MySQL count(*)查询性能

    2024-01-28 04:40:58
  • 改进评论提交表单

    2009-03-25 20:37:00
  • ASP常见的保留字整理(变量与表名注意不能用)

    2013-06-01 19:58:01
  • python中%格式表达式实例用法

    2022-02-05 06:59:02
  • 数据库分页大全(mssql,mysql,oracle)

    2010-10-25 20:02:00
  • Pandas对每个分组应用apply函数的实现

    2022-01-26 04:02:00
  • 一文教会你用Python读取PDF文件

    2021-03-23 00:24:55
  • python实现windows壁纸定期更换功能

    2022-09-06 15:05:11
  • asp之家 网络编程 m.aspxhome.com