Shogo's Blog

たぶんプログラミングとかについて書いていくブログ

Go言語のchanはいったいいくつ付けられるのか試してみた

pecoに入った修正をみて、果たしてchanはいくつまで付けられるのか気になったので、 雑に試してみました。 先に断っておきますが、全く有用ではないですよ。

背景

pecoに入った修正はこちら(一部抜粋)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
diff --git a/interface.go b/interface.go
index 3d4472f..fff446c 100644
--- a/interface.go
+++ b/interface.go
@@ -162,8 +162,8 @@ type Screen interface {
 // Termbox just hands out the processing to the termbox library
 type Termbox struct {
  mutex     sync.Mutex
-    resumeCh  chan (struct{})
-    suspendCh chan (struct{})
+    resumeCh  chan chan struct{}
+    suspendCh chan struct{}
 }

 // View handles the drawing/updating the screen
diff --git a/screen.go b/screen.go
index edbce87..f6dd71e 100644
--- a/screen.go
+++ b/screen.go
@@ -21,7 +21,7 @@ func (t *Termbox) Init() error {
 func NewTermbox() *Termbox {
  return &Termbox{
      suspendCh: make(chan struct{}),
-        resumeCh:  make(chan struct{}),
+        resumeCh:  make(chan chan struct{}),
  }
 }

channelを使ってchannelをやり取りすることができるので、 chan struct{}をやり取りするchan chan struct{}という型が使えます。 同じ要領で、channelをやり取りするchannelをやり取りするchannelをやり取り…するchannelが 無限に作れるはずです(少なくとも構文上は)。 ということで、実際にやってみました。

実験

雑なPerlスクリプトを準備して、大量のchanを付けたGoのコードを自動生成します。

1
2
3
4
5
6
7
8
9
10
11
12
13
print <<EOF;
package main

import (
    "fmt"
)

type Foo @{['chan ' x 4096]} struct{}

func main() {
    fmt.Printf("Hello, %#v\\n", make(Foo))
}
EOF

chanの個数を変えて何度かビルドを繰り返します。

1
time go build -o main main.go

結果

chanの個数とビルドにかかった時間をまとめてみました。

chanの個数ビルド時間
10.236s
20.240s
40.226s
80.234s
160.240s
320.250s
640.281s
1280.258s
2560.360s
5120.775s
10243.228s
204818.605s
40961m53.614s
819213m46.018s(ビルド失敗したので参考記録)

8192個付けたら以下のようなエラーを吐いてビルドが失敗してしまったので、 8192個の時の記録は参考記録です。

1
2
# command-line-arguments
too much data in section SDWARFINFO (over 2000000000 bytes)

何かビルドの設定をいじればもっと行けるかもしれませんが、 デフォルトの設定では4096から8192の間に限界があるようです。 4096個chanを付けたときのソースコードは20KB程度なのにバイナリサイズは524MBまで膨らんでいました。

256個当たりからビルド時間に影響が出ているので、 ビルド時間を考える256個以下に抑えるのがよさそうです。 それ以上だと 程度のオーダーでビルド時間が延びます。 とはいえ、256個もchanを付いたコードを人間が読めるとは思えないので、 2個が限度でしょうね・・・。 3個以上必要になるケースは余りないと思います。

型定義を再帰的にして無限chanを実現する

そもそも、chanを大量に並べなくとも、 型定義を再帰的に行えば無限のchanを付けたときと同等のことができます。 例えば以下のコードで"Goroutine 1"と"Goroutine 2"を交互に表示することが可能です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import (
  "fmt"
)

type Foo chan Foo

func main() {
  ch := make(Foo)
  go func() {
      ch := ch
      for {
          done := <-ch
          fmt.Println("Goroutine 2")
          done <- ch
      }
  }()
  
  for i := 0; i < 100; i++ {
      fmt.Println("Goroutine 1")
      done := make(Foo)
      ch <- done
      ch = <-done
  }
  fmt.Println("Hello, playground")
}

channelでのやり取りが複雑になるので実用性があるかは不明ですが・・・。 例えば先程の例だと、普通にループを書いたほうが圧倒的にシンプルです。

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
  "fmt"
)

func main() {
  for i := 0; i < 100; i++ {
      fmt.Println("Goroutine 1")
      fmt.Println("Goroutine 2")
  }
  fmt.Println("Hello, playground")
}

無限chanが必要になる多くのケースは、このような書き換えができるような気がします。 (そもそも必要になったことがない)

まとめ

  • chanの個数の上限は4096から8192の間のどこか
  • 256個あたりからビルド時間に影響が出始める
    • プログラムを読む人の精神力に多大な影響を与えるので、実際は2個までに留めるべきだと思う
  • 再帰的に型を定義することで、無限にchanを付けた時と同等のことが可能

chanを大量に付けたいケースには今までに僕自身は遭遇したことがないです。 有用な例を見つけた人は教えてください。

Comments