Go をカンストさせる話
Maxing out Go
こんにちは
select してますか ?
select
go func(x *Daemon) {
defer func() {
x.WaitGroup.Done()
log.Printf("Daemon %s stopped", x.Name)
}()
loop:
for {
select {
case cmd := <-x.CmdChan:
log.Printf("Daemon %s received commandn")
go doCommand(
x,
withDeadline(
x.Ctx,
time.Now().Add(4*time.Second),
),
cmd.Cmd,
cmd.Arg,
)
case <-x.Ctx.Done():
break loop
}
}
}(x)
select の実装はどうなっているのか ?
case x=<-❶ case x->❷ case x=<-❸ case x=<-❹goroutine1
case x=<-❶ case x->❷goroutine2
goroutine 1
goroutine 2
goroutine 1goroutine 1
goroutine 2
goroutine 1
chan ❶ chan ❷ chan ❸ chan ❹
1. chan の wait キューに入れる
2. 起こされるまで待つ
3. どれに起こされたか調べる
4. 該当する case の処理に飛ぶ
大事なことは
src/runtime/select.go に
全部書いてある
src/runtime/runtime.go
// Select statement header.
// Known to compiler.
// Changes here must also be made in src/cmd/internal/gc/select.go's
selecttype.
type hselect struct {
tcase uint16 // total count of scase[]
ncase uint16 // currently filled scase[]
pollorder *uint16 // case poll order
lockorder *uint16 // channel lock order
scase [1]scase // one per case (in order of appearance)
}
// Select case descriptor.
// Known to compiler.
// Changes here must also be made in src/cmd/internal/gc/select.go's
selecttype.
type scase struct {
elem unsafe.Pointer // data element
c *hchan // chan
pc uintptr // return pc (for race detector /
msan)
tcase uint16 // total count of scase[]
uint16
65535
敵を 65535 体倒すとデスペナルテ
所持ギルの上限は 65
ぐれメタルを 1 回の戦闘で複数体倒しても合計値が 65535 でカウンタ
率を 65535% にしたらダメー
35 ターン経過させると全滅
HUMAN BEINGS
CANNOT
HELP
MAXING
IT
OUT
実験
package main
import "fmt"
func main() {
c := [65536]chan int{}
f := fmt.Println
select {
case i := <-c[0]:
f(i)
case i := <-c[1]:
f(i)
case i := <-c[2]:
f(i)
case i := <-c[3]:
f(i)
実験
package main
import "fmt"
func main() {
c := [65536]chan int{}
f := fmt.Println
select {
case i := <-c[0]:
f(i)
case i := <-c[1]:
f(i)
case i := <-c[2]:
f(i)
case i := <-c[3]:
f(i)
case i := <-c[4]:
f(i)
case i := <-c[5]:
f(i)
case i := <-c[6]:
f(i)
case i := <-c[7]:
f(i)
実験
package main
import "fmt"
func main() {
c := [65536]chan int{}
f := fmt.Println
select {
case i := <-c[0]:
f(i)
case i := <-c[1]:
f(i)
case i := <-c[2]:
f(i)
case i := <-c[3]:
f(i)
case i := <-c[4]:
f(i)
case i := <-c[5]:
f(i)
case i := <-c[6]:
f(i)
case i := <-c[7]:
f(i)
case i := <-c[8]:
f(i)
case i := <-c[9]:
f(i)
case i := <-c[10]:
f(i)
case i := <-c[11]:
f(i)
case i := <-c[12]:
f(i)
case i := <-c[13]:
f(i)
case i := <-c[14]:
f(i)
case i := <-c[15]:
f(i)
case i := <-c[16]:
f(i)
case i := <-c[17]:
実験
package main
import "fmt"
func main() {
c := [65536]chan int{}
f := fmt.Println
select {
case i := <-c[0]:
f(i)
case i := <-c[1]:
f(i)
case i := <-c[2]:
f(i)
case i := <-c[3]:
f(i)
case i := <-c[4]:
f(i)
case i := <-c[5]:
f(i)
case i := <-c[6]:
f(i)
case i := <-c[7]:
f(i)
case i := <-c[8]:
f(i)
case i := <-c[9]:
f(i)
case i := <-c[10]:
f(i)
case i := <-c[11]:
f(i)
case i := <-c[12]:
f(i)
case i := <-c[13]:
f(i)
case i := <-c[14]:
f(i)
case i := <-c[15]:
f(i)
case i := <-c[16]:
f(i)
case i := <-c[17]:
f(i)
case i := <-c[18]:
f(i)
case i := <-c[19]:
f(i)
case i := <-c[20]:
f(i)
case i := <-c[21]:
f(i)
case i := <-c[22]:
f(i)
case i := <-c[23]:
f(i)
case i := <-c[24]:
f(i)
case i := <-c[25]:
f(i)
case i := <-c[26]:
f(i)
case i := <-c[27]:
f(i)
case i := <-c[28]:
f(i)
case i := <-c[29]:
f(i)
case i := <-c[30]:
f(i)
case i := <-c[31]:
f(i)
case i := <-c[32]:
f(i)
case i := <-c[33]:
f(i)
case i := <-c[34]:
f(i)
case i := <-c[35]:
f(i)
case i := <-c[36]:
f(i)
case i := <-c[37]:
f(i)
case i := <-c[38]:
f(i)
case i := <-c[39]:
f(i)
case i := <-c[40]:
f(i)
case i := <-c[41]:
f(i)
case i := <-c[42]:
f(i)
case i := <-c[43]:
f(i)
case i := <-c[44]:
f(i)
case i := <-c[45]:
f(i)
case i := <-c[46]:
f(i)
case i := <-c[47]:
f(i)
case i := <-c[48]:
f(i)
case i := <-c[49]:
f(i)
case i := <-c[50]:
f(i)
case i := <-c[51]:
f(i)
case i := <-c[52]:
f(i)
以下 65536 ま
で続く
結果
コンパイルが終わらない
CPU100%
やがてメモリを食いつくして OOM Killer に殺される
OOM Killer のイメージ ( オオアリク
イ )
諦めるのはまだ早い
reflect.Select
reflect.Select
func main() {
cases := [65536]reflect.SelectCase{}
for i, _ := range cases {
cases[i] = reflect.SelectCase{
Dir: reflect.SelectRecv,
Chan: reflect.ValueOf(make(chan int)),
}
}
go func() {
for {
i := rand.Int() % len(cases)
cases[i].Chan.Send(reflect.ValueOf(i))
time.Sleep(time.Second)
}
}()
fmt.Println("ready")
for {
c, v, ok := reflect.Select(cases[:])
fmt.Println(c, v, ok)
}
}
リフレクションで select 相当の処理を書ける
実行結果
ubuntu:gotorture:% go run uint16maxselect.go
ready
fatal error: selectrecv: too many cases
goroutine 1 [running]:
runtime.throw(0x521ce0, 0x1a)
...
too many
cases
Questions?
ないですね
ご清聴ありがとうございました

Goをカンストさせる話

  • 1.
  • 2.
  • 3.
  • 4.
    select go func(x *Daemon){ defer func() { x.WaitGroup.Done() log.Printf("Daemon %s stopped", x.Name) }() loop: for { select { case cmd := <-x.CmdChan: log.Printf("Daemon %s received commandn") go doCommand( x, withDeadline( x.Ctx, time.Now().Add(4*time.Second), ), cmd.Cmd, cmd.Arg, ) case <-x.Ctx.Done(): break loop } } }(x)
  • 5.
    select の実装はどうなっているのか ? casex=<-❶ case x->❷ case x=<-❸ case x=<-❹goroutine1 case x=<-❶ case x->❷goroutine2 goroutine 1 goroutine 2 goroutine 1goroutine 1 goroutine 2 goroutine 1 chan ❶ chan ❷ chan ❸ chan ❹ 1. chan の wait キューに入れる 2. 起こされるまで待つ 3. どれに起こされたか調べる 4. 該当する case の処理に飛ぶ
  • 6.
  • 7.
    src/runtime/runtime.go // Select statementheader. // Known to compiler. // Changes here must also be made in src/cmd/internal/gc/select.go's selecttype. type hselect struct { tcase uint16 // total count of scase[] ncase uint16 // currently filled scase[] pollorder *uint16 // case poll order lockorder *uint16 // channel lock order scase [1]scase // one per case (in order of appearance) } // Select case descriptor. // Known to compiler. // Changes here must also be made in src/cmd/internal/gc/select.go's selecttype. type scase struct { elem unsafe.Pointer // data element c *hchan // chan pc uintptr // return pc (for race detector / msan)
  • 8.
    tcase uint16 //total count of scase[]
  • 9.
  • 10.
  • 11.
    敵を 65535 体倒すとデスペナルテ 所持ギルの上限は65 ぐれメタルを 1 回の戦闘で複数体倒しても合計値が 65535 でカウンタ 率を 65535% にしたらダメー 35 ターン経過させると全滅
  • 12.
  • 13.
    実験 package main import "fmt" funcmain() { c := [65536]chan int{} f := fmt.Println select { case i := <-c[0]: f(i) case i := <-c[1]: f(i) case i := <-c[2]: f(i) case i := <-c[3]: f(i)
  • 14.
    実験 package main import "fmt" funcmain() { c := [65536]chan int{} f := fmt.Println select { case i := <-c[0]: f(i) case i := <-c[1]: f(i) case i := <-c[2]: f(i) case i := <-c[3]: f(i) case i := <-c[4]: f(i) case i := <-c[5]: f(i) case i := <-c[6]: f(i) case i := <-c[7]: f(i)
  • 15.
    実験 package main import "fmt" funcmain() { c := [65536]chan int{} f := fmt.Println select { case i := <-c[0]: f(i) case i := <-c[1]: f(i) case i := <-c[2]: f(i) case i := <-c[3]: f(i) case i := <-c[4]: f(i) case i := <-c[5]: f(i) case i := <-c[6]: f(i) case i := <-c[7]: f(i) case i := <-c[8]: f(i) case i := <-c[9]: f(i) case i := <-c[10]: f(i) case i := <-c[11]: f(i) case i := <-c[12]: f(i) case i := <-c[13]: f(i) case i := <-c[14]: f(i) case i := <-c[15]: f(i) case i := <-c[16]: f(i) case i := <-c[17]:
  • 16.
    実験 package main import "fmt" funcmain() { c := [65536]chan int{} f := fmt.Println select { case i := <-c[0]: f(i) case i := <-c[1]: f(i) case i := <-c[2]: f(i) case i := <-c[3]: f(i) case i := <-c[4]: f(i) case i := <-c[5]: f(i) case i := <-c[6]: f(i) case i := <-c[7]: f(i) case i := <-c[8]: f(i) case i := <-c[9]: f(i) case i := <-c[10]: f(i) case i := <-c[11]: f(i) case i := <-c[12]: f(i) case i := <-c[13]: f(i) case i := <-c[14]: f(i) case i := <-c[15]: f(i) case i := <-c[16]: f(i) case i := <-c[17]: f(i) case i := <-c[18]: f(i) case i := <-c[19]: f(i) case i := <-c[20]: f(i) case i := <-c[21]: f(i) case i := <-c[22]: f(i) case i := <-c[23]: f(i) case i := <-c[24]: f(i) case i := <-c[25]: f(i) case i := <-c[26]: f(i) case i := <-c[27]: f(i) case i := <-c[28]: f(i) case i := <-c[29]: f(i) case i := <-c[30]: f(i) case i := <-c[31]: f(i) case i := <-c[32]: f(i) case i := <-c[33]: f(i) case i := <-c[34]: f(i) case i := <-c[35]: f(i) case i := <-c[36]: f(i) case i := <-c[37]: f(i) case i := <-c[38]: f(i) case i := <-c[39]: f(i) case i := <-c[40]: f(i) case i := <-c[41]: f(i) case i := <-c[42]: f(i) case i := <-c[43]: f(i) case i := <-c[44]: f(i) case i := <-c[45]: f(i) case i := <-c[46]: f(i) case i := <-c[47]: f(i) case i := <-c[48]: f(i) case i := <-c[49]: f(i) case i := <-c[50]: f(i) case i := <-c[51]: f(i) case i := <-c[52]: f(i) 以下 65536 ま で続く
  • 17.
    結果 コンパイルが終わらない CPU100% やがてメモリを食いつくして OOM Killerに殺される OOM Killer のイメージ ( オオアリク イ )
  • 18.
  • 19.
  • 20.
    reflect.Select func main() { cases:= [65536]reflect.SelectCase{} for i, _ := range cases { cases[i] = reflect.SelectCase{ Dir: reflect.SelectRecv, Chan: reflect.ValueOf(make(chan int)), } } go func() { for { i := rand.Int() % len(cases) cases[i].Chan.Send(reflect.ValueOf(i)) time.Sleep(time.Second) } }() fmt.Println("ready") for { c, v, ok := reflect.Select(cases[:]) fmt.Println(c, v, ok) } } リフレクションで select 相当の処理を書ける
  • 21.
    実行結果 ubuntu:gotorture:% go runuint16maxselect.go ready fatal error: selectrecv: too many cases goroutine 1 [running]: runtime.throw(0x521ce0, 0x1a) ...
  • 22.
  • 23.
  • 24.
  • 25.