mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-19 08:40:42 +00:00
Make accurate Quantity->float64 be a new method
This leaves the old method alone, since the performance difference is so stark. ``` $ go test ./staging/src/k8s.io/apimachinery/pkg/api/resource/ -bench=Float64 goos: linux goarch: amd64 pkg: k8s.io/apimachinery/pkg/api/resource cpu: Intel(R) Xeon(R) W-2135 CPU @ 3.70GHz BenchmarkQuantityAsApproximateFloat64-6 95865672 11.44 ns/op BenchmarkQuantityAsFloat64Slow-6 2800825 430.2 ns/op PASS ok k8s.io/apimachinery/pkg/api/resource 2.786s ```
This commit is contained in:
parent
75ba5a9651
commit
595dc5155b
@ -20,6 +20,7 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
math "math"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -459,10 +460,32 @@ func (q *Quantity) CanonicalizeBytes(out []byte) (result, suffix []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
// AsApproximateFloat64 returns a float64 representation of the quantity which may
|
||||
// lose precision. If the value of the quantity is outside the range of a float64
|
||||
// +Inf/-Inf will be returned.
|
||||
// AsApproximateFloat64 returns a float64 representation of the quantity which
|
||||
// may lose precision. If precision matter more than performance, see
|
||||
// AsFloat64Slow. If the value of the quantity is outside the range of a
|
||||
// float64 +Inf/-Inf will be returned.
|
||||
func (q *Quantity) AsApproximateFloat64() float64 {
|
||||
var base float64
|
||||
var exponent int
|
||||
if q.d.Dec != nil {
|
||||
base, _ = big.NewFloat(0).SetInt(q.d.Dec.UnscaledBig()).Float64()
|
||||
exponent = int(-q.d.Dec.Scale())
|
||||
} else {
|
||||
base = float64(q.i.value)
|
||||
exponent = int(q.i.scale)
|
||||
}
|
||||
if exponent == 0 {
|
||||
return base
|
||||
}
|
||||
|
||||
return base * math.Pow10(exponent)
|
||||
}
|
||||
|
||||
// AsFloat64Slow returns a float64 representation of the quantity. This is
|
||||
// more precise than AsApproximateFloat64 but significantly slower. If the
|
||||
// value of the quantity is outside the range of a float64 +Inf/-Inf will be
|
||||
// returned.
|
||||
func (q *Quantity) AsFloat64Slow() float64 {
|
||||
infDec := q.AsDec()
|
||||
|
||||
var absScale int64
|
||||
|
@ -1294,6 +1294,78 @@ func TestNegateRoundTrip(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestQuantityAsApproximateFloat64(t *testing.T) {
|
||||
// NOTE: this table should be kept in sync with TestQuantityAsFloat64Slow
|
||||
table := []struct {
|
||||
in Quantity
|
||||
out float64
|
||||
}{
|
||||
{decQuantity(0, 0, DecimalSI), 0.0},
|
||||
{decQuantity(0, 0, DecimalExponent), 0.0},
|
||||
{decQuantity(0, 0, BinarySI), 0.0},
|
||||
|
||||
{decQuantity(1, 0, DecimalSI), 1},
|
||||
{decQuantity(1, 0, DecimalExponent), 1},
|
||||
{decQuantity(1, 0, BinarySI), 1},
|
||||
|
||||
// Binary suffixes
|
||||
{decQuantity(1024, 0, BinarySI), 1024},
|
||||
{decQuantity(8*1024, 0, BinarySI), 8 * 1024},
|
||||
{decQuantity(7*1024*1024, 0, BinarySI), 7 * 1024 * 1024},
|
||||
{decQuantity(7*1024*1024, 1, BinarySI), (7 * 1024 * 1024) * 10},
|
||||
{decQuantity(7*1024*1024, 4, BinarySI), (7 * 1024 * 1024) * 10000},
|
||||
{decQuantity(7*1024*1024, 8, BinarySI), (7 * 1024 * 1024) * 100000000},
|
||||
{decQuantity(7*1024*1024, -1, BinarySI), (7 * 1024 * 1024) * math.Pow10(-1)}, // '* Pow10' and '/ float(10)' do not round the same way
|
||||
{decQuantity(7*1024*1024, -8, BinarySI), (7 * 1024 * 1024) / float64(100000000)},
|
||||
|
||||
{decQuantity(1024, 0, DecimalSI), 1024},
|
||||
{decQuantity(8*1024, 0, DecimalSI), 8 * 1024},
|
||||
{decQuantity(7*1024*1024, 0, DecimalSI), 7 * 1024 * 1024},
|
||||
{decQuantity(7*1024*1024, 1, DecimalSI), (7 * 1024 * 1024) * 10},
|
||||
{decQuantity(7*1024*1024, 4, DecimalSI), (7 * 1024 * 1024) * 10000},
|
||||
{decQuantity(7*1024*1024, 8, DecimalSI), (7 * 1024 * 1024) * 100000000},
|
||||
{decQuantity(7*1024*1024, -1, DecimalSI), (7 * 1024 * 1024) * math.Pow10(-1)}, // '* Pow10' and '/ float(10)' do not round the same way
|
||||
{decQuantity(7*1024*1024, -8, DecimalSI), (7 * 1024 * 1024) / float64(100000000)},
|
||||
|
||||
{decQuantity(1024, 0, DecimalExponent), 1024},
|
||||
{decQuantity(8*1024, 0, DecimalExponent), 8 * 1024},
|
||||
{decQuantity(7*1024*1024, 0, DecimalExponent), 7 * 1024 * 1024},
|
||||
{decQuantity(7*1024*1024, 1, DecimalExponent), (7 * 1024 * 1024) * 10},
|
||||
{decQuantity(7*1024*1024, 4, DecimalExponent), (7 * 1024 * 1024) * 10000},
|
||||
{decQuantity(7*1024*1024, 8, DecimalExponent), (7 * 1024 * 1024) * 100000000},
|
||||
{decQuantity(7*1024*1024, -1, DecimalExponent), (7 * 1024 * 1024) * math.Pow10(-1)}, // '* Pow10' and '/ float(10)' do not round the same way
|
||||
{decQuantity(7*1024*1024, -8, DecimalExponent), (7 * 1024 * 1024) / float64(100000000)},
|
||||
|
||||
// very large numbers
|
||||
{Quantity{d: maxAllowed, Format: DecimalSI}, math.MaxInt64},
|
||||
{Quantity{d: maxAllowed, Format: BinarySI}, math.MaxInt64},
|
||||
{decQuantity(12, 18, DecimalSI), 1.2e19},
|
||||
|
||||
// infinities caused due to float64 overflow
|
||||
{decQuantity(12, 500, DecimalSI), math.Inf(0)},
|
||||
{decQuantity(-12, 500, DecimalSI), math.Inf(-1)},
|
||||
}
|
||||
|
||||
for i, item := range table {
|
||||
t.Run(fmt.Sprintf("%s %s", item.in.Format, item.in.String()), func(t *testing.T) {
|
||||
out := item.in.AsApproximateFloat64()
|
||||
if out != item.out {
|
||||
t.Fatalf("test %d expected %v, got %v", i+1, item.out, out)
|
||||
}
|
||||
if item.in.d.Dec != nil {
|
||||
if i, ok := item.in.AsInt64(); ok {
|
||||
q := intQuantity(i, 0, item.in.Format)
|
||||
out := q.AsApproximateFloat64()
|
||||
if out != item.out {
|
||||
t.Fatalf("as int quantity: expected %v, got %v", item.out, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuantityAsFloat64Slow(t *testing.T) {
|
||||
// NOTE: this table should be kept in sync with TestQuantityAsApproximateFloat64
|
||||
table := []struct {
|
||||
in Quantity
|
||||
out float64
|
||||
@ -1346,14 +1418,14 @@ func TestQuantityAsApproximateFloat64(t *testing.T) {
|
||||
|
||||
for i, item := range table {
|
||||
t.Run(fmt.Sprintf("%s %s", item.in.Format, item.in.String()), func(t *testing.T) {
|
||||
out := item.in.AsApproximateFloat64()
|
||||
out := item.in.AsFloat64Slow()
|
||||
if out != item.out {
|
||||
t.Fatalf("test %d expected %v, got %v", i+1, item.out, out)
|
||||
}
|
||||
if item.in.d.Dec != nil {
|
||||
if i, ok := item.in.AsInt64(); ok {
|
||||
q := intQuantity(i, 0, item.in.Format)
|
||||
out := q.AsApproximateFloat64()
|
||||
out := q.AsFloat64Slow()
|
||||
if out != item.out {
|
||||
t.Fatalf("as int quantity: expected %v, got %v", item.out, out)
|
||||
}
|
||||
@ -1397,6 +1469,40 @@ func TestStringQuantityAsApproximateFloat64(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringQuantityAsFloat64Slow(t *testing.T) {
|
||||
table := []struct {
|
||||
in string
|
||||
out float64
|
||||
}{
|
||||
{"2Ki", 2048},
|
||||
{"1.1Ki", 1126.4e+0},
|
||||
{"1Mi", 1.048576e+06},
|
||||
{"2Gi", 2.147483648e+09},
|
||||
}
|
||||
|
||||
for _, item := range table {
|
||||
t.Run(item.in, func(t *testing.T) {
|
||||
in, err := ParseQuantity(item.in)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out := in.AsFloat64Slow()
|
||||
if out != item.out {
|
||||
t.Fatalf("expected %v, got %v", item.out, out)
|
||||
}
|
||||
if in.d.Dec != nil {
|
||||
if i, ok := in.AsInt64(); ok {
|
||||
q := intQuantity(i, 0, in.Format)
|
||||
out := q.AsFloat64Slow()
|
||||
if out != item.out {
|
||||
t.Fatalf("as int quantity: expected %v, got %v", item.out, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkQuantities() []Quantity {
|
||||
return []Quantity{
|
||||
intQuantity(1024*1024*1024, 0, BinarySI),
|
||||
@ -1579,6 +1685,18 @@ func BenchmarkQuantityAsApproximateFloat64(b *testing.B) {
|
||||
b.StopTimer()
|
||||
}
|
||||
|
||||
func BenchmarkQuantityAsFloat64Slow(b *testing.B) {
|
||||
values := benchmarkQuantities()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
q := values[i%len(values)]
|
||||
if q.AsFloat64Slow() == -1 {
|
||||
b.Fatal(q)
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
||||
|
||||
var _ pflag.Value = &QuantityValue{}
|
||||
|
||||
func TestQuantityValueSet(t *testing.T) {
|
||||
|
Loading…
Reference in New Issue
Block a user