mirror of
https://github.com/ClusterCockpit/cc-metric-store.git
synced 2024-11-10 05:07:25 +01:00
Pre-allocate batchwise via mmap
This commit is contained in:
parent
9830f3c5d4
commit
ac5f113ccb
@ -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")
|
||||
|
@ -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)
|
||||
|
@ -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...
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user