4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

os.Open自体のオーバーヘッドについて

Last updated at Posted at 2025-02-02

最近、大学のサークルの人たちとGoを使ってのDB自作を始めました。
参考にしているのはこちらの本です:

この本は、実際に簡単なDBの実装を追いながらデータベースの実装や中身の仕組みについて学んでいくという内容になっており、Javaによるサンプルコードも公開されています。
これを参考にしながらGoで実装していこうという感じです。

疑問

ストレージ周りの操作に関する部分にて、参考にしている本の実装が、OpenしたファイルをCloseせずに使い回すというものになっていました。

ここで一つ疑問が湧きました。os.Fileは、Openしたとてそのファイルの内容がメモリに読み込まれるわけではありません。実際に読むためにはReadが必要なはずです。
ということはつまり、os.Fileはファイルへの参照を持っているだけであり、ファイルアクセスに伴うオーバーヘッドはOpen/Close操作ではほぼ発生しないはずです。
だというのに、本の提供する実装ではos.Fileに対応するオブジェクトをCloseせずに使いまわしています。
本当にこのコードに意味はあるのでしょうか?

仮説と検証

意味のない実装が本に載るわけがありません。
きっとos.Openはそれなりに重い操作で、それを回避するためにこういった実装になっているのでしょう。
というわけで、ファイルを読むたびにos.Openos.Closeをするコード、os.Openしたまま繰り返しファイルを読むコードを動かして比較してみることにしました。

検証に使ったのは以下のコードです:

package main

import (
	"fmt"
	"os"
	"time"
)

func main() {
	iteration:=100000
	buf := make([]byte, 1)

	start := time.Now()
	for i:=0; i<iteration; i++ {
		f, _ := os.Open("test.txt")
		f.Read(buf)
		f.Close()
	}
	fmt.Printf("open/close, for %v times: %vms\n", iteration, time.Since(start).Milliseconds())

	start = time.Now()
	f, _ := os.Open("test.txt")
	for i:=0; i<iteration; i++ {
		f.Seek(0,0) // 毎回ファイルの頭から読み始めるため
		f.Read(buf)
	}
	f.Close()
	fmt.Printf("keep opened, for %v times: %vms\n", iteration, time.Since(start).Milliseconds())
}

これを手元のUbuntuで動かしてみると、結果として以下のような出力が得られました:

$ go run main.go
open/close, for 100000 times: 436ms
keep opened, for 100000 times: 73ms

というわけで、10万回ファイルを読む場合について、その都度ファイルをOpenしてCloseする場合には436ms、一度Openしたファイルをそのまま使用する場合には73msと、およそ6倍ほど速くなることが判明しました。
それなりにos.Openは重いようです。

備考

Unixでのgoの実装では、Fileはファイルディスクリプタを保持する実装になるようです。

go-1.23/src/os/types.go
type File struct {
	*file // os specific
}
go-1.23/src/os/file_unix.go
// file is the real representation of *File.
// The extra level of indirection ensures that no clients of os
// can overwrite this data, which could cause the finalizer
// to close the wrong file descriptor.
type file struct {
	pfd         poll.FD
	name        string
	dirinfo     atomic.Pointer[dirInfo] // nil unless directory being read
	nonblock    bool                    // whether we set nonblocking mode
	stdoutOrErr bool                    // whether this is stdout or stderr
	appendMode  bool                    // whether file is opened for appending
}
go-1.23/src/internal/poll/fd_unix.go
// FD is a file descriptor. The net and os packages use this type as a
// field of a larger type representing a network connection or OS file.
type FD struct {
...

ということは、今回の結果を加味すると、FDの取得にある程度のオーバーヘッドがあるという感じなんですかね。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?