いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-288 (goアプリが使っているシステムコールを見てみる2 (バッファリング))

概要

以下、自分用のメモです。特にGo限定の話では無くて、straceコマンドのメモです。

前回、以下のような記事をメモしました。

devlights.hatenablog.com

ついでなので、バッファリングする・しないという行為がシステムコール上で

どのようになるのかを確認してみましょう。プログラミングの参考書とかで「バッファリングしましょう」って書かれていたりしますが、実際にOS側に仕事をお願いする際に、どんな形で渡されているのか、つまり、どんな形でシステムコールされているのかを知るのって結構いい勉強になります。

サンプル

ヘルパー関数

以下のソースでは、出力する値を生成するために簡単なヘルパーメソッドを用意して使っています。

package enumerable

func Range(start, count int) <-chan int {
    ch := make(chan int)

    go func() {
        defer close(ch)
        for i := start; i <= count; i++ {
            ch <- i
        }
    }()

    return ch
}

バッファリングなし

まずは、バッファリング無しからです。Goの場合は *os.File をそのまま使うとバッファリング無しなので、それでやります。

コードは以下のようなものです。

package main

import (
    "fmt"
    "os"

    "github.com/devlights/go-syscall-with-strace/pkg/enumerable"
)

func main() {
    file, err := os.Create("out.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    for v := range enumerable.Range(1, 10) {
        fmt.Fprintln(file, v)
    }
}

go-syscall-with-strace/cmd/unbuffered at main · devlights/go-syscall-with-strace · GitHub

実行するためのタスクファイルは以下のような感じ。

いろいろ出ると分かりづらいので、writeシステムコールのみに絞り込みしてます。

version: '3'

vars:
  APPNAME: ./app
  SYSCALLLOG: syscall.log
  COMMANDOUT: out.txt
  STRACEFILTER: openat,read,write,clone,close

tasks:
  run-unbuffered:
    dir: cmd/unbuffered
    cmds:
      - go build -o {{ .APPNAME }}
      - strace -y -e write {{ .APPNAME }} 2>&1 1>/dev/null | tee {{ .SYSCALLLOG }}

実行すると以下のようになります。

$ task run-unbuffered
task: [run-unbuffered] go build -o ./app
task: [run-unbuffered] strace -y -e write ./app 2>&1 1>/dev/null | tee syscall.log
--- SIGURG {si_signo=SIGURG, si_code=SI_TKILL, si_pid=16435, si_uid=33333} ---
write(3</workspace/go-syscall-with-strace/cmd/unbuffered/out.txt>, "1\n", 2) = 2
write(3</workspace/go-syscall-with-strace/cmd/unbuffered/out.txt>, "2\n", 2) = 2
write(3</workspace/go-syscall-with-strace/cmd/unbuffered/out.txt>, "3\n", 2) = 2
write(3</workspace/go-syscall-with-strace/cmd/unbuffered/out.txt>, "4\n", 2) = 2
write(3</workspace/go-syscall-with-strace/cmd/unbuffered/out.txt>, "5\n", 2) = 2
write(3</workspace/go-syscall-with-strace/cmd/unbuffered/out.txt>, "6\n", 2) = 2
write(3</workspace/go-syscall-with-strace/cmd/unbuffered/out.txt>, "7\n", 2) = 2
write(3</workspace/go-syscall-with-strace/cmd/unbuffered/out.txt>, "8\n", 2) = 2
write(3</workspace/go-syscall-with-strace/cmd/unbuffered/out.txt>, "9\n", 2) = 2
write(3</workspace/go-syscall-with-strace/cmd/unbuffered/out.txt>, "10\n", 3) = 3
+++ exited with 0 +++

fmt.Fprintln で呼び出しを行った時点で即 writeシステムコール が呼び出されていますね。

バッファリングあり

次はバッファリングありで同じ出力をしてみます。

package main

import (
    "bufio"
    "fmt"
    "os"

    "github.com/devlights/go-syscall-with-strace/pkg/enumerable"
)

func main() {
    file, err := os.Create("out.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    bufW := bufio.NewWriter(file)
    for v := range enumerable.Range(1, 10) {
        fmt.Fprintln(bufW, v)
    }
    defer bufW.Flush()
}

go-syscall-with-strace/cmd/buffered at main · devlights/go-syscall-with-strace · GitHub

実行するためのタスクファイルは以下のような感じ。

いろいろ出ると分かりづらいので、writeシステムコールのみに絞り込みしてます。

version: '3'

vars:
  APPNAME: ./app
  SYSCALLLOG: syscall.log
  COMMANDOUT: out.txt
  STRACEFILTER: openat,read,write,clone,close

tasks:
  run-buffered:
    dir: cmd/buffered
    cmds:
      - go build -o {{ .APPNAME }}
      - strace -y -e write {{ .APPNAME }} 2>&1 1>/dev/null | tee {{ .SYSCALLLOG }}

実行すると以下のようになります。

$ task run-buffered
task: [run-buffered] go build -o ./app
task: [run-buffered] strace -y -e write ./app 2>&1 1>/dev/null | tee syscall.log
--- SIGURG {si_signo=SIGURG, si_code=SI_TKILL, si_pid=16688, si_uid=33333} ---
--- SIGURG {si_signo=SIGURG, si_code=SI_TKILL, si_pid=16688, si_uid=33333} ---
--- SIGURG {si_signo=SIGURG, si_code=SI_TKILL, si_pid=16688, si_uid=33333} ---
write(3</workspace/go-syscall-with-strace/cmd/buffered/out.txt>, "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n", 21) = 21
+++ exited with 0 +++

writeシステムコールの数がグッと減って一回になりました。塊にして渡していますね。これがバッファリングされている状態です。

100回やそこらの呼び出し程度ぐらいでは全然バッファリング無しでも問題ないことが多いですので、常にバッファリングすることを気にする必要は無いですが、塵も積もればなんとやらって感じで、たとえ1回の呼び出しが1msとかで済むものでも何万回もバッファリング無しで行うとボトルネックになる場合があります。そのような状態が計測で確認出来た場合は、バッファリングしたら劇的に改善する場合もあったりします。

参考情報

github.com

Go言語による並行処理

Go言語による並行処理

Amazon


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。