pecoに入った修正をみて、果たしてchanはいくつまで付けられるのか気になったので、
雑に試してみました。
先に断っておきますが、全く有用ではないですよ。
背景
pecoに入った修正はこちら(一部抜粋)。
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のコードを自動生成します。
print <<EOF;
package main
import (
"fmt"
)
type Foo @{['chan ' x 4096]} struct{}
func main() {
fmt.Printf("Hello, %#v\\n", make(Foo))
}
EOF
chanの個数を変えて何度かビルドを繰り返します。
time go build -o main main.go
結果
chanの個数とビルドにかかった時間をまとめてみました。
| chanの個数 | ビルド時間 |
|---|---|
| 1 | 0.236s |
| 2 | 0.240s |
| 4 | 0.226s |
| 8 | 0.234s |
| 16 | 0.240s |
| 32 | 0.250s |
| 64 | 0.281s |
| 128 | 0.258s |
| 256 | 0.360s |
| 512 | 0.775s |
| 1024 | 3.228s |
| 2048 | 18.605s |
| 4096 | 1m53.614s |
| 8192 | 13m46.018s(ビルド失敗したので参考記録) |
8192個付けたら以下のようなエラーを吐いてビルドが失敗してしまったので、 8192個の時の記録は参考記録です。
# command-line-arguments
too much data in section SDWARFINFO (over 2000000000 bytes)
何かビルドの設定をいじればもっと行けるかもしれませんが、
デフォルトの設定では4096から8192の間に限界があるようです。
4096個chanを付けたときのソースコードは20KB程度なのにバイナリサイズは524MBまで膨らんでいました。
256個当たりからビルド時間に影響が出ているので、
ビルド時間を考える256個以下に抑えるのがよさそうです。
それ以上だと $O(n^{2.6})$ 程度のオーダーでビルド時間が延びます。
とはいえ、256個もchanを付いたコードを人間が読めるとは思えないので、
2個が限度でしょうね・・・。
3個以上必要になるケースは余りないと思います。
型定義を再帰的にして無限chanを実現する
そもそも、chanを大量に並べなくとも、
型定義を再帰的に行えば無限のchanを付けたときと同等のことができます。
例えば以下のコードで"Goroutine 1"と"Goroutine 2"を交互に表示することが可能です。
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でのやり取りが複雑になるので実用性があるかは不明ですが・・・。 例えば先程の例だと、普通にループを書いたほうが圧倒的にシンプルです。
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を大量に付けたいケースには今までに僕自身は遭遇したことがないです。
有用な例を見つけた人は教えてください。