雑なメモ書き

気楽にいきます

GolangにおけるバッファリングI/Oの確認

GolangにおけるバッファリングI/O

C言語でstdioライブラリを使用した場合、バッファリングされた入出力を行うのだが、goの場合は通常fmtで標準出力に出力するケースが多い。この場合バッファリングが行われるのかこの辺の理解が曖昧で、気になったので調べてみた。(そもそも論として、bufioがあるので大丈夫だとは思うのですが、気になったので調べてみたメモ。あとでdebugして確認したい)

Println

  • Fprintlnをos.Stdoutを引数にして呼び出している
func Println(a ...interface{}) (n int, err error) {
    return Fprintln(os.Stdout, a...)
}

Fprintln

  • doPrintlnでbufを生成
  • w.Writeで書き込み
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
    p := newPrinter()
    p.doPrintln(a)
    n, err = w.Write(p.buf)
    p.free()
    return
}

os.Stdout

  • wはStdoutなので
  • osに定義がある
Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")

NewFile

func NewFile(fd uintptr, name string) *File {
    kind := kindNewFile
    if nb, err := unix.IsNonblock(int(fd)); err == nil && nb {
        kind = kindNonBlock
    }
    return newFile(fd, name, kind)
}

Write

  • f.writeで書き込んでいる
func (f *File) Write(b []byte) (n int, err error) {
    if err := f.checkValid("write"); err != nil {
        return 0, err
    }
    n, e := f.write(b)
    if n < 0 {
        n = 0
    }
    if n != len(b) {
        err = io.ErrShortWrite
    }

    epipecheck(f, e)

    if e != nil {
        err = f.wrapErr("write", e)
    }

    return n, err
}

f.write

  • f.pfd.Writeで書き込みをしているようだ
func (f *File) write(b []byte) (n int, err error) {
    n, err = f.pfd.Write(b)
    runtime.KeepAlive(f)
    return n, err
}
type file struct {
    pfd         poll.FD
    name        string
    dirinfo     *dirInfo // nil unless directory being read
    nonblock    bool     // whether we set nonblocking mode
    stdoutOrErr bool     // whether this is stdout or stderr
}

poll.FD

  • poll.FDはfile descriptorだとみていい
  • os file か networkを扱うようだ
type FD struct {
    // Lock sysfd and serialize access to Read and Write methods.
    fdmu fdMutex

    // System file descriptor. Immutable until Close.
    Sysfd int

    // I/O poller.
    pd pollDesc

    // Writev cache.
    iovecs *[]syscall.Iovec

    // Semaphore signaled when file is closed.
    csema uint32

    // Non-zero if this file has been set to blocking mode.
    isBlocking uint32

    // Whether this is a streaming descriptor, as opposed to a
    // packet-based descriptor like a UDP socket. Immutable.
    IsStream bool

    // Whether a zero byte read indicates EOF. This is false for a
    // message based socket connection.
    ZeroReadIsEOF bool

    // Whether this is a file rather than a network socket.
    isFile bool
}

FD.Write

  • ここのsyscall.Writeで書き込んでいると考えられる
  • おそらくLinuxのsystem callのwriteを呼んでいるはずなのでこれはバッファリングはないはず
func (fd *FD) Write(p []byte) (int, error) {
    if err := fd.writeLock(); err != nil {
        return 0, err
    }
    defer fd.writeUnlock()
    if err := fd.pd.prepareWrite(fd.isFile); err != nil {
        return 0, err
    }
    var nn int
    for {
        max := len(p)
        if fd.IsStream && max-nn > maxRW {
            max = nn + maxRW
        }
        n, err := syscall.Write(fd.Sysfd, p[nn:max])
        if n > 0 {
            nn += n
        }
        if nn == len(p) {
            return nn, err
        }
        if err == syscall.EAGAIN && fd.pd.pollable() {
            if err = fd.pd.waitWrite(fd.isFile); err == nil {
                continue
            }
        }
        if err != nil {
            return nn, err
        }
        if n == 0 {
            return nn, io.ErrUnexpectedEOF
        }
    }
}

参考