forked from pwaller/zerocopy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
zerocopy.go
136 lines (116 loc) · 3.51 KB
/
zerocopy.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// This work is subject to the CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
// license. Its contents can be found at:
// http://creativecommons.org/publicdomain/zero/1.0/
// zerocopy.Reader gives a reading interface for a byte slice or a file, which
// doesn't make a copy of the underlying byte slice.
// To achieve this, a new "Read" interface is required, where the data storage
// is not specified by the caller, but by the callee. This is so that the
// implementation is at liberty to return a byte slice
package zerocopy
import (
"bytes"
"fmt"
"io"
"os"
"reflect"
"unsafe"
"github.com/edsrzf/mmap-go"
)
// Construct a new Zero-Copy reader on `r`.
// `r` must be an *os.File or a *bytes.Reader, otherwise an error is returned.
func NewReader(r io.Reader) (ReadCloser, error) {
switch r := r.(type) {
case *bytes.Reader:
return newBytesReader(r)
case *os.File:
return newMmapReader(r)
default:
// Note: I prefer to return an error for the moment, rather than
// return a "fake" zero-copy reader which actually does a copy.
return nil, fmt.Errorf("No zero-copy reader implementation available for %T", r)
}
}
// Helper to construct a zero-copy reader directly from a byte slice.
func NewReaderFromBytes(b []byte) (ReadCloser, error) {
return NewReader(bytes.NewReader(b))
}
// The zero-copy Reader interface.
// It has a different Read() method than the usual because the semantics
// are different.
type Reader interface {
// Read `size` bytes from the underlying stream, and return a byte slice
// to those bytes. The returned []byte is a slice which references the
// underlying bytes, and must not be written to.
Read(size uint64) ([]byte, error)
}
type ReadCloser interface {
Reader
Close() error
}
// bytesReader provides zero-copy Reads on an existing bytes.Reader by futzing
// around with its internal. Caveat Emptor.
type bytesReader struct {
b *bytes.Reader
s []byte
i *int
}
// Construct a new zerocopy.Reader.
func newBytesReader(r *bytes.Reader) (*bytesReader, error) {
old := reflect.ValueOf(r).Elem()
newSlice := copyPrivateByteSlice(old.FieldByName("s"))
iptr := (*int)(unsafe.Pointer(old.FieldByName("i").UnsafeAddr()))
return &bytesReader{r, newSlice, iptr}, nil
}
func (r *bytesReader) Read(size uint64) ([]byte, error) {
if size == 0 {
return nil, nil
}
if *r.i >= len(r.s) {
return nil, io.EOF
}
// TODO(pwaller): Ideally, we'd set this on the underlying reader, but
// we assume that we own it for now.
// r.prevRune = -1
if *r.i+int(size) >= len(r.s) {
size = uint64(len(r.s) - int(*r.i))
}
result := r.s[*r.i : *r.i+int(size)]
*r.i += int(size)
return result, nil
}
func (r *bytesReader) Close() error {
return nil
}
// The mmapReader implementation works by using the bytesReader implemenation
// on an mmap'ed byte-array.
type mmapReader struct {
Reader
mmap.MMap
}
func newMmapReader(r *os.File) (*mmapReader, error) {
mapping, err := mmap.Map(r, mmap.RDONLY, 0)
if err != nil {
return nil, err
}
underlying, err := NewReaderFromBytes(mapping)
if err != nil {
return nil, err
}
return &mmapReader{
underlying,
mapping,
}, nil
}
func (m *mmapReader) Close() error {
return m.Unmap()
}
// Obtain a copy of a private byte slice.
func copyPrivateByteSlice(value reflect.Value) []byte {
newSlice := []byte{}
_newSlice := (*reflect.SliceHeader)(unsafe.Pointer(&newSlice))
_origSlice := (*reflect.SliceHeader)(unsafe.Pointer(value.UnsafeAddr()))
_newSlice.Data = _origSlice.Data
_newSlice.Len = _origSlice.Len
_newSlice.Cap = _origSlice.Cap
return newSlice
}