Skip to content

Commit 7d71916

Browse files
fix: mem-copy for READ benchmarks
1 parent 269798d commit 7d71916

File tree

3 files changed

+91
-117
lines changed

3 files changed

+91
-117
lines changed

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ require (
88
github.com/fatih/color v1.18.0
99
github.com/felixge/fgprof v0.9.5
1010
github.com/google/uuid v1.6.0
11-
github.com/minio/pkg/v3 v3.0.26-0.20250106155027-2becdc33e233
11+
github.com/minio/pkg/v3 v3.0.28
1212
github.com/ncw/directio v1.0.5
1313
github.com/spf13/cobra v1.8.1
1414
github.com/spf13/viper v1.19.0

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
5252
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
5353
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
5454
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
55-
github.com/minio/pkg/v3 v3.0.26-0.20250106155027-2becdc33e233 h1:SR5q/92Xqkj2Zg3O3sPnJvms+/ZMN5W1mSiA3htdnhc=
56-
github.com/minio/pkg/v3 v3.0.26-0.20250106155027-2becdc33e233/go.mod h1:mIaN552nu0D2jiSk5BQC8LB25f44ytbOBJCuLtksX7Q=
55+
github.com/minio/pkg/v3 v3.0.28 h1:8tSuZnJbjc3C3DM2DEh4ZnSWjMZdccd679stk8sPD60=
56+
github.com/minio/pkg/v3 v3.0.28/go.mod h1:mIaN552nu0D2jiSk5BQC8LB25f44ytbOBJCuLtksX7Q=
5757
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
5858
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
5959
github.com/ncw/directio v1.0.5 h1:JSUBhdjEvVaJvOoyPAbcW0fnd0tvRXD76wEfZ1KcQz4=

pkg/dperf/run_linux.go

+88-114
Original file line numberDiff line numberDiff line change
@@ -31,70 +31,6 @@ import (
3131
"golang.org/x/sys/unix"
3232
)
3333

34-
// odirectReader - to support O_DIRECT reads for erasure backends.
35-
type odirectReader struct {
36-
fd int
37-
Bufp *[]byte
38-
buf []byte
39-
err error
40-
seenRead bool
41-
alignment bool
42-
43-
ctx context.Context
44-
}
45-
46-
// Read - Implements Reader interface.
47-
func (o *odirectReader) Read(buf []byte) (n int, err error) {
48-
if o.ctx.Err() != nil {
49-
return 0, o.ctx.Err()
50-
}
51-
if o.err != nil && (len(o.buf) == 0 || !o.seenRead) {
52-
return 0, o.err
53-
}
54-
if !o.seenRead {
55-
o.buf = *o.Bufp
56-
n, err = syscall.Read(o.fd, o.buf)
57-
if err != nil && err != io.EOF {
58-
if errors.Is(err, syscall.EINVAL) {
59-
if err = disableDirectIO(uintptr(o.fd)); err != nil {
60-
o.err = err
61-
return n, err
62-
}
63-
n, err = syscall.Read(o.fd, o.buf)
64-
}
65-
if err != nil && err != io.EOF {
66-
o.err = err
67-
return n, err
68-
}
69-
}
70-
if n == 0 {
71-
if err == nil {
72-
err = io.EOF
73-
}
74-
o.err = err
75-
return n, err
76-
}
77-
o.err = err
78-
o.buf = o.buf[:n]
79-
o.seenRead = true
80-
}
81-
if len(buf) >= len(o.buf) {
82-
n = copy(buf, o.buf)
83-
o.seenRead = false
84-
return n, o.err
85-
}
86-
n = copy(buf, o.buf)
87-
o.buf = o.buf[n:]
88-
// There is more left in buffer, do not return any EOF yet.
89-
return n, nil
90-
}
91-
92-
// Close - Release the buffer and close the file.
93-
func (o *odirectReader) Close() error {
94-
o.err = errors.New("internal error: odirectReader Read after Close")
95-
return syscall.Close(o.fd)
96-
}
97-
9834
type nullWriter struct{}
9935

10036
func (n nullWriter) Write(b []byte) (int, error) {
@@ -103,21 +39,14 @@ func (n nullWriter) Write(b []byte) (int, error) {
10339

10440
func (d *DrivePerf) runReadTest(ctx context.Context, path string, data []byte) (uint64, error) {
10541
startTime := time.Now()
106-
fd, err := syscall.Open(path, syscall.O_DIRECT|syscall.O_RDONLY, 0o400)
42+
r, err := os.OpenFile(path, syscall.O_DIRECT|os.O_RDONLY, 0o400)
10743
if err != nil {
10844
return 0, err
10945
}
110-
unix.Fadvise(fd, 0, int64(d.FileSize), unix.FADV_SEQUENTIAL)
46+
unix.Fadvise(int(r.Fd()), 0, int64(d.FileSize), unix.FADV_SEQUENTIAL)
11147

112-
of := &odirectReader{
113-
fd: fd,
114-
Bufp: &data,
115-
ctx: ctx,
116-
alignment: d.FileSize%4096 == 0,
117-
}
118-
119-
n, err := io.Copy(&nullWriter{}, of)
120-
of.Close()
48+
n, err := copyAligned(&nullWriter{}, r, data, int64(d.FileSize), r.Fd())
49+
r.Close()
12150
if err != nil {
12251
return 0, err
12352
}
@@ -164,7 +93,7 @@ func (n nullReader) Read(b []byte) (int, error) {
16493
return len(b), nil
16594
}
16695

167-
func newEncReader(ctx context.Context) io.Reader {
96+
func newRandomReader(ctx context.Context) io.Reader {
16897
r, err := rng.NewReader()
16998
if err != nil {
17099
panic(err)
@@ -183,73 +112,108 @@ func disableDirectIO(fd uintptr) error {
183112
return err
184113
}
185114

186-
func copyAligned(fd int, r io.Reader, alignedBuf []byte, totalSize int64) (written int64, err error) {
187-
defer func() {
188-
ferr := fdatasync(fd)
189-
if ferr != nil {
190-
// preserve error on fdatasync
191-
err = ferr
192-
}
193-
cerr := syscall.Close(fd)
194-
if cerr != nil {
195-
// preserve error on close
196-
err = cerr
197-
}
198-
}()
199-
200-
// Writes remaining bytes in the buffer.
201-
writeUnaligned := func(buf []byte) (remainingWritten int64, err error) {
202-
// Disable O_DIRECT on fd's on unaligned buffer
203-
// perform an amortized Fdatasync(fd) on the fd at
204-
// the end, this is performed by the caller before
205-
// closing 'w'.
206-
if err = disableDirectIO(uintptr(fd)); err != nil {
207-
return remainingWritten, err
208-
}
209-
n, err := syscall.Write(fd, buf)
210-
return int64(n), err
115+
// DirectioAlignSize - DirectIO alignment needs to be 4K. Defined here as
116+
// directio.AlignSize is defined as 0 in MacOS causing divide by 0 error.
117+
const DirectioAlignSize = 4096
118+
119+
// copyAligned - copies from reader to writer using the aligned input
120+
// buffer, it is expected that input buffer is page aligned to
121+
// 4K page boundaries. Without passing aligned buffer may cause
122+
// this function to return error.
123+
//
124+
// This code is similar in spirit to io.Copy but it is only to be
125+
// used with DIRECT I/O based file descriptor and it is expected that
126+
// input writer *os.File not a generic io.Writer. Make sure to have
127+
// the file opened for writes with syscall.O_DIRECT flag.
128+
func copyAligned(w io.Writer, r io.Reader, alignedBuf []byte, totalSize int64, fd uintptr) (int64, error) {
129+
if totalSize == 0 {
130+
return 0, nil
211131
}
212132

133+
var written int64
213134
for {
214135
buf := alignedBuf
215-
if totalSize != -1 {
136+
if totalSize > 0 {
216137
remaining := totalSize - written
217138
if remaining < int64(len(buf)) {
218139
buf = buf[:remaining]
219140
}
220141
}
142+
143+
if len(buf)%DirectioAlignSize != 0 {
144+
// Disable O_DIRECT on fd's on unaligned buffer
145+
// perform an amortized Fdatasync(fd) on the fd at
146+
// the end, this is performed by the caller before
147+
// closing 'w'.
148+
if err := disableDirectIO(fd); err != nil {
149+
return written, err
150+
}
151+
}
152+
221153
nr, err := io.ReadFull(r, buf)
222-
eof := err == io.EOF || err == io.ErrUnexpectedEOF
154+
eof := errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF)
223155
if err != nil && !eof {
224156
return written, err
225157
}
158+
226159
buf = buf[:nr]
227-
var nw int64
228-
if len(buf)%4096 == 0 {
229-
var n int
160+
var (
161+
n int
162+
un int
163+
nw int64
164+
)
165+
166+
remain := len(buf) % DirectioAlignSize
167+
if remain == 0 {
230168
// buf is aligned for directio write()
231-
n, err = syscall.Write(fd, buf)
169+
n, err = w.Write(buf)
232170
nw = int64(n)
233171
} else {
172+
if remain < len(buf) {
173+
n, err = w.Write(buf[:len(buf)-remain])
174+
if err != nil {
175+
return written, err
176+
}
177+
nw = int64(n)
178+
}
179+
180+
// Disable O_DIRECT on fd's on unaligned buffer
181+
// perform an amortized Fdatasync(fd) on the fd at
182+
// the end, this is performed by the caller before
183+
// closing 'w'.
184+
if err = disableDirectIO(fd); err != nil {
185+
return written, err
186+
}
187+
234188
// buf is not aligned, hence use writeUnaligned()
235-
nw, err = writeUnaligned(buf)
189+
// for the remainder
190+
un, err = w.Write(buf[len(buf)-remain:])
191+
nw += int64(un)
236192
}
193+
237194
if nw > 0 {
238195
written += nw
239196
}
197+
240198
if err != nil {
241199
return written, err
242200
}
201+
243202
if nw != int64(len(buf)) {
244203
return written, io.ErrShortWrite
245204
}
246205

247-
if totalSize != -1 {
248-
if written == totalSize {
249-
return written, nil
250-
}
206+
if totalSize > 0 && written == totalSize {
207+
// we have written the entire stream, return right here.
208+
return written, nil
251209
}
210+
252211
if eof {
212+
// We reached EOF prematurely but we did not write everything
213+
// that we promised that we would write.
214+
if totalSize > 0 && written != totalSize {
215+
return written, io.ErrUnexpectedEOF
216+
}
253217
return written, nil
254218
}
255219
}
@@ -261,20 +225,30 @@ func (d *DrivePerf) runWriteTest(ctx context.Context, path string, data []byte)
261225
}
262226

263227
startTime := time.Now()
264-
fd, err := syscall.Open(path, syscall.O_DIRECT|syscall.O_RDWR|syscall.O_CREAT|syscall.O_TRUNC, 0o600)
228+
w, err := os.OpenFile(path, syscall.O_DIRECT|os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)
265229
if err != nil {
266230
return 0, err
267231
}
268232

269-
n, err := copyAligned(fd, newEncReader(ctx), data, int64(d.FileSize))
233+
n, err := copyAligned(w, newRandomReader(ctx), data, int64(d.FileSize), w.Fd())
270234
if err != nil {
235+
w.Close()
271236
return 0, err
272237
}
273238

274239
if n != int64(d.FileSize) {
240+
w.Close()
275241
return 0, fmt.Errorf("Expected to write %d, wrote %d bytes", d.FileSize, n)
276242
}
277243

244+
if err := fdatasync(int(w.Fd())); err != nil {
245+
return 0, err
246+
}
247+
248+
if err := w.Close(); err != nil {
249+
return 0, err
250+
}
251+
278252
dt := float64(time.Since(startTime))
279253
throughputInSeconds := (float64(d.FileSize) / dt) * float64(time.Second)
280254
return uint64(throughputInSeconds), nil

0 commit comments

Comments
 (0)