Round should avoid clearing s, save a string

Instead of saving bytes, save a string, which makes String() faster
and does not unduly penalize marshal. During parse, save the string
if it is in canonical form.
pull/6/head
Clayton Coleman 2016-05-21 11:38:47 -04:00
parent 1ef1906209
commit b1310216bf
No known key found for this signature in database
GPG Key ID: 3D16906B4F1C5CB3
4 changed files with 133 additions and 93 deletions

View File

@ -1,5 +1,3 @@
// +build !ignore_autogenerated
/* /*
Copyright 2016 The Kubernetes Authors All rights reserved. Copyright 2016 The Kubernetes Authors All rights reserved.
@ -16,32 +14,19 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
// This file was autogenerated by deepcopy-gen. Do not edit it manually!
package resource package resource
import ( import (
inf "gopkg.in/inf.v0"
conversion "k8s.io/kubernetes/pkg/conversion" conversion "k8s.io/kubernetes/pkg/conversion"
) )
func DeepCopy_resource_Quantity(in Quantity, out *Quantity, c *conversion.Cloner) error { func DeepCopy_resource_Quantity(in Quantity, out *Quantity, c *conversion.Cloner) error {
if newVal, err := c.DeepCopy(in.i); err != nil { *out = in
return err if in.d.Dec != nil {
} else { tmp := &inf.Dec{}
out.i = newVal.(int64Amount) out.d.Dec = tmp.Set(in.d.Dec)
} }
if newVal, err := c.DeepCopy(in.d); err != nil {
return err
} else {
out.d = newVal.(infDecAmount)
}
if in.s != nil {
in, out := in.s, &out.s
*out = make([]byte, len(in))
copy(*out, in)
} else {
out.s = nil
}
out.Format = in.Format
return nil return nil
} }

View File

@ -97,7 +97,7 @@ type Quantity struct {
// d is the quantity in inf.Dec form if d.Dec != nil // d is the quantity in inf.Dec form if d.Dec != nil
d infDecAmount d infDecAmount
// s is the generated value of this quantity to avoid recalculation // s is the generated value of this quantity to avoid recalculation
s []byte s string
// Change Format at will. See the comment for Canonicalize for // Change Format at will. See the comment for Canonicalize for
// more details. // more details.
@ -275,7 +275,7 @@ func ParseQuantity(str string) (Quantity, error) {
return Quantity{}, ErrFormatWrong return Quantity{}, ErrFormatWrong
} }
if str == "0" { if str == "0" {
return Quantity{Format: DecimalSI}, nil return Quantity{Format: DecimalSI, s: str}, nil
} }
positive, value, num, denom, suf, err := parseQuantityString(str) positive, value, num, denom, suf, err := parseQuantityString(str)
@ -324,6 +324,17 @@ func ParseQuantity(str string) (Quantity, error) {
if !positive { if !positive {
result = -result result = -result
} }
// if the number is in canonical form, reuse the string
switch format {
case BinarySI:
if exponent%10 == 0 && (value&0x07 != 0) {
return Quantity{i: int64Amount{value: result, scale: Scale(scale)}, Format: format, s: str}, nil
}
default:
if scale%3 == 0 && !strings.HasSuffix(shifted, "000") && shifted[0] != '0' {
return Quantity{i: int64Amount{value: result, scale: Scale(scale)}, Format: format, s: str}, nil
}
}
return Quantity{i: int64Amount{value: result, scale: Scale(scale)}, Format: format}, nil return Quantity{i: int64Amount{value: result, scale: Scale(scale)}, Format: format}, nil
} }
} }
@ -490,10 +501,16 @@ func (q *Quantity) AsScale(scale Scale) (CanonicalValue, bool) {
// Negative numbers are rounded away from zero (-9 scale 1 rounds to -10). // Negative numbers are rounded away from zero (-9 scale 1 rounds to -10).
func (q *Quantity) RoundUp(scale Scale) bool { func (q *Quantity) RoundUp(scale Scale) bool {
if q.d.Dec != nil { if q.d.Dec != nil {
q.s = ""
d, exact := q.d.AsScale(scale) d, exact := q.d.AsScale(scale)
q.d = d q.d = d
return exact return exact
} }
// avoid clearing the string value if we have already calculated it
if q.i.scale >= scale {
return true
}
q.s = ""
i, exact := q.i.AsScale(scale) i, exact := q.i.AsScale(scale)
q.i = i q.i = i
return exact return exact
@ -502,7 +519,7 @@ func (q *Quantity) RoundUp(scale Scale) bool {
// Add adds the provide y quantity to the current value. If the current value is zero, // Add adds the provide y quantity to the current value. If the current value is zero,
// the format of the quantity will be updated to the format of y. // the format of the quantity will be updated to the format of y.
func (q *Quantity) Add(y Quantity) { func (q *Quantity) Add(y Quantity) {
q.s = nil q.s = ""
if q.d.Dec == nil && y.d.Dec == nil { if q.d.Dec == nil && y.d.Dec == nil {
if q.i.value == 0 { if q.i.value == 0 {
q.Format = y.Format q.Format = y.Format
@ -519,7 +536,7 @@ func (q *Quantity) Add(y Quantity) {
// Sub subtracts the provided quantity from the current value in place. If the current // Sub subtracts the provided quantity from the current value in place. If the current
// value is zero, the format of the quantity will be updated to the format of y. // value is zero, the format of the quantity will be updated to the format of y.
func (q *Quantity) Sub(y Quantity) { func (q *Quantity) Sub(y Quantity) {
q.s = nil q.s = ""
if q.IsZero() { if q.IsZero() {
q.Format = y.Format q.Format = y.Format
} }
@ -549,7 +566,7 @@ func (q *Quantity) CmpInt64(y int64) int {
// Neg sets quantity to be the negative value of itself. // Neg sets quantity to be the negative value of itself.
func (q *Quantity) Neg() { func (q *Quantity) Neg() {
q.s = nil q.s = ""
if q.d.Dec == nil { if q.d.Dec == nil {
q.i.value = -q.i.value q.i.value = -q.i.value
return return
@ -557,31 +574,26 @@ func (q *Quantity) Neg() {
q.d.Dec.Neg(q.d.Dec) q.d.Dec.Neg(q.d.Dec)
} }
// toBytes ensures q.s is set to a byte slice representing the canonical string form of this
// quantity and then returns the value. CanonicalizeBytes is an expensive operation, and caching
// this result significantly reduces the cost of normal parse / marshal operations on Quantity.
func (q *Quantity) toBytes() []byte {
if q.s == nil {
result := make([]byte, 0, int64QuantityExpectedBytes)
number, suffix := q.CanonicalizeBytes(result)
number = append(number, suffix...)
q.s = number
}
return q.s
}
// int64QuantityExpectedBytes is the expected width in bytes of the canonical string representation // int64QuantityExpectedBytes is the expected width in bytes of the canonical string representation
// of most Quantity values. // of most Quantity values.
const int64QuantityExpectedBytes = 18 const int64QuantityExpectedBytes = 18
// String formats the Quantity as a string. // String formats the Quantity as a string, caching the result if not calculated.
// String is an expensive operation and caching this result significantly reduces the cost of
// normal parse / marshal operations on Quantity.
func (q *Quantity) String() string { func (q *Quantity) String() string {
return string(q.toBytes()) if len(q.s) == 0 {
result := make([]byte, 0, int64QuantityExpectedBytes)
number, suffix := q.CanonicalizeBytes(result)
number = append(number, suffix...)
q.s = string(number)
}
return q.s
} }
// MarshalJSON implements the json.Marshaller interface. // MarshalJSON implements the json.Marshaller interface.
func (q Quantity) MarshalJSON() ([]byte, error) { func (q Quantity) MarshalJSON() ([]byte, error) {
if q.s != nil { if len(q.s) > 0 {
out := make([]byte, len(q.s)+2) out := make([]byte, len(q.s)+2)
out[0], out[len(out)-1] = '"', '"' out[0], out[len(out)-1] = '"', '"'
copy(out[1:], q.s) copy(out[1:], q.s)
@ -620,11 +632,12 @@ func (q *Quantity) UnmarshalJSON(value []byte) error {
if value[0] == '"' && value[l-1] == '"' { if value[0] == '"' && value[l-1] == '"' {
value = value[1 : l-1] value = value[1 : l-1]
} }
parsed, err := ParseQuantity(string(value)) parsed, err := ParseQuantity(string(value))
if err != nil { if err != nil {
return err return err
} }
parsed.s = value
// This copy is safe because parsed will not be referred to again. // This copy is safe because parsed will not be referred to again.
*q = parsed *q = parsed
return nil return nil
@ -693,7 +706,7 @@ func (q *Quantity) SetMilli(value int64) {
// SetScaled sets q's value to be value * 10^scale // SetScaled sets q's value to be value * 10^scale
func (q *Quantity) SetScaled(value int64, scale Scale) { func (q *Quantity) SetScaled(value int64, scale Scale) {
q.s = nil q.s = ""
q.d.Dec = nil q.d.Dec = nil
q.i = int64Amount{value: value, scale: scale} q.i = int64Amount{value: value, scale: scale}
} }

View File

@ -46,7 +46,7 @@ func (m *Quantity) MarshalTo(data []byte) (int, error) {
data[i] = 0xa data[i] = 0xa
i++ i++
// BEGIN CUSTOM MARSHAL // BEGIN CUSTOM MARSHAL
out := m.toBytes() out := m.String()
i = encodeVarintGenerated(data, i, uint64(len(out))) i = encodeVarintGenerated(data, i, uint64(len(out)))
i += copy(data[i:], out) i += copy(data[i:], out)
// END CUSTOM MARSHAL // END CUSTOM MARSHAL
@ -69,7 +69,7 @@ func (m *Quantity) Size() (n int) {
_ = l _ = l
// BEGIN CUSTOM SIZE // BEGIN CUSTOM SIZE
l = len(m.toBytes()) l = len(m.String())
// END CUSTOM SIZE // END CUSTOM SIZE
n += 1 + l + sovGenerated(uint64(l)) n += 1 + l + sovGenerated(uint64(l))

View File

@ -625,55 +625,80 @@ func TestQuantityString(t *testing.T) {
table := []struct { table := []struct {
in Quantity in Quantity
expect string expect string
alternate string
}{ }{
{decQuantity(1024*1024*1024, 0, BinarySI), "1Gi"}, {decQuantity(1024*1024*1024, 0, BinarySI), "1Gi", "1024Mi"},
{decQuantity(300*1024*1024, 0, BinarySI), "300Mi"}, {decQuantity(300*1024*1024, 0, BinarySI), "300Mi", "307200Ki"},
{decQuantity(6*1024, 0, BinarySI), "6Ki"}, {decQuantity(6*1024, 0, BinarySI), "6Ki", ""},
{decQuantity(1001*1024*1024*1024, 0, BinarySI), "1001Gi"}, {decQuantity(1001*1024*1024*1024, 0, BinarySI), "1001Gi", "1025024Mi"},
{decQuantity(1024*1024*1024*1024, 0, BinarySI), "1Ti"}, {decQuantity(1024*1024*1024*1024, 0, BinarySI), "1Ti", "1024Gi"},
{decQuantity(5, 0, BinarySI), "5"}, {decQuantity(5, 0, BinarySI), "5", "5000m"},
{decQuantity(500, -3, BinarySI), "500m"}, {decQuantity(500, -3, BinarySI), "500m", "0.5"},
{decQuantity(1, 9, DecimalSI), "1G"}, {decQuantity(1, 9, DecimalSI), "1G", "1000M"},
{decQuantity(1000, 6, DecimalSI), "1G"}, {decQuantity(1000, 6, DecimalSI), "1G", "0.001T"},
{decQuantity(1000000, 3, DecimalSI), "1G"}, {decQuantity(1000000, 3, DecimalSI), "1G", ""},
{decQuantity(1000000000, 0, DecimalSI), "1G"}, {decQuantity(1000000000, 0, DecimalSI), "1G", ""},
{decQuantity(1, -3, DecimalSI), "1m"}, {decQuantity(1, -3, DecimalSI), "1m", "1000u"},
{decQuantity(80, -3, DecimalSI), "80m"}, {decQuantity(80, -3, DecimalSI), "80m", ""},
{decQuantity(1080, -3, DecimalSI), "1080m"}, {decQuantity(1080, -3, DecimalSI), "1080m", "1.08"},
{decQuantity(108, -2, DecimalSI), "1080m"}, {decQuantity(108, -2, DecimalSI), "1080m", "1080000000n"},
{decQuantity(10800, -4, DecimalSI), "1080m"}, {decQuantity(10800, -4, DecimalSI), "1080m", ""},
{decQuantity(300, 6, DecimalSI), "300M"}, {decQuantity(300, 6, DecimalSI), "300M", ""},
{decQuantity(1, 12, DecimalSI), "1T"}, {decQuantity(1, 12, DecimalSI), "1T", ""},
{decQuantity(1234567, 6, DecimalSI), "1234567M"}, {decQuantity(1234567, 6, DecimalSI), "1234567M", ""},
{decQuantity(1234567, -3, BinarySI), "1234567m"}, {decQuantity(1234567, -3, BinarySI), "1234567m", ""},
{decQuantity(3, 3, DecimalSI), "3k"}, {decQuantity(3, 3, DecimalSI), "3k", ""},
{decQuantity(1025, 0, BinarySI), "1025"}, {decQuantity(1025, 0, BinarySI), "1025", ""},
{decQuantity(0, 0, DecimalSI), "0"}, {decQuantity(0, 0, DecimalSI), "0", ""},
{decQuantity(0, 0, BinarySI), "0"}, {decQuantity(0, 0, BinarySI), "0", ""},
{decQuantity(1, 9, DecimalExponent), "1e9"}, {decQuantity(1, 9, DecimalExponent), "1e9", ".001e12"},
{decQuantity(1, -3, DecimalExponent), "1e-3"}, {decQuantity(1, -3, DecimalExponent), "1e-3", "0.001e0"},
{decQuantity(1, -9, DecimalExponent), "1e-9"}, {decQuantity(1, -9, DecimalExponent), "1e-9", "1000e-12"},
{decQuantity(80, -3, DecimalExponent), "80e-3"}, {decQuantity(80, -3, DecimalExponent), "80e-3", ""},
{decQuantity(300, 6, DecimalExponent), "300e6"}, {decQuantity(300, 6, DecimalExponent), "300e6", ""},
{decQuantity(1, 12, DecimalExponent), "1e12"}, {decQuantity(1, 12, DecimalExponent), "1e12", ""},
{decQuantity(1, 3, DecimalExponent), "1e3"}, {decQuantity(1, 3, DecimalExponent), "1e3", ""},
{decQuantity(3, 3, DecimalExponent), "3e3"}, {decQuantity(3, 3, DecimalExponent), "3e3", ""},
{decQuantity(3, 3, DecimalSI), "3k"}, {decQuantity(3, 3, DecimalSI), "3k", ""},
{decQuantity(0, 0, DecimalExponent), "0"}, {decQuantity(0, 0, DecimalExponent), "0", "00"},
{decQuantity(1, -9, DecimalSI), "1n"}, {decQuantity(1, -9, DecimalSI), "1n", ""},
{decQuantity(80, -9, DecimalSI), "80n"}, {decQuantity(80, -9, DecimalSI), "80n", ""},
{decQuantity(1080, -9, DecimalSI), "1080n"}, {decQuantity(1080, -9, DecimalSI), "1080n", ""},
{decQuantity(108, -8, DecimalSI), "1080n"}, {decQuantity(108, -8, DecimalSI), "1080n", ""},
{decQuantity(10800, -10, DecimalSI), "1080n"}, {decQuantity(10800, -10, DecimalSI), "1080n", ""},
{decQuantity(1, -6, DecimalSI), "1u"}, {decQuantity(1, -6, DecimalSI), "1u", ""},
{decQuantity(80, -6, DecimalSI), "80u"}, {decQuantity(80, -6, DecimalSI), "80u", ""},
{decQuantity(1080, -6, DecimalSI), "1080u"}, {decQuantity(1080, -6, DecimalSI), "1080u", ""},
} }
for _, item := range table { for _, item := range table {
got := item.in.String() got := item.in.String()
if e, a := item.expect, got; e != a { if e, a := item.expect, got; e != a {
t.Errorf("%#v: expected %v, got %v", item.in, e, a) t.Errorf("%#v: expected %v, got %v", item.in, e, a)
} }
q, err := ParseQuantity(item.expect)
if err != nil {
t.Errorf("%#v: unexpected error: %v", item.expect, err)
}
if len(q.s) == 0 || q.s != item.expect {
t.Errorf("%#v: did not copy canonical string on parse: %s", item.expect, q.s)
}
if len(item.alternate) == 0 {
continue
}
q, err = ParseQuantity(item.alternate)
if err != nil {
t.Errorf("%#v: unexpected error: %v", item.expect, err)
continue
}
if len(q.s) != 0 {
t.Errorf("%#v: unexpected nested string: %v", item.expect, q.s)
}
if q.String() != item.expect {
t.Errorf("%#v: unexpected alternate canonical: %v", item.expect, q.String())
}
if len(q.s) == 0 || q.s != item.expect {
t.Errorf("%#v: did not set canonical string on ToString: %s", item.expect, q.s)
}
} }
desired := &inf.Dec{} // Avoid modifying the values in the table. desired := &inf.Dec{} // Avoid modifying the values in the table.
for _, item := range table { for _, item := range table {
@ -1126,7 +1151,24 @@ func BenchmarkQuantityString(b *testing.B) {
var s string var s string
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
q := values[i%len(values)] q := values[i%len(values)]
q.s = nil q.s = ""
s = q.String()
}
b.StopTimer()
if len(s) == 0 {
b.Fatal(s)
}
}
func BenchmarkQuantityStringPrecalc(b *testing.B) {
values := benchmarkQuantities()
for i := range values {
_ = values[i].String()
}
b.ResetTimer()
var s string
for i := 0; i < b.N; i++ {
q := values[i%len(values)]
s = q.String() s = q.String()
} }
b.StopTimer() b.StopTimer()
@ -1144,7 +1186,7 @@ func BenchmarkQuantityStringBinarySI(b *testing.B) {
var s string var s string
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
q := values[i%len(values)] q := values[i%len(values)]
q.s = nil q.s = ""
s = q.String() s = q.String()
} }
b.StopTimer() b.StopTimer()
@ -1158,7 +1200,7 @@ func BenchmarkQuantityMarshalJSON(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
q := values[i%len(values)] q := values[i%len(values)]
q.s = nil q.s = ""
if _, err := q.MarshalJSON(); err != nil { if _, err := q.MarshalJSON(); err != nil {
b.Fatal(err) b.Fatal(err)
} }