Skip to content

Commit c294666

Browse files
committed
build: Add benchmark for open/read ops
This benchmark aims to compare the time and space complexity across the different billy implementations, which will later be used to assess their trends over time. The initial figures shows that there is a substantial difference in cost for the Open operation amongst the os-based options, however the impact on Read operations is more subtle. As expected, in memory has the best figures for most cases. goos: linux goarch: amd64 pkg: github.com/go-git/go-billy/v6/test cpu: AMD Ryzen 7 PRO 8840HS w/ Radeon 780M Graphics BenchmarkOpenRead/stdlib_open-16 319782 3750 ns/op 152 B/op 3 allocs/op BenchmarkOpenRead/osfs.chrootOS_open-16 248833 4620 ns/op 360 B/op 9 allocs/op BenchmarkOpenRead/osfs.boundOS_open-16 182808 6370 ns/op 984 B/op 20 allocs/op BenchmarkOpenRead/memfs_open-16 1000000 1038 ns/op 224 B/op 10 allocs/op BenchmarkOpenRead/stdlib_read-16 165465 6791 ns/op 150.79 MB/s 0 B/op 0 allocs/op BenchmarkOpenRead/osfs.chrootOS_read-16 158107 7018 ns/op 145.90 MB/s 0 B/op 0 allocs/op BenchmarkOpenRead/osfs.boundOS_read-16 155649 6865 ns/op 149.15 MB/s 0 B/op 0 allocs/op BenchmarkOpenRead/memfs_read-16 705140 1703 ns/op 601.17 MB/s 0 B/op 0 allocs/op PASS ok github.com/go-git/go-billy/v6/test 34.080s Signed-off-by: Paulo Gomes <[email protected]>
1 parent 19379a5 commit c294666

File tree

1 file changed

+149
-0
lines changed

1 file changed

+149
-0
lines changed

test/bench_test.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package test
2+
3+
import (
4+
"crypto/rand"
5+
"io"
6+
"os"
7+
"testing"
8+
9+
"github.com/go-git/go-billy/v6"
10+
"github.com/go-git/go-billy/v6/memfs"
11+
"github.com/go-git/go-billy/v6/osfs"
12+
"github.com/stretchr/testify/assert"
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
const (
17+
fn = "test"
18+
contentSize = 1024 * 10
19+
bufSize = 1024
20+
)
21+
22+
type test struct {
23+
name string
24+
fn string
25+
sut billy.Filesystem
26+
openF func(billy.Filesystem) func(string) (io.ReadSeekCloser, error)
27+
createF func(*testing.B, billy.Filesystem, string) (io.WriteCloser, error)
28+
}
29+
30+
func BenchmarkOpenRead(b *testing.B) {
31+
tests := []test{
32+
{
33+
// provide baseline comparison against direct use of os.
34+
name: "stdlib",
35+
fn: filepath.Join(b.TempDir(), fn),
36+
openF: stdlibOpen,
37+
createF: stdlibCreate,
38+
},
39+
{
40+
name: "osfs.chrootOS",
41+
fn: fn,
42+
sut: osfs.New(b.TempDir(), osfs.WithChrootOS()),
43+
openF: billyOpen,
44+
createF: billyCreate,
45+
},
46+
{
47+
name: "osfs.boundOS",
48+
fn: fn,
49+
sut: osfs.New(b.TempDir(), osfs.WithBoundOS()),
50+
openF: billyOpen,
51+
createF: billyCreate,
52+
},
53+
{
54+
name: "memfs",
55+
fn: fn,
56+
sut: memfs.New(),
57+
openF: billyOpen,
58+
createF: billyCreate,
59+
},
60+
}
61+
62+
for _, tc := range tests {
63+
f, err := tc.createF(b, tc.sut, tc.fn)
64+
require.NoError(b, err)
65+
assert.NotNil(b, f)
66+
67+
prepFS(b, f)
68+
b.Run(tc.name+"_open", open(tc.fn, tc.openF(tc.sut)))
69+
}
70+
71+
for _, tc := range tests {
72+
b.Run(tc.name+"_read", read(tc.fn, tc.openF(tc.sut)))
73+
}
74+
}
75+
76+
func open(n string, of func(string) (io.ReadSeekCloser, error)) func(b *testing.B) {
77+
return func(b *testing.B) {
78+
for i := 0; i < b.N; i++ {
79+
f, err := of(n)
80+
require.NoError(b, err)
81+
assert.NotNil(b, f)
82+
83+
b.StopTimer()
84+
err = f.Close()
85+
require.NoError(b, err)
86+
b.StartTimer()
87+
}
88+
}
89+
}
90+
91+
func read(n string, of func(string) (io.ReadSeekCloser, error)) func(b *testing.B) {
92+
return func(b *testing.B) {
93+
b.StopTimer()
94+
95+
buf := make([]byte, 1024)
96+
f, err := of(n)
97+
require.NoError(b, err)
98+
assert.NotNil(b, f)
99+
100+
b.StartTimer()
101+
for i := 0; i < b.N; i++ {
102+
_, err = f.Seek(0, io.SeekStart)
103+
require.NoError(b, err)
104+
105+
for {
106+
n, err := f.Read(buf)
107+
if n == 0 {
108+
break
109+
}
110+
b.SetBytes(int64(n))
111+
require.NoError(b, err)
112+
}
113+
}
114+
115+
err = f.Close()
116+
require.NoError(b, err)
117+
}
118+
}
119+
120+
func prepFS(b *testing.B, f io.WriteCloser) {
121+
defer f.Close()
122+
123+
content := make([]byte, contentSize)
124+
_, err := rand.Read(content)
125+
require.NoError(b, err, "failed to generate random content")
126+
127+
_, err = f.Write(content)
128+
require.NoError(b, err, "failed to write test file")
129+
}
130+
131+
func stdlibOpen(_ billy.Filesystem) func(n string) (io.ReadSeekCloser, error) {
132+
return func(n string) (io.ReadSeekCloser, error) {
133+
return os.Open(n)
134+
}
135+
}
136+
137+
func billyOpen(fs billy.Filesystem) func(n string) (io.ReadSeekCloser, error) {
138+
return func(n string) (io.ReadSeekCloser, error) {
139+
return fs.Open(n)
140+
}
141+
}
142+
143+
func stdlibCreate(_ *testing.B, _ billy.Filesystem, n string) (io.WriteCloser, error) {
144+
return os.Create(n)
145+
}
146+
147+
func billyCreate(b *testing.B, fs billy.Filesystem, n string) (io.WriteCloser, error) {
148+
return fs.Create(n)
149+
}

0 commit comments

Comments
 (0)