go 1.12 では Windows にも Unix ドメインソケットが入るので簡単に使ってみた。
参考
go 1.12 beta1 のインストール
ここ(Google グループ) に書いてある通りにダウンロード。
> go get golang.org/dl/go1.12beta1 > go1.12beta1 download
簡単にインストールできるし、Windows でもコマンドが同じなのが便利。
ベンチマーク
単純にサーバーとクライアントを作るのもつまらないので、go test -bench
で通信のベンチマークをとってみる
サーバーサイド
net.Listen
に渡す引数を tcp
から unix
に変えて、IPアドレスとポートの代わりにUNIXドメインソケット用ファイルをアドレスとして渡すだけ。
package main import ( "log" "net" "os" "path/filepath" ) var ok = []byte("ok") var bufSize = 65537 func main() { proto := "unix" addr := filepath.Join(os.TempDir(), "unixdomainsocketsample") os.Remove(addr) listener, _ := net.Listen(proto, addr) defer listener.Close() for { func() { conn, err := listener.Accept() if err != nil { log.Fatal(err) } defer conn.Close() buf := make([]byte, bufSize) for { for { n, err := conn.Read(buf) if err != nil { return } // 終端をチェック if buf[n-1] == '\n' { break } } conn.Write(ok) } }() } }
クライアントサイド
ベンチマーク用関数を定義。送信サイズごとに計測
package main import ( "log" "net" "os" "path/filepath" "strings" "testing" ) func setup() net.Conn { proto := "unix" addr := filepath.Join(os.TempDir(), "unixdomainsocketsample") conn, err := net.Dial(proto, addr) if err != nil { panic(err) } return conn } func sendBase(conn net.Conn, times int, data []byte) { buf := make([]byte, 2) for i := 0; i < times; i++ { _, err := conn.Write(data) if err != nil { log.Println(err) return } conn.Read(buf) } } func BenchmarkSend1Char(b *testing.B) { conn := setup() defer conn.Close() benchmarks := []struct { name string data []byte }{ {"00001-char", []byte(strings.Repeat("a", 1))}, {"00127-char", []byte(strings.Repeat("a", 127))}, {"00256-char", []byte(strings.Repeat("a", 256))}, {"00512-char", []byte(strings.Repeat("a", 512))}, {"01024-char", []byte(strings.Repeat("a", 1024))}, {"02048-char", []byte(strings.Repeat("a", 2048))}, {"04096-char", []byte(strings.Repeat("a", 4096))}, {"08192-char", []byte(strings.Repeat("a", 8192))}, {"16384-char", []byte(strings.Repeat("a", 16384))}, {"32768-char", []byte(strings.Repeat("a", 32768))}, {"65536-char", []byte(strings.Repeat("a", 65536))}, {"131072-char", []byte(strings.Repeat("a", 131072))}, {"262144-char", []byte(strings.Repeat("a", 262144))}, } for _, bm := range benchmarks { var bmData []byte b.Run(bm.name, func(b *testing.B) { bmData = append(bm.data, '\n') // 終端追加 b.ResetTimer() sendBase(conn, b.N, bmData) }) } }
計測
計測方法は、サーバー側を起動した後、ベンチコマンドをクライアント側で実行する。
サーバー側実行
> go1.12beta1 build -o server.exe server.go & server.exe
クライアント側
> go1.12beta1 test -bench .
結果
TCP、名前付きパイプとの実行結果を合わせて表にしてみた。TCPは標準の net
パッケージを使用。名前付きパイプはMicroSoft/winio
(https://github.com/Microsoft/go-winio) の実装を使用
() の中はサーバー側のバッファサイズ
size は 送信データのサイズ
size | Unix(8192) | Unix(65537) | Tcp(8192) | Tcp(65537) | Pipe(8192) | Pipe(65537) |
---|---|---|---|---|---|---|
1 bytes | 19900 ns/op | 26439 ns/op | 74849 ns/op | 77649 ns/op | 60250 ns/op | 58899 ns/op |
127 bytes | 19230 ns/op | 26299 ns/op | 74449 ns/op | 77105 ns/op | 59733 ns/op | 58099 ns/op |
256 bytes | 19319 ns/op | 26499 ns/op | 75199 ns/op | 77009 ns/op | 61233 ns/op | 56000 ns/op |
512 bytes | 19790 ns/op | 26119 ns/op | 76249 ns/op | 77757 ns/op | 57700 ns/op | 58233 ns/op |
1024 bytes | 21549 ns/op | 27120 ns/op | 76349 ns/op | 77599 ns/op | 60200 ns/op | 57533 ns/op |
2048 bytes | 19020 ns/op | 33019 ns/op | 86399 ns/op | 87699 ns/op | 60050 ns/op | 57549 ns/op |
4096 bytes | 25670 ns/op | 31639 ns/op | 93450 ns/op | 96149 ns/op | 60600 ns/op | 59499 ns/op |
8192 bytes | 39780 ns/op | 34859 ns/op | 119399 ns/op | 119499 ns/op | 61300 ns/op | 60166 ns/op |
16384 bytes | 44699 ns/op | 33139 ns/op | 169699 ns/op | 167199 ns/op | 65550 ns/op | 63899 ns/op |
32768 bytes | 62300 ns/op | 40339 ns/op | 270600 ns/op | 259000 ns/op | 73649 ns/op | 64999 ns/op |
65536 bytes | 84750 ns/op | 44933 ns/op | 496664 ns/op | 493668 ns/op | 86299 ns/op | 75300 ns/op |
131072 bytes | 129299 ns/op | 80049 ns/op | 905476 ns/op | 908032 ns/op | 113600 ns/op | 80750 ns/op |
262144 bytes | 213400 ns/op | 119999 ns/op | 1828996 ns/op | 1816518 ns/op | 188400 ns/op | 117900 ns/op |
TCPとUNIXドメインソケットの差が大きいのでTcp(8192)
とTpc(65536)
を除外してグラフ化
Unix ドメインソケット同士の比較だと 4096と8192の間ぐらいで速度が反転する。大きいデータを送信する場合はそのあたりを基準にチューニングする必要があるのかな? パイプとUnix ドメインソケットの比較だと、基本的にはUnixドメインソケットのほうが早いようだ。ただ、サイズが大きくなると速度が同等になるので、大きいデータを一括で送信するようなケースの場合はパイプのほうがいいのかもしれない。
まとめ
今回はUNIXドメインソケットをGO言語で使ってみた。 使うだけだとつまらないのでパフォーマンスを測定し、TCP、名前付きパイプとの速度と比較してみた。 UNIXドメインソケットはTCPより速いみたいなので、内部的な通信ではUNIXドメインソケットを使うほうがいいのかもしれない。 また、Unixドメインソケットはパイプより速いが、大きなデータを一括で送信する場合はパイプでも速度的な違いはない。
今後の展開
Go言語とそれ以外の言語で通信できるようなら色々楽しそうなので そういうことにもチャレンジしていきたい。