mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-11-21 14:44:00 +00:00
lib/{decimal,encoding}: optimize float64<->decimal conversion for arrays with zeros or ones
Time series with only zeros or ones frequently occur in monitoring, so it is worth optimizing their handling.
This commit is contained in:
parent
808fc0971f
commit
55d728c849
4 changed files with 370 additions and 0 deletions
|
@ -3,6 +3,8 @@ package decimal
|
|||
import (
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fastnum"
|
||||
)
|
||||
|
||||
// CalibrateScale calibrates a and b with the corresponding exponents ae, be
|
||||
|
@ -81,6 +83,13 @@ func ExtendInt64sCapacity(dst []int64, additionalItems int) []int64 {
|
|||
// AppendDecimalToFloat converts each item in va to f=v*10^e, appends it
|
||||
// to dst and returns the resulting dst.
|
||||
func AppendDecimalToFloat(dst []float64, va []int64, e int16) []float64 {
|
||||
if fastnum.IsInt64Zeros(va) {
|
||||
return fastnum.AppendFloat64Zeros(dst, len(va))
|
||||
}
|
||||
if e == 0 && fastnum.IsInt64Ones(va) {
|
||||
return fastnum.AppendFloat64Ones(dst, len(va))
|
||||
}
|
||||
|
||||
// Extend dst capacity in order to eliminate memory allocations below.
|
||||
dst = ExtendFloat64sCapacity(dst, len(va))
|
||||
|
||||
|
@ -108,6 +117,14 @@ func AppendFloatToDecimal(dst []int64, src []float64) (va []int64, e int16) {
|
|||
if len(src) == 0 {
|
||||
return dst, 0
|
||||
}
|
||||
if fastnum.IsFloat64Zeros(src) {
|
||||
dst = fastnum.AppendInt64Zeros(dst, len(src))
|
||||
return dst, 0
|
||||
}
|
||||
if fastnum.IsFloat64Ones(src) {
|
||||
dst = fastnum.AppendInt64Ones(dst, len(src))
|
||||
return dst, 0
|
||||
}
|
||||
|
||||
// Extend dst capacity in order to eliminate memory allocations below.
|
||||
dst = ExtendInt64sCapacity(dst, len(src))
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/bytesutil"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fastnum"
|
||||
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
|
||||
)
|
||||
|
||||
|
@ -201,6 +202,14 @@ func unmarshalInt64Array(dst []int64, src []byte, mt MarshalType, firstValue int
|
|||
if len(src) > 0 {
|
||||
return nil, fmt.Errorf("unexpected data left in const encoding: %d bytes", len(src))
|
||||
}
|
||||
if firstValue == 0 {
|
||||
dst = fastnum.AppendInt64Zeros(dst, itemsCount)
|
||||
return dst, nil
|
||||
}
|
||||
if firstValue == 1 {
|
||||
dst = fastnum.AppendInt64Ones(dst, itemsCount)
|
||||
return dst, nil
|
||||
}
|
||||
for itemsCount > 0 {
|
||||
dst = append(dst, firstValue)
|
||||
itemsCount--
|
||||
|
@ -267,6 +276,14 @@ func isConst(a []int64) bool {
|
|||
if len(a) == 0 {
|
||||
return false
|
||||
}
|
||||
if fastnum.IsInt64Zeros(a) {
|
||||
// Fast path for array containing only zeros.
|
||||
return true
|
||||
}
|
||||
if fastnum.IsInt64Ones(a) {
|
||||
// Fast path for array containing only ones.
|
||||
return true
|
||||
}
|
||||
v1 := a[0]
|
||||
for _, v := range a {
|
||||
if v != v1 {
|
||||
|
|
144
lib/fastnum/fastnum.go
Normal file
144
lib/fastnum/fastnum.go
Normal file
|
@ -0,0 +1,144 @@
|
|||
package fastnum
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// AppendInt64Zeros appends items zeros to dst and returns the result.
|
||||
//
|
||||
// It is faster than the corresponding loop.
|
||||
func AppendInt64Zeros(dst []int64, items int) []int64 {
|
||||
return appendInt64Data(dst, items, int64Zeros[:])
|
||||
}
|
||||
|
||||
// AppendInt64Ones appends items ones to dst and returns the result.
|
||||
//
|
||||
// It is faster than the correponding loop.
|
||||
func AppendInt64Ones(dst []int64, items int) []int64 {
|
||||
return appendInt64Data(dst, items, int64Ones[:])
|
||||
}
|
||||
|
||||
// AppendFloat64Zeros appends items zeros to dst and returns the result.
|
||||
//
|
||||
// It is faster than the corresponding loop.
|
||||
func AppendFloat64Zeros(dst []float64, items int) []float64 {
|
||||
return appendFloat64Data(dst, items, float64Zeros[:])
|
||||
}
|
||||
|
||||
// AppendFloat64Ones appends items ones to dst and returns the result.
|
||||
//
|
||||
// It is faster than the corresponding loop.
|
||||
func AppendFloat64Ones(dst []float64, items int) []float64 {
|
||||
return appendFloat64Data(dst, items, float64Ones[:])
|
||||
}
|
||||
|
||||
// IsInt64Zeros checks whether a contains only zeros.
|
||||
func IsInt64Zeros(a []int64) bool {
|
||||
return isInt64Data(a, int64Zeros[:])
|
||||
}
|
||||
|
||||
// IsInt64Ones checks whether a contains only ones.
|
||||
func IsInt64Ones(a []int64) bool {
|
||||
return isInt64Data(a, int64Ones[:])
|
||||
}
|
||||
|
||||
// IsFloat64Zeros checks whether a contains only zeros.
|
||||
func IsFloat64Zeros(a []float64) bool {
|
||||
return isFloat64Data(a, float64Zeros[:])
|
||||
}
|
||||
|
||||
// IsFloat64Ones checks whether a contains only ones.
|
||||
func IsFloat64Ones(a []float64) bool {
|
||||
return isFloat64Data(a, float64Ones[:])
|
||||
}
|
||||
|
||||
func appendInt64Data(dst []int64, items int, src []int64) []int64 {
|
||||
for items > 0 {
|
||||
n := len(src)
|
||||
if n > items {
|
||||
n = items
|
||||
}
|
||||
dst = append(dst, src[:n]...)
|
||||
items -= n
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func appendFloat64Data(dst []float64, items int, src []float64) []float64 {
|
||||
for items > 0 {
|
||||
n := len(src)
|
||||
if n > items {
|
||||
n = items
|
||||
}
|
||||
dst = append(dst, src[:n]...)
|
||||
items -= n
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func isInt64Data(a, data []int64) bool {
|
||||
if len(a) == 0 {
|
||||
return true
|
||||
}
|
||||
if len(data) != 8*1024 {
|
||||
panic("len(data) must equal to 8*1024")
|
||||
}
|
||||
b := (*[64 * 1024]byte)(unsafe.Pointer(&data[0]))
|
||||
for len(a) > 0 {
|
||||
n := len(data)
|
||||
if n > len(a) {
|
||||
n = len(a)
|
||||
}
|
||||
x := a[:n]
|
||||
a = a[n:]
|
||||
xb := (*[64 * 1024]byte)(unsafe.Pointer(&x[0]))
|
||||
xbLen := len(x) * 8
|
||||
if !bytes.Equal(xb[:xbLen], b[:xbLen]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isFloat64Data(a, data []float64) bool {
|
||||
if len(a) == 0 {
|
||||
return true
|
||||
}
|
||||
if len(data) != 8*1024 {
|
||||
panic("len(data) must equal to 8*1024")
|
||||
}
|
||||
b := (*[64 * 1024]byte)(unsafe.Pointer(&data[0]))
|
||||
for len(a) > 0 {
|
||||
n := len(data)
|
||||
if n > len(a) {
|
||||
n = len(a)
|
||||
}
|
||||
x := a[:n]
|
||||
a = a[n:]
|
||||
xb := (*[64 * 1024]byte)(unsafe.Pointer(&x[0]))
|
||||
xbLen := len(x) * 8
|
||||
if !bytes.Equal(xb[:xbLen], b[:xbLen]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var (
|
||||
int64Zeros [8 * 1024]int64
|
||||
int64Ones = func() (a [8 * 1024]int64) {
|
||||
for i := 0; i < len(a); i++ {
|
||||
a[i] = 1
|
||||
}
|
||||
return a
|
||||
}()
|
||||
|
||||
float64Zeros [8 * 1024]float64
|
||||
float64Ones = func() (a [8 * 1024]float64) {
|
||||
for i := 0; i < len(a); i++ {
|
||||
a[i] = 1
|
||||
}
|
||||
return a
|
||||
}()
|
||||
)
|
192
lib/fastnum/fastnum_test.go
Normal file
192
lib/fastnum/fastnum_test.go
Normal file
|
@ -0,0 +1,192 @@
|
|||
package fastnum
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIsInt64Zeros(t *testing.T) {
|
||||
for _, n := range []int{0, 1, 10, 100, 1000, 1e4, 1e5, 8*1024 + 1} {
|
||||
t.Run(fmt.Sprintf("%d_items", n), func(t *testing.T) {
|
||||
a := make([]int64, n)
|
||||
if !IsInt64Zeros(a) {
|
||||
t.Fatalf("IsInt64Zeros must return true")
|
||||
}
|
||||
if len(a) > 0 {
|
||||
a[len(a)-1] = 1
|
||||
if IsInt64Zeros(a) {
|
||||
t.Fatalf("IsInt64Zeros must return false")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsInt64Ones(t *testing.T) {
|
||||
for _, n := range []int{0, 1, 10, 100, 1000, 1e4, 1e5, 8*1024 + 1} {
|
||||
t.Run(fmt.Sprintf("%d_items", n), func(t *testing.T) {
|
||||
a := make([]int64, n)
|
||||
for i := 0; i < n; i++ {
|
||||
a[i] = 1
|
||||
}
|
||||
if !IsInt64Ones(a) {
|
||||
t.Fatalf("IsInt64Ones must return true")
|
||||
}
|
||||
if len(a) > 0 {
|
||||
a[len(a)-1] = 0
|
||||
if IsInt64Ones(a) {
|
||||
t.Fatalf("IsInt64Ones must return false")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsFloat64Zeros(t *testing.T) {
|
||||
for _, n := range []int{0, 1, 10, 100, 1000, 1e4, 1e5, 8*1024 + 1} {
|
||||
t.Run(fmt.Sprintf("%d_items", n), func(t *testing.T) {
|
||||
a := make([]float64, n)
|
||||
if !IsFloat64Zeros(a) {
|
||||
t.Fatalf("IsInt64Zeros must return true")
|
||||
}
|
||||
if len(a) > 0 {
|
||||
a[len(a)-1] = 1
|
||||
if IsFloat64Zeros(a) {
|
||||
t.Fatalf("IsInt64Zeros must return false")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsFloat64Ones(t *testing.T) {
|
||||
for _, n := range []int{0, 1, 10, 100, 1000, 1e4, 1e5, 8*1024 + 1} {
|
||||
t.Run(fmt.Sprintf("%d_items", n), func(t *testing.T) {
|
||||
a := make([]float64, n)
|
||||
for i := 0; i < n; i++ {
|
||||
a[i] = 1
|
||||
}
|
||||
if !IsFloat64Ones(a) {
|
||||
t.Fatalf("IsInt64Ones must return true")
|
||||
}
|
||||
if len(a) > 0 {
|
||||
a[len(a)-1] = 0
|
||||
if IsFloat64Ones(a) {
|
||||
t.Fatalf("IsInt64Ones must return false")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendInt64Zeros(t *testing.T) {
|
||||
for _, n := range []int{0, 1, 10, 100, 1000, 1e4, 1e5, 8*1024 + 1} {
|
||||
t.Run(fmt.Sprintf("%d_items", n), func(t *testing.T) {
|
||||
a := AppendInt64Zeros(nil, n)
|
||||
if len(a) != n {
|
||||
t.Fatalf("unexpected len(a); got %d; want %d", len(a), n)
|
||||
}
|
||||
if !IsInt64Zeros(a) {
|
||||
t.Fatalf("IsInt64Zeros must return true")
|
||||
}
|
||||
|
||||
prefix := []int64{1, 2, 3}
|
||||
a = AppendInt64Zeros(prefix, n)
|
||||
if len(a) != len(prefix)+n {
|
||||
t.Fatalf("unexpected len(a) with prefix; got %d; want %d", len(a), len(prefix)+n)
|
||||
}
|
||||
for i := 0; i < len(prefix); i++ {
|
||||
if a[i] != prefix[i] {
|
||||
t.Fatalf("unexpected prefix[%d]; got %d; want %d", i, a[i], prefix[i])
|
||||
}
|
||||
}
|
||||
if !IsInt64Zeros(a[len(prefix):]) {
|
||||
t.Fatalf("IsInt64Zeros for prefixed a must return true")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendInt64Ones(t *testing.T) {
|
||||
for _, n := range []int{0, 1, 10, 100, 1000, 1e4, 1e5, 8*1024 + 1} {
|
||||
t.Run(fmt.Sprintf("%d_items", n), func(t *testing.T) {
|
||||
a := AppendInt64Ones(nil, n)
|
||||
if len(a) != n {
|
||||
t.Fatalf("unexpected len(a); got %d; want %d", len(a), n)
|
||||
}
|
||||
if !IsInt64Ones(a) {
|
||||
t.Fatalf("IsInt64Ones must return true")
|
||||
}
|
||||
|
||||
prefix := []int64{1, 2, 3}
|
||||
a = AppendInt64Ones(prefix, n)
|
||||
if len(a) != len(prefix)+n {
|
||||
t.Fatalf("unexpected len(a) with prefix; got %d; want %d", len(a), len(prefix)+n)
|
||||
}
|
||||
for i := 0; i < len(prefix); i++ {
|
||||
if a[i] != prefix[i] {
|
||||
t.Fatalf("unexpected prefix[%d]; got %d; want %d", i, a[i], prefix[i])
|
||||
}
|
||||
}
|
||||
if !IsInt64Ones(a[len(prefix):]) {
|
||||
t.Fatalf("IsInt64Ones for prefixed a must return true")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendFloat64Zeros(t *testing.T) {
|
||||
for _, n := range []int{0, 1, 10, 100, 1000, 1e4, 1e5, 8*1024 + 1} {
|
||||
t.Run(fmt.Sprintf("%d_items", n), func(t *testing.T) {
|
||||
a := AppendFloat64Zeros(nil, n)
|
||||
if len(a) != n {
|
||||
t.Fatalf("unexpected len(a); got %d; want %d", len(a), n)
|
||||
}
|
||||
if !IsFloat64Zeros(a) {
|
||||
t.Fatalf("IsFloat64Zeros must return true")
|
||||
}
|
||||
|
||||
prefix := []float64{1, 2, 3}
|
||||
a = AppendFloat64Zeros(prefix, n)
|
||||
if len(a) != len(prefix)+n {
|
||||
t.Fatalf("unexpected len(a) with prefix; got %d; want %d", len(a), len(prefix)+n)
|
||||
}
|
||||
for i := 0; i < len(prefix); i++ {
|
||||
if a[i] != prefix[i] {
|
||||
t.Fatalf("unexpected prefix[%d]; got %f; want %f", i, a[i], prefix[i])
|
||||
}
|
||||
}
|
||||
if !IsFloat64Zeros(a[len(prefix):]) {
|
||||
t.Fatalf("IsFloat64Zeros for prefixed a must return true")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendFloat64Ones(t *testing.T) {
|
||||
for _, n := range []int{0, 1, 10, 100, 1000, 1e4, 1e5, 8*1024 + 1} {
|
||||
t.Run(fmt.Sprintf("%d_items", n), func(t *testing.T) {
|
||||
a := AppendFloat64Ones(nil, n)
|
||||
if len(a) != n {
|
||||
t.Fatalf("unexpected len(a); got %d; want %d", len(a), n)
|
||||
}
|
||||
if !IsFloat64Ones(a) {
|
||||
t.Fatalf("IsFloat64Ones must return true")
|
||||
}
|
||||
|
||||
prefix := []float64{1, 2, 3}
|
||||
a = AppendFloat64Ones(prefix, n)
|
||||
if len(a) != len(prefix)+n {
|
||||
t.Fatalf("unexpected len(a) with prefix; got %d; want %d", len(a), len(prefix)+n)
|
||||
}
|
||||
for i := 0; i < len(prefix); i++ {
|
||||
if a[i] != prefix[i] {
|
||||
t.Fatalf("unexpected prefix[%d]; got %f; want %f", i, a[i], prefix[i])
|
||||
}
|
||||
}
|
||||
if !IsFloat64Ones(a[len(prefix):]) {
|
||||
t.Fatalf("IsFloat64Ones for prefixed a must return true")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue