经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Go语言 » 查看文章
go select编译期的优化处理逻辑使用场景分析
来源:jb51  时间:2021/6/28 12:41:44  对本文有异议

前言

select作为Go chan通信的重要监听工具,有着很广泛的使用场景。select的使用主要是搭配通信case使用,表面上看,只是简单的selectcase搭配,实际上根据case的数量及类型,在编译时select会进行优化处理,根据不同的情况调用不同的底层逻辑。

select的编译处理

select编译时的核心处理逻辑如下:

  1. func walkselectcases(cases *Nodes) []*Node {
  2. ncas := cases.Len()
  3. sellineno := lineno
  4.  
  5. // optimization: zero-case select
  6. // 针对没有case的select优化
  7. if ncas == 0 {
  8. return []*Node{mkcall("block", nil, nil)}
  9. }
  10.  
  11. // optimization: one-case select: single op.
  12. // 针对1个case(单个操作)select的优化
  13. if ncas == 1 {
  14. cas := cases.First()
  15. setlineno(cas)
  16. l := cas.Ninit.Slice()
  17. if cas.Left != nil { // not default: 非default case
  18. n := cas.Left // 获取case表达式
  19. l = append(l, n.Ninit.Slice()...)
  20. n.Ninit.Set(nil)
  21. switch n.Op {
  22. default:
  23. Fatalf("select %v", n.Op)
  24.  
  25. case OSEND: // Left <- Right
  26. // already ok
  27. // n中已包含left/right
  28. case OSELRECV, OSELRECV2: // OSELRECV(Left = <-Right.Left) OSELRECV2(List = <-Right.Left)
  29. if n.Op == OSELRECV || n.List.Len() == 0 { // 左侧有0或1个接收者
  30. if n.Left == nil { // 没有接收者
  31. n = n.Right // 只需保留右侧
  32. } else { //
  33. n.Op = OAS // 只有一个接收者,更新Op为OAS
  34. }
  35. break
  36. }
  37.  
  38. if n.Left == nil { // 检查是否表达式或赋值
  39. nblank = typecheck(nblank, ctxExpr|ctxAssign)
  40. n.Left = nblank
  41. }
  42.  
  43. n.Op = OAS2 // OSELRECV2多个接收者
  44. n.List.Prepend(n.Left) // 将left放在前面
  45. n.Rlist.Set1(n.Right)
  46. n.Right = nil
  47. n.Left = nil
  48. n.SetTypecheck(0)
  49. n = typecheck(n, ctxStmt)
  50. }
  51.  
  52. l = append(l, n)
  53. }
  54.  
  55. l = append(l, cas.Nbody.Slice()...) // case内的处理
  56. l = append(l, nod(OBREAK, nil, nil)) // 添加break
  57. return l
  58. }
  59.  
  60. // convert case value arguments to addresses.
  61. // this rewrite is used by both the general code and the next optimization.
  62. var dflt *Node
  63. for _, cas := range cases.Slice() {
  64. setlineno(cas)
  65. n := cas.Left
  66. if n == nil {
  67. dflt = cas
  68. continue
  69. }
  70. switch n.Op {
  71. case OSEND:
  72. n.Right = nod(OADDR, n.Right, nil)
  73. n.Right = typecheck(n.Right, ctxExpr)
  74.  
  75. case OSELRECV, OSELRECV2:
  76. if n.Op == OSELRECV2 && n.List.Len() == 0 {
  77. n.Op = OSELRECV
  78. }
  79.  
  80. if n.Left != nil {
  81. n.Left = nod(OADDR, n.Left, nil)
  82. n.Left = typecheck(n.Left, ctxExpr)
  83. }
  84. }
  85. }
  86.  
  87. // optimization: two-case select but one is default: single non-blocking op.
  88. if ncas == 2 && dflt != nil {
  89. cas := cases.First()
  90. if cas == dflt {
  91. cas = cases.Second()
  92. }
  93.  
  94. n := cas.Left
  95. setlineno(n)
  96. r := nod(OIF, nil, nil)
  97. r.Ninit.Set(cas.Ninit.Slice())
  98. switch n.Op {
  99. default:
  100. Fatalf("select %v", n.Op)
  101.  
  102. case OSEND:
  103. // if selectnbsend(c, v) { body } else { default body }
  104. ch := n.Left
  105. r.Left = mkcall1(chanfn("selectnbsend", 2, ch.Type), types.Types[TBOOL], &r.Ninit, ch, n.Right)
  106.  
  107. case OSELRECV:
  108. // if selectnbrecv(&v, c) { body } else { default body }
  109. ch := n.Right.Left
  110. elem := n.Left
  111. if elem == nil {
  112. elem = nodnil()
  113. }
  114. r.Left = mkcall1(chanfn("selectnbrecv", 2, ch.Type), types.Types[TBOOL], &r.Ninit, elem, ch)
  115.  
  116. case OSELRECV2:
  117. // if selectnbrecv2(&v, &received, c) { body } else { default body }
  118. ch := n.Right.Left
  119. elem := n.Left
  120. if elem == nil {
  121. elem = nodnil()
  122. }
  123. receivedp := nod(OADDR, n.List.First(), nil)
  124. receivedp = typecheck(receivedp, ctxExpr)
  125. r.Left = mkcall1(chanfn("selectnbrecv2", 2, ch.Type), types.Types[TBOOL], &r.Ninit, elem, receivedp, ch)
  126. }
  127.  
  128. r.Left = typecheck(r.Left, ctxExpr)
  129. r.Nbody.Set(cas.Nbody.Slice())
  130. r.Rlist.Set(append(dflt.Ninit.Slice(), dflt.Nbody.Slice()...))
  131. return []*Node{r, nod(OBREAK, nil, nil)}
  132. }
  133.  
  134. if dflt != nil {
  135. ncas--
  136. }
  137. casorder := make([]*Node, ncas)
  138. nsends, nrecvs := 0, 0
  139.  
  140. var init []*Node
  141.  
  142. // generate sel-struct
  143. lineno = sellineno
  144. selv := temp(types.NewArray(scasetype(), int64(ncas)))
  145. r := nod(OAS, selv, nil)
  146. r = typecheck(r, ctxStmt)
  147. init = append(init, r)
  148.  
  149. // No initialization for order; runtime.selectgo is responsible for that.
  150. order := temp(types.NewArray(types.Types[TUINT16], 2*int64(ncas)))
  151.  
  152. var pc0, pcs *Node
  153. if flag_race {
  154. pcs = temp(types.NewArray(types.Types[TUINTPTR], int64(ncas)))
  155. pc0 = typecheck(nod(OADDR, nod(OINDEX, pcs, nodintconst(0)), nil), ctxExpr)
  156. } else {
  157. pc0 = nodnil()
  158. }
  159.  
  160. // register cases
  161. for _, cas := range cases.Slice() {
  162. setlineno(cas)
  163.  
  164. init = append(init, cas.Ninit.Slice()...)
  165. cas.Ninit.Set(nil)
  166.  
  167. n := cas.Left
  168. if n == nil { // default:
  169. continue
  170. }
  171.  
  172. var i int
  173. var c, elem *Node
  174. switch n.Op {
  175. default:
  176. Fatalf("select %v", n.Op)
  177. case OSEND:
  178. i = nsends
  179. nsends++
  180. c = n.Left
  181. elem = n.Right
  182. case OSELRECV, OSELRECV2:
  183. nrecvs++
  184. i = ncas - nrecvs
  185. c = n.Right.Left
  186. elem = n.Left
  187. }
  188.  
  189. casorder[i] = cas
  190.  
  191. setField := func(f string, val *Node) {
  192. r := nod(OAS, nodSym(ODOT, nod(OINDEX, selv, nodintconst(int64(i))), lookup(f)), val)
  193. r = typecheck(r, ctxStmt)
  194. init = append(init, r)
  195. }
  196.  
  197. c = convnop(c, types.Types[TUNSAFEPTR])
  198. setField("c", c)
  199. if elem != nil {
  200. elem = convnop(elem, types.Types[TUNSAFEPTR])
  201. setField("elem", elem)
  202. }
  203.  
  204. // TODO(mdempsky): There should be a cleaner way to
  205. // handle this.
  206. if flag_race {
  207. r = mkcall("selectsetpc", nil, nil, nod(OADDR, nod(OINDEX, pcs, nodintconst(int64(i))), nil))
  208. init = append(init, r)
  209. }
  210. }
  211. if nsends+nrecvs != ncas {
  212. Fatalf("walkselectcases: miscount: %v + %v != %v", nsends, nrecvs, ncas)
  213. }
  214.  
  215. // run the select
  216. lineno = sellineno
  217. chosen := temp(types.Types[TINT])
  218. recvOK := temp(types.Types[TBOOL])
  219. r = nod(OAS2, nil, nil)
  220. r.List.Set2(chosen, recvOK)
  221. fn := syslook("selectgo")
  222. 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)))
  223. r = typecheck(r, ctxStmt)
  224. init = append(init, r)
  225.  
  226. // selv and order are no longer alive after selectgo.
  227. init = append(init, nod(OVARKILL, selv, nil))
  228. init = append(init, nod(OVARKILL, order, nil))
  229. if flag_race {
  230. init = append(init, nod(OVARKILL, pcs, nil))
  231. }
  232.  
  233. // dispatch cases
  234. dispatch := func(cond, cas *Node) {
  235. cond = typecheck(cond, ctxExpr)
  236. cond = defaultlit(cond, nil)
  237.  
  238. r := nod(OIF, cond, nil)
  239.  
  240. if n := cas.Left; n != nil && n.Op == OSELRECV2 {
  241. x := nod(OAS, n.List.First(), recvOK)
  242. x = typecheck(x, ctxStmt)
  243. r.Nbody.Append(x)
  244. }
  245.  
  246. r.Nbody.AppendNodes(&cas.Nbody)
  247. r.Nbody.Append(nod(OBREAK, nil, nil))
  248. init = append(init, r)
  249. }
  250.  
  251. if dflt != nil {
  252. setlineno(dflt)
  253. dispatch(nod(OLT, chosen, nodintconst(0)), dflt)
  254. }
  255. for i, cas := range casorder {
  256. setlineno(cas)
  257. dispatch(nod(OEQ, chosen, nodintconst(int64(i))), cas)
  258. }
  259.  
  260. return init
  261. }

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

总结

最后,以一张图进行简单总结。

select编译优化

以上就是go select编译期的优化处理逻辑使用场景分析的详细内容,更多关于go select编译的资料请关注w3xue其它相关文章!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号