Go言語の並行処理で総和を求める
パタヘネ本の7章(マルチコアとマルチプロセッサとクラスタ)に以下のような(ちょっと違うけど)図が載っていたので、Goの並行処理を使って100,000個の数の和を求めてみた。
最初の図とはちょっと違って、実際の制御はこんな感じ。
値が2つ揃ったら、加算を行うgoroutineを生成する。 goroutineからは、channelを通して合計が送信される。 全ての数を足し切るまでこれを繰り返す。(足す順番は気にしない)
足す数は0〜99,999までの等差数列。
time.Sleep(1 * time.Millisecond)
は、後々の単純総和コードとの比較のために、擬似的に処理を重くしているつもり。(足し算だけだと処理が軽すぎて並行処理にした方が遅かった…)
下は単純に総和を求めるコード。
実行結果
$ time go run sum_goroutine.go 704982704 go run sum_goroutine.go 0.52s user 1.47s system 119% cpu 1.668 total $ time go run sum_simple.go 704982704 go run sum_simple.go 1.23s user 1.75s system 2% cpu 2:07.93 total
単純な総和が2分強かかっているのに対して、goroutineを使うと1.668秒。
本当は、子ルーチン(生成したgoroutine)同士で通信を行なって、親ルーチン(大本のgoroutine)には結果だけを返すように実装したかったけど、channel制御が複雑になってしまって断念。
子ルーチンの計算結果を親ルーチンに返して、それを元にまた子ルーチンを生成する方法で実装した。
channel制御むずかしい…。すぐにdeadlock起こす。
チャネルの配列(スライス)を生成する
Go言語でチャネルの配列(スライス)を生成するには、以下のように宣言する。
チャネルの固定長配列
var chans = []chan int { make(chan int), make(chan int), make(chan int), make(chan int), }
もしくは
var chans [4]chan int for i := range chans { chans[i] = make(chan int) }
チャネルの動的配列
変数numの大きさのチャネル配列確保。
num := 10 ch := make([]chan int, num) for i, _ := range ch { ch[i] = make(chan int) }
2013.4.21 追記
これは配列で、
var chans [4]chan int
こっちはスライスですね。
var chans = []chan int ch := make([]chan int, num)
参考
Go言語のflagパッケージを使う
Go言語でコマンドラインオプションを扱う - そこはかとなく書くよん。
に触発されたので、flagパッケージについて自分で使ってみてハマったところを書いておく。
コマンドラインオプションの略称を登録する
flag.IntVar()
などのXxxVar()
を使う。
これで -boolでも-bでも同じ変数にフラグの結果が反映されるようになる。
$ go run flag_shorthand.go -bool -int 100 -string hoge bool flag = true int flag = 100 string flag = hoge $ go run flag_shorthand.go -b -i 100 -s hoge bool flag = true int flag = 100 string flag = hoge
コマンドラインオプションを最後まで解析する
flag.Parse()
の解析は、非フラグ引数(頭に"-"の付いた引数)の前、もしくは"--"の後で打ち切られる。
Flag parsing stops just before the first non-flag argument ("-" is a non-flag argument) or after the terminator "--".
http://golang.org/pkg/flag/#pkg-overview
そのため、以下のようなコマンドラインだとxxx
の部分でフラグ解析が打ち切られるため、フラグが設定されない。
$ go run flag_shorthand.go xxx -b -i 100 -s hoge bool flag = false int flag = 0 string flag = blank
そこで、flag.NewFlagSet()
を用いて以下のような処理を書くことでフラグ引数の前にxxx
のような非フラグ引数があっても最後までフラグ解析を行わせることができる。
flag.NewFlagSet()
でFlagSetを生成した場合、Parse()
には解析する文字列のスライスが引数として要求される。その引数の開始インデックスを1ずつ進めてやればいい。
$ go run flag_checkall.go xxx -b -i 100 -s hoge bool flag = true int flag = 100 string flag = hoge
外部パッケージでoptargなるものがあるそうなので、後で試してみよう。
Go言語は定数配列宣言ができない
以下のコードはコンパイルが通らない。
package main func main() { const array = [...]int {1,2,3,4,5} }
コンパイルエラー。
const initializer must be constan
Goでは、配列やスライスは全てランタイム中に生成される。 それに対して、定数はコンパイル時に生成される。 そのため、配列を定数として宣言することはできないらしい。
参考
はじめてのOSコードリーディング #2 simhでV6を動かす
simhというエミュレータ上でUNIX V6を動かせるとのことで試してみた。
simhエミュレータのインストール
http://simh.trailing-edge.com/がsimhの開発プロジェクト。
debian系なら apt-get で楽ちんインストール。
$ sudo apt-get install simh
simh用のUNIX V6キットをダウンロード
http://simh.trailing-edge.com/software.htmlからPDP-11 UNIX V6キットをダウンロードする。
$ wget http://simh.trailing-edge.com/kits/uv6swre.zip
$ unzip uv6swre.zip
simhでUNIX V6を動かす
起動の前にPDP-11用の設定ファイルを作る。名前はなんでもいいけど、pdp11.rcにした。
set cpu 11/40
set cpu u18
att rk0 unix0_v6_rk.dsk
att rk1 unix1_v6_rk.dsk
att rk2 unix2_v6_rk.dsk
att rk3 unix3_v6_rk.dsk
boot rk0
simh、UNIX V6を起動する
simhのPDP-11エミュレータを起動する。引数に先ほどの設定ファイルをつける。
$ pdp11 pdp11.rc
PDP-11 simulator V3.8-1
Disabling XQ
@
@の後にunix
と入力するとUNIX V6が立ち上がる。ログイン名はroot
。
@unix
login:root
UNIX V6の終了方法
sync
を3回入力してからctrl-e
を入力する。
# sync
# sync
# sync
# [ctrl-e]
simhの終了方法
quit
を入力
UNIX V6上でアセンブラ出力
ed hoge.c
で頑張ってコードを書くcc -S hoge.c
でアセンブラコードを吐かせるcat hoge.s
で眺める
参考ページ
2013.1.30追記
ここのページのやり方の方がいいみたい。