Pre-allocate batchwise via mmap

This commit is contained in:
Lou Knauer 2022-07-25 15:16:25 +02:00
parent 9830f3c5d4
commit ac5f113ccb
4 changed files with 86 additions and 8 deletions

View File

@ -1,6 +1,8 @@
package memstore
import (
"errors"
"log"
"os"
"reflect"
"sync"
@ -16,20 +18,26 @@ const bufferSizeInBytes int = bufferSizeInFloats * 8
// The allocator rarely used, so a single big lock should be fine!
var allocatorLock sync.Mutex
var allocatorPool [][]byte
var allocatorBatch []byte
func RequestBytes(size int) []byte {
requested := size
size = (size + bufferSizeInBytes - 1) / bufferSizeInBytes * bufferSizeInBytes
if size == bufferSizeInBytes {
allocatorLock.Lock()
if len(allocatorPool) > 0 {
bytes := allocatorPool[len(allocatorPool)-1]
allocatorPool = allocatorPool[:len(allocatorPool)-1]
allocatorLock.Unlock()
return bytes
}
// Check allocation caches:
allocatorLock.Lock()
if len(allocatorPool) > 0 && size == bufferSizeInBytes {
bytes := allocatorPool[len(allocatorPool)-1]
allocatorPool = allocatorPool[:len(allocatorPool)-1]
allocatorLock.Unlock()
return bytes[:requested]
} else if cap(allocatorBatch) > size {
bytes := allocatorBatch[:0:size]
allocatorBatch = allocatorBatch[size:]
allocatorLock.Unlock()
return bytes[:requested]
}
allocatorLock.Unlock()
pagesize := os.Getpagesize()
if size < pagesize || size%pagesize != 0 {
@ -48,6 +56,35 @@ func RequestBytes(size int) []byte {
return bytes[:requested]
}
func FillAllocatorCache(estimate int) error {
size := (estimate + bufferSizeInBytes - 1) / bufferSizeInBytes * bufferSizeInBytes
pagesize := os.Getpagesize()
if size < pagesize || size%pagesize != 0 {
return errors.New("estimate to small of buffer size not page size compatible")
}
allocatorLock.Lock()
defer allocatorLock.Unlock()
if len(allocatorBatch) > 0 {
n := len(allocatorBatch) / bufferSizeInBytes
for i := 0; i < n; i++ {
chunk := allocatorBatch[i*bufferSizeInBytes : i*bufferSizeInBytes+bufferSizeInBytes]
allocatorPool = append(allocatorPool, chunk[0:0:bufferSizeInBytes])
}
allocatorBatch = nil
}
bytes, err := unix.Mmap(-1, 0, size, unix.PROT_READ|unix.PROT_WRITE, unix.MAP_ANONYMOUS|unix.MAP_SHARED)
if err != nil {
panic("unix.Mmap failed: " + err.Error())
}
log.Printf("batch-allocated %d bytes", cap(bytes))
allocatorBatch = bytes
return nil
}
func ReleaseBytes(bytes []byte) {
if cap(bytes)%bufferSizeInBytes != 0 {
panic("bytes released that do not match the buffer size constraints")

View File

@ -42,6 +42,7 @@ func (c *Checkpoint) Serialize(w io.Writer) error {
buf = encodeBytes(buf, c.Reserved)
buf = encodeUint64(buf, c.From)
buf = encodeUint64(buf, c.To)
buf = encodeUint64(buf, 0) // Estimate zero for non-streaming checkpoints
// The rest:
var err error
@ -129,6 +130,9 @@ func (c *Checkpoint) Deserialize(r *bufio.Reader) error {
if c.To, err = decodeUint64(buf, r); err != nil {
return err
}
if _, err = decodeUint64(buf, r); err != nil { // Estimate ignored here
return err
}
c.Root = &CheckpointLevel{}
return c.Root.deserialize(buf, r)

View File

@ -110,6 +110,35 @@ func (l *Level) free(t int64) (delme bool, n int) {
return len(l.metrics) == 0 && len(l.sublevels) == 0, n
}
// Return an estimate of how many values are stored for all metrics and sublevels between from and to.
func (l *Level) countValues(from int64, to int64) int {
vals := 0
l.lock.RLock()
defer l.lock.RUnlock()
for _, c := range l.metrics {
for c != nil {
if to < c.start || c.end() < from {
continue
} else if c.start < from {
vals += int((from - c.start) / c.frequency)
} else if from < c.start {
vals += int((c.start - from) / c.frequency)
} else {
vals += len(c.data)
}
c = c.prev
}
}
for _, sublevel := range l.sublevels {
vals += sublevel.countValues(from, to)
}
return vals
}
type MemoryStore struct {
root Level // root of the tree structure
// TODO...

View File

@ -17,6 +17,7 @@ func (ms *MemoryStore) SaveCheckpoint(from, to int64, w io.Writer) error {
buf = encodeBytes(buf, nil)
buf = encodeUint64(buf, uint64(from))
buf = encodeUint64(buf, uint64(to))
buf = encodeUint64(buf, uint64(ms.root.countValues(from, to)))
metricsbuf := make([]types.Float, 0, (to-from)/ms.MinFrequency()+1)
var err error
@ -122,6 +123,13 @@ func (ms *MemoryStore) LoadCheckpoint(r io.Reader) error {
if _, err := decodeUint64(buf, r); err != nil { // To
return err
}
if valueEstimate, err := decodeUint64(buf, r); err != nil {
return err
} else {
if err := FillAllocatorCache(int(valueEstimate) * 8); err != nil {
return nil
}
}
if err := ms.root.loadCheckpoint(ms, r, buf); err != nil {
return err