Skip to content

Commit a3eda1e

Browse files
committed
mmap: implement io.WriterTo and io.ReadSeeker
1 parent db7319d commit a3eda1e

File tree

4 files changed

+168
-1
lines changed

4 files changed

+168
-1
lines changed

mmap/mmap_other.go

+16
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package mmap
99

1010
import (
1111
"fmt"
12+
"io"
1213
"os"
1314
)
1415

@@ -46,6 +47,21 @@ func (r *ReaderAt) ReadAt(p []byte, off int64) (int, error) {
4647
return r.f.ReadAt(p, off)
4748
}
4849

50+
// WriteTo implements the io.WriterTo interface.
51+
func (r *ReaderAt) WriteTo(w io.Writer) (int64, error) {
52+
return r.f.WriterTo(w)
53+
}
54+
55+
// Read implements the io.ReadSeeker interface.
56+
func (r *ReaderAt) Read(p []byte) (n int, err error) {
57+
return r.f.Read(p)
58+
}
59+
60+
// Seek implements the io.ReadSeeker interface.
61+
func (r *ReaderAt) Seek(offset int64, whence int) (int64, error) {
62+
return r.f.Seek(offset, whence)
63+
}
64+
4965
// Open memory-maps the named file for reading.
5066
func Open(filename string) (*ReaderAt, error) {
5167
f, err := os.Open(filename)

mmap/mmap_test.go

+117
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,120 @@ func TestOpen(t *testing.T) {
3232
t.Fatalf("\ngot %q\nwant %q", string(got), string(want))
3333
}
3434
}
35+
36+
func TestSeekRead(t *testing.T) {
37+
const filename = "mmap_test.go"
38+
r, err := Open(filename)
39+
if err != nil {
40+
t.Fatalf("Open: %v", err)
41+
}
42+
buf := make([]byte, 1)
43+
if _, err := r.Seek(0, io.SeekStart); err != nil {
44+
t.Fatalf("Seek: %v", err)
45+
}
46+
n, err := r.Read(buf)
47+
if err != nil {
48+
t.Fatalf("Read: %v", err)
49+
}
50+
if n != 1 {
51+
t.Fatalf("Read: got %d bytes, want 1", n)
52+
}
53+
if buf[0] != '/' { // first comment slash
54+
t.Fatalf("Read: got %q, want '/'", buf[0])
55+
}
56+
if _, err := r.Seek(1, io.SeekCurrent); err != nil {
57+
t.Fatalf("Seek: %v", err)
58+
}
59+
n, err = r.Read(buf)
60+
if err != nil {
61+
t.Fatalf("Read: %v", err)
62+
}
63+
if n != 1 {
64+
t.Fatalf("Read: got %d bytes, want 1", n)
65+
}
66+
if buf[0] != ' ' { // space after comment
67+
t.Fatalf("Read: got %q, want ' '", buf[0])
68+
}
69+
if _, err := r.Seek(-1, io.SeekEnd); err != nil {
70+
t.Fatalf("Seek: %v", err)
71+
}
72+
n, err = r.Read(buf)
73+
if err != nil {
74+
t.Fatalf("Read: %v", err)
75+
}
76+
if n != 1 {
77+
t.Fatalf("Read: got %d bytes, want 1", n)
78+
}
79+
if buf[0] != '\n' { // last newline
80+
t.Fatalf("Read: got %q, want newline", buf[0])
81+
}
82+
if _, err := r.Seek(0, io.SeekEnd); err != nil {
83+
t.Fatalf("Seek: %v", err)
84+
}
85+
if _, err := r.Read(buf); err != io.EOF {
86+
t.Fatalf("Read: expected EOF, got %v", err)
87+
}
88+
}
89+
90+
func TestWriterTo_idempotency(t *testing.T) {
91+
const filename = "mmap_test.go"
92+
r, err := Open(filename)
93+
if err != nil {
94+
t.Fatalf("Open: %v", err)
95+
}
96+
buf := bytes.NewBuffer(make([]byte, 0, len(r.data)))
97+
// first run
98+
n, err := r.WriteTo(buf)
99+
if err != nil {
100+
t.Fatalf("WriteTo: %v", err)
101+
}
102+
if n != int64(len(r.data)) {
103+
t.Fatalf("WriteTo: got %d bytes, want %d", n, len(r.data))
104+
}
105+
if !bytes.Equal(buf.Bytes(), r.data) {
106+
t.Fatalf("WriteTo: got %q, want %q", buf.Bytes(), r.data)
107+
}
108+
// second run
109+
n, err = r.WriteTo(buf)
110+
if err != nil {
111+
t.Fatalf("WriteTo: %v", err)
112+
}
113+
if n != 0 {
114+
t.Fatalf("WriteTo: got %d bytes, want %d", n, 0)
115+
}
116+
if !bytes.Equal(buf.Bytes(), r.data) {
117+
t.Fatalf("WriteTo: got %q, want %q", buf.Bytes(), r.data)
118+
}
119+
}
120+
121+
func BenchmarkMmapCopy(b *testing.B) {
122+
var f io.ReadSeeker
123+
124+
// mmap some big-ish file; will only work on unix-like OSs.
125+
r, err := Open("/proc/self/exe")
126+
if err != nil {
127+
b.Fatalf("Open: %v", err)
128+
}
129+
130+
// Sanity check: ensure we will run into the io.Copy optimization when using the NEW code above.
131+
var _ io.WriterTo = r
132+
133+
// f = io.NewSectionReader(r, 0, int64(len(r.data))) // old
134+
f = r // new
135+
136+
buf := bytes.NewBuffer(make([]byte, 0, len(r.data)))
137+
// "Hide" the ReaderFrom interface by wrapping the writer.
138+
// Otherwise we skew the results by optimizing the wrong side.
139+
writer := struct{ io.Writer }{buf}
140+
141+
b.ReportAllocs()
142+
b.ResetTimer()
143+
144+
for i := 0; i < b.N; i++ {
145+
_, _ = f.Seek(0, io.SeekStart)
146+
buf.Reset()
147+
148+
n, _ := io.Copy(writer, f)
149+
b.SetBytes(n)
150+
}
151+
}

mmap/mmap_unix.go

+19-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const debug = false
3131
// not safe to call Close and reading methods concurrently.
3232
type ReaderAt struct {
3333
data []byte
34+
*io.SectionReader
3435
}
3536

3637
// Close closes the reader.
@@ -79,6 +80,20 @@ func (r *ReaderAt) ReadAt(p []byte, off int64) (int, error) {
7980
return n, nil
8081
}
8182

83+
// WriteTo implements the io.WriterTo interface.
84+
func (r *ReaderAt) WriteTo(w io.Writer) (int64, error) {
85+
pos, err := r.Seek(0, io.SeekCurrent)
86+
if err != nil {
87+
return 0, fmt.Errorf("mmap: WriteTo: %w", err)
88+
}
89+
n, err := w.Write(r.data[pos:])
90+
_, seekErr := r.Seek(int64(n), io.SeekCurrent)
91+
if seekErr != nil {
92+
panic("cannot happen") // neither errWhence nor errOffset are possible with current usage
93+
}
94+
return int64(n), err
95+
}
96+
8297
// Open memory-maps the named file for reading.
8398
func Open(filename string) (*ReaderAt, error) {
8499
f, err := os.Open(filename)
@@ -113,7 +128,10 @@ func Open(filename string) (*ReaderAt, error) {
113128
if err != nil {
114129
return nil, err
115130
}
116-
r := &ReaderAt{data}
131+
r := &ReaderAt{
132+
data: data,
133+
}
134+
r.SectionReader = io.NewSectionReader(r, 0, size)
117135
if debug {
118136
var p *byte
119137
if len(data) != 0 {

mmap/mmap_windows.go

+16
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const debug = false
3030
// not safe to call Close and reading methods concurrently.
3131
type ReaderAt struct {
3232
data []byte
33+
*io.SectionReader
3334
}
3435

3536
// Close closes the reader.
@@ -78,6 +79,20 @@ func (r *ReaderAt) ReadAt(p []byte, off int64) (int, error) {
7879
return n, nil
7980
}
8081

82+
// WriteTo implements the io.WriterTo interface.
83+
func (r *ReaderAt) WriteTo(w io.Writer) (int64, error) {
84+
pos, err := r.Seek(0, io.SeekCurrent)
85+
if err != nil {
86+
return 0, fmt.Errorf("mmap: WriteTo: %w", err)
87+
}
88+
n, err := w.Write(r.data[pos:])
89+
_, seekErr := r.Seek(int64(n), io.SeekCurrent)
90+
if seekErr != nil {
91+
panic("cannot happen") // neither errWhence nor errOffset are possible with current usage
92+
}
93+
return int64(n), err
94+
}
95+
8196
// Open memory-maps the named file for reading.
8297
func Open(filename string) (*ReaderAt, error) {
8398
f, err := os.Open(filename)
@@ -121,6 +136,7 @@ func Open(filename string) (*ReaderAt, error) {
121136
data := unsafe.Slice((*byte)(unsafe.Pointer(ptr)), size)
122137

123138
r := &ReaderAt{data: data}
139+
r.SectionReader = io.NewSectionReader(r, 0, size)
124140
if debug {
125141
var p *byte
126142
if len(data) != 0 {

0 commit comments

Comments
 (0)