-
Notifications
You must be signed in to change notification settings - Fork 35
/
Copy pathmalloc.go
218 lines (183 loc) · 5.86 KB
/
malloc.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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
package offheap
import (
"fmt"
"os"
"syscall"
"github.com/glycerine/gommap"
)
// The MmapMalloc struct represents either an anonymous, private
// region of memory (if path was "", or a memory mapped file if
// path was supplied to Malloc() at creation.
//
// Malloc() creates and returns an MmapMalloc struct, which can then
// be later Free()-ed. Malloc() calls request memory directly
// from the kernel via mmap(). Memory can optionally be backed
// by a file for simplicity/efficiency of saving to disk.
//
// For use when the Go GC overhead is too large, and you need to move
// the hash table off-heap.
//
type MmapMalloc struct {
Path string
File *os.File
Fd int
FileBytesLen int64
BytesAlloc int64
MMap gommap.MMap // equiv to Mem, just avoids casts everywhere.
Mem []byte // equiv to Mmap
}
// TruncateTo enlarges or shortens the file backing the
// memory map to be size newSize bytes. It only impacts
// the file underlying the mapping, not
// the mapping itself at this point.
func (mm *MmapMalloc) TruncateTo(newSize int64) {
if mm.File == nil {
panic("cannot call TruncateTo() on a non-file backed MmapMalloc.")
}
err := syscall.Ftruncate(int(mm.File.Fd()), newSize)
if err != nil {
panic(err)
}
}
// Free releases the memory allocation back to the OS by removing
// the (possibly anonymous and private) memroy mapped file that
// was backing it. Warning: any pointers still remaining will crash
// the program if dereferenced.
func (mm *MmapMalloc) Free() {
if mm.File != nil {
mm.File.Close()
}
err := mm.MMap.UnsafeUnmap()
if err != nil {
panic(err)
}
}
// Malloc() creates a new memory region that is provided directly
// by OS via the mmap() call, and is thus not scanned by the Go
// garbage collector.
//
// If path is not empty then we memory map to the given path.
// Otherwise it is just like a call to malloc(): an anonymous memory allocation,
// outside the realm of the Go Garbage Collector.
// If numBytes is -1, then we take the size from the path file's size. Otherwise
// the file is expanded or truncated to be numBytes in size. If numBytes is -1
// then a path must be provided; otherwise we have no way of knowing the size
// to allocate, and the function will panic.
//
// The returned value's .Mem member holds a []byte pointing to the returned memory (as does .MMap, for use in other gommap calls).
//
func Malloc(numBytes int64, path string) *MmapMalloc {
mm := MmapMalloc{
Path: path,
}
flags := syscall.MAP_SHARED
if path == "" {
flags = syscall.MAP_ANON | syscall.MAP_PRIVATE
mm.Fd = -1
if numBytes < 0 {
panic("numBytes was negative but path was also empty: don't know how much to allocate!")
}
} else {
if dirExists(mm.Path) {
panic(fmt.Sprintf("path '%s' already exists as a directory, so cannot be used as a memory mapped file.", mm.Path))
}
if !fileExists(mm.Path) {
file, err := os.Create(mm.Path)
if err != nil {
panic(err)
}
mm.File = file
} else {
file, err := os.OpenFile(mm.Path, os.O_RDWR, 0777)
if err != nil {
panic(err)
}
mm.File = file
}
mm.Fd = int(mm.File.Fd())
}
sz := numBytes
if path != "" {
// file-backed memory
if numBytes < 0 {
var stat syscall.Stat_t
if err := syscall.Fstat(mm.Fd, &stat); err != nil {
panic(err)
}
sz = stat.Size
} else {
// set to the size requested
err := syscall.Ftruncate(mm.Fd, numBytes)
if err != nil {
panic(err)
}
}
mm.FileBytesLen = sz
}
// INVAR: sz holds non-negative length of memory/file.
mm.BytesAlloc = sz
prot := syscall.PROT_READ | syscall.PROT_WRITE
vprintf("\n ------->> path = '%v', mm.Fd = %v, with flags = %x, sz = %v, prot = '%v'\n", path, mm.Fd, flags, sz, prot)
var mmap []byte
var err error
if mm.Fd == -1 {
flags = syscall.MAP_ANON | syscall.MAP_PRIVATE
mmap, err = syscall.Mmap(-1, 0, int(sz), prot, flags)
} else {
flags = syscall.MAP_SHARED
mmap, err = syscall.Mmap(mm.Fd, 0, int(sz), prot, flags)
}
if err != nil {
panic(err)
}
// duplicate member to avoid casts all over the place.
mm.MMap = mmap
mm.Mem = mmap
return &mm
}
// BlockUntilSync() returns only once the file is synced to disk.
func (mm *MmapMalloc) BlockUntilSync() {
mm.MMap.Sync(gommap.MS_SYNC)
}
// BackgroundSync() schedules a sync to disk, but may return before it is done.
// Without a call to either BackgroundSync() or BlockUntilSync(), there
// is no guarantee that file has ever been written to disk at any point before
// the munmap() call that happens during Free(). See the man pages msync(2)
// and mmap(2) for details.
func (mm *MmapMalloc) BackgroundSync() {
mm.MMap.Sync(gommap.MS_ASYNC)
}
// Growmap grows a memory mapping
func Growmap(oldmap *MmapMalloc, newSize int64) (newmap *MmapMalloc, err error) {
if oldmap.Path == "" || oldmap.Fd <= 0 {
return nil, fmt.Errorf("oldmap must be mapping to " +
"actual file, so we can grow it")
}
if newSize <= oldmap.BytesAlloc || newSize <= oldmap.FileBytesLen {
return nil, fmt.Errorf("mapping in Growmap must be larger than before")
}
newmap = &MmapMalloc{}
newmap.Path = oldmap.Path
newmap.Fd = oldmap.Fd
// set to the size requested
err = syscall.Ftruncate(newmap.Fd, newSize)
if err != nil {
panic(err)
return nil, fmt.Errorf("syscall.Ftruncate to grow %v -> %v"+
" returned error: '%v'", oldmap.BytesAlloc, newSize, err)
}
newmap.FileBytesLen = newSize
newmap.BytesAlloc = newSize
prot := syscall.PROT_READ | syscall.PROT_WRITE
flags := syscall.MAP_SHARED
p("\n ------->> path = '%v', newmap.Fd = %v, with flags = %x, sz = %v, prot = '%v'\n", newmap.Path, newmap.Fd, flags, newSize, prot)
mmap, err := syscall.Mmap(newmap.Fd, 0, int(newSize), prot, flags)
if err != nil {
panic(err)
return nil, fmt.Errorf("syscall.Mmap returned error: '%v'", err)
}
// duplicate member to avoid casts all over the place.
newmap.MMap = mmap
newmap.Mem = mmap
return newmap, nil
}