From af8261b1f6fb521039facf90055dcbf1f264c431 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 16 May 2026 01:07:59 -0400 Subject: [PATCH] Use compact oneof memory structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Borrows the storage format idea from https://github.com/tigrannajaryan/govariant This reduces memory usage when multiple records are kept in memory and also improves speed of some operations. Benchmarking results: ``` goos: darwin goarch: arm64 pkg: github.com/splunk/stef/benchmarks cpu: Apple M2 Pro │ bench_base.txt │ bench_current.txt │ │ sec/op │ sec/op vs base │ SerializeNative/STEF/serialize-10 5.000m ± 4% 3.193m ± 3% -36.14% (p=0.000 n=18) DeserializeNative/STEF/deser-10 1.479m ± 7% 1.489m ± 5% ~ (p=0.839 n=18) SerializeFromPdata/STEF/serialize-10 70.37m ± 3% 58.67m ± 4% -16.63% (p=0.000 n=18) DeserializeToPdata/STEF/deserialize-10 24.92m ± 2% 24.89m ± 2% ~ (p=0.815 n=18) STEFReaderRead-10 1.523m ± 3% 1.495m ± 6% ~ (p=0.323 n=18) STEFSerializeMultipart/astronomy-otelmetrics-10 1.958 ± 3% 1.808 ± 3% -7.68% (p=0.000 n=18) STEFDeserializeMultipart/astronomy-otelmetrics-10 45.87m ± 3% 46.92m ± 6% ~ (p=0.143 n=18) ReadSTEF-10 1.695m ± 2% 1.655m ± 3% -2.35% (p=0.008 n=18) ReadSTEFZ-10 2.218m ± 2% 2.227m ± 3% ~ (p=0.988 n=18) ReadSTEFZWriteSTEF-10 4.682m ± 4% 4.735m ± 5% ~ (p=0.134 n=18) geomean 11.21m 10.44m -6.85% │ bench_base.txt │ bench_current.txt │ │ sec/point │ sec/point vs base │ SerializeNative/STEF/serialize-10 74.79n ± 4% 47.76n ± 3% -36.14% (p=0.000 n=18) DeserializeNative/STEF/deser-10 22.12n ± 7% 22.27n ± 5% ~ (p=0.833 n=18) SerializeFromPdata/STEF/serialize-10 1053.5n ± 3% 878.0n ± 4% -16.66% (p=0.000 n=18) DeserializeToPdata/STEF/deserialize-10 373.0n ± 2% 372.5n ± 2% ~ (p=0.784 n=18) STEFReaderRead-10 22.79n ± 3% 22.36n ± 6% ~ (p=0.319 n=18) STEFSerializeMultipart/astronomy-otelmetrics-10 2.489µ ± 3% 2.298µ ± 3% -7.68% (p=0.000 n=18) STEFDeserializeMultipart/astronomy-otelmetrics-10 58.30n ± 3% 59.63n ± 6% ~ (p=0.145 n=18) ReadSTEF-10 25.37n ± 2% 24.77n ± 3% -2.35% (p=0.008 n=18) ReadSTEFZ-10 33.19n ± 2% 33.33n ± 3% ~ (p=0.981 n=18) ReadSTEFZWriteSTEF-10 70.07n ± 4% 70.86n ± 5% ~ (p=0.132 n=18) geomean 102.4n 95.39n -6.85% │ bench_base.txt │ bench_current.txt │ │ B/op │ B/op vs base │ SerializeNative/STEF/serialize-10 3.373Mi ± 0% 3.395Mi ± 0% +0.63% (p=0.000 n=18) DeserializeNative/STEF/deser-10 951.4Ki ± 0% 725.8Ki ± 0% -23.71% (p=0.000 n=18) SerializeFromPdata/STEF/serialize-10 79.76Mi ± 0% 36.49Mi ± 0% -54.25% (p=0.000 n=18) DeserializeToPdata/STEF/deserialize-10 34.83Mi ± 0% 34.61Mi ± 0% -0.63% (p=0.000 n=18) STEFReaderRead-10 953.1Ki ± 0% 727.5Ki ± 0% -23.67% (p=0.000 n=18) STEFSerializeMultipart/astronomy-otelmetrics-10 4.868Gi ± 0% 4.168Gi ± 0% -14.37% (p=0.000 n=18) STEFDeserializeMultipart/astronomy-otelmetrics-10 20.30Mi ± 0% 20.19Mi ± 0% -0.56% (p=0.000 n=18) ReadSTEF-10 953.1Ki ± 0% 727.5Ki ± 0% -23.67% (p=0.000 n=18) ReadSTEFZ-10 10.29Mi ± 0% 10.07Mi ± 0% -2.14% (p=0.000 n=18) ReadSTEFZWriteSTEF-10 13.45Mi ± 0% 13.22Mi ± 0% -1.67% (p=0.000 n=18) geomean 12.66Mi 10.58Mi -16.41% │ bench_base.txt │ bench_current.txt │ │ allocs/op │ allocs/op vs base │ SerializeNative/STEF/serialize-10 2.642k ± 0% 2.662k ± 0% +0.76% (p=0.000 n=18) DeserializeNative/STEF/deser-10 463.0 ± 0% 464.0 ± 0% +0.22% (p=0.000 n=18) SerializeFromPdata/STEF/serialize-10 134.7k ± 0% 134.7k ± 0% +0.03% (p=0.021 n=18) DeserializeToPdata/STEF/deserialize-10 756.2k ± 0% 756.2k ± 0% +0.00% (p=0.000 n=18) STEFReaderRead-10 463.0 ± 0% 464.0 ± 0% +0.22% (p=0.000 n=18) STEFSerializeMultipart/astronomy-otelmetrics-10 13.15M ± 0% 13.22M ± 0% +0.51% (p=0.000 n=18) STEFDeserializeMultipart/astronomy-otelmetrics-10 1.958k ± 0% 7.923k ± 0% +304.65% (p=0.000 n=18) ReadSTEF-10 463.0 ± 0% 464.0 ± 0% +0.22% (p=0.000 n=18) ReadSTEFZ-10 503.0 ± 0% 504.0 ± 0% +0.20% (p=0.000 n=18) ReadSTEFZWriteSTEF-10 1.230k ± 0% 1.232k ± 0% +0.16% (p=0.000 n=18) geomean 7.293k 8.406k +15.27% ``` Also added a benchmarks that reads many records at once to show how much memory is reduced: ``` goos: darwin goarch: arm64 pkg: github.com/splunk/stef/benchmarks cpu: Apple M2 Pro │ main.txt │ after.txt │ │ sec/op │ sec/op vs base │ ReaderReadMany-10 12.087m ± 5% 9.782m ± 2% -19.07% (p=0.002 n=6) │ main.txt │ after.txt │ │ sec/point │ sec/point vs base │ ReaderReadMany-10 181.0n ± 5% 146.5n ± 2% -19.06% (p=0.002 n=6) │ main.txt │ after.txt │ │ B/op │ B/op vs base │ ReaderReadMany-10 2111.4Ki ± 6% 921.2Ki ± 1% -56.37% (p=0.002 n=6) │ main.txt │ after.txt │ │ allocs/op │ allocs/op vs base │ ReaderReadMany-10 970.0 ± 5% 845.5 ± 2% -12.84% (p=0.002 n=6) ``` --- benchmarks/benchmarks_test.go | 27 +- examples/ints/internal/ints/record.go | 6 + examples/jsonl/internal/jsonstef/jsonvalue.go | 354 +++++++++------ examples/jsonl/internal/jsonstef/record.go | 6 + examples/profile/internal/profile/function.go | 6 + .../profile/internal/profile/labelvalue.go | 217 +++++---- examples/profile/internal/profile/line.go | 6 + examples/profile/internal/profile/location.go | 6 + examples/profile/internal/profile/mapping.go | 6 + examples/profile/internal/profile/numvalue.go | 6 + .../internal/profile/profilemetadata.go | 6 + examples/profile/internal/profile/sample.go | 6 + .../profile/internal/profile/samplevalue.go | 6 + .../internal/profile/samplevaluetype.go | 6 + go/otel/otelstef/anyvalue.go | 424 +++++++++++------- go/otel/otelstef/envelope.go | 6 + go/otel/otelstef/event.go | 6 + go/otel/otelstef/exemplar.go | 6 + go/otel/otelstef/exemplarvalue.go | 171 ++++--- go/otel/otelstef/exphistogrambuckets.go | 6 + go/otel/otelstef/exphistogramvalue.go | 6 + go/otel/otelstef/histogramvalue.go | 6 + go/otel/otelstef/link.go | 6 + go/otel/otelstef/metric.go | 6 + go/otel/otelstef/metrics.go | 6 + go/otel/otelstef/point.go | 6 + go/otel/otelstef/pointvalue.go | 387 ++++++++++------ go/otel/otelstef/quantilevalue.go | 6 + go/otel/otelstef/resource.go | 6 + go/otel/otelstef/scope.go | 6 + go/otel/otelstef/span.go | 6 + go/otel/otelstef/spans.go | 6 + go/otel/otelstef/spanstatus.go | 6 + go/otel/otelstef/summaryvalue.go | 6 + stefc/templates/go/oneof.go.tmpl | 387 ++++++++++------ stefc/templates/go/struct.go.tmpl | 6 + 36 files changed, 1424 insertions(+), 717 deletions(-) diff --git a/benchmarks/benchmarks_test.go b/benchmarks/benchmarks_test.go index 063963dc..4dcb2819 100644 --- a/benchmarks/benchmarks_test.go +++ b/benchmarks/benchmarks_test.go @@ -316,7 +316,6 @@ func BenchmarkDeserializeToPdata(b *testing.B) { b.ReportAllocs() } -/* Need to rewrite this to use STEF.ReadMany() API when it becomes available. func BenchmarkReaderReadMany(b *testing.B) { generator := &generators.File{ FilePath: "testdata/hipstershop-otelmetrics.zst", @@ -332,26 +331,38 @@ func BenchmarkReaderReadMany(b *testing.B) { log.Fatal(err) } + pointCount := 100_000 + records := make([]otelstef.Metrics, pointCount) + for i := 0; i < pointCount; i++ { + records[i].Init() + } + b.ResetTimer() - var records metrics.Records for i := 0; i < b.N; i++ { buf := bytes.NewBuffer(bodyBytes) - reader, err := metrics.NewReader(buf) + reader, err := otelstef.NewMetricsReader(buf) if err != nil { log.Fatal(err) } - err = reader.ReadMany(0, &records) - if err != nil && err != io.EOF { - log.Fatal(err) + for j := 0; ; j++ { + err = reader.Read(pkg.ReadOptions{}) + if err == io.EOF { + break + } + if err != nil { + log.Fatal(err) + } + + records[j%pointCount].Reset() + records[j%pointCount].CopyFrom(&reader.Record) } } b.ReportMetric( - float64(b.Elapsed().Nanoseconds())/float64(b.N*batch.DataPointCount()), + nsPerPoint(b, batch.DataPointCount()), "ns/point", ) } -*/ func BenchmarkSTEFReaderRead(b *testing.B) { generator := &generators.File{ diff --git a/examples/ints/internal/ints/record.go b/examples/ints/internal/ints/record.go index 59438800..cf7ef42e 100644 --- a/examples/ints/internal/ints/record.go +++ b/examples/ints/internal/ints/record.go @@ -55,6 +55,12 @@ func (s *Record) initAlloc(parentModifiedFields *modifiedFields, parentModifiedB } +// Reset the struct to its initial state. +func (s *Record) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *Record) reset() { diff --git a/examples/jsonl/internal/jsonstef/jsonvalue.go b/examples/jsonl/internal/jsonstef/jsonvalue.go index 035537a6..c7d39ae9 100644 --- a/examples/jsonl/internal/jsonstef/jsonvalue.go +++ b/examples/jsonl/internal/jsonstef/jsonvalue.go @@ -18,14 +18,11 @@ var _ = codecs.StringEncoder{} // JsonValue is a oneof struct. type JsonValue struct { - // The current type of the oneof. - typ JsonValueType - - object JsonObject - array JsonValueArray - string string - number float64 - bool bool + // The low 8 bits hold the current type. The remaining 56 bits hold + // string/bytes length when a choice needs it. + typLen uint64 + ptr unsafe.Pointer + bits uint64 // Pointer to parent's modifiedFields parentModifiedFields *modifiedFields @@ -41,33 +38,33 @@ func (s *JsonValue) Init() { func (s *JsonValue) init(parentModifiedFields *modifiedFields, parentModifiedBit uint64) { s.parentModifiedFields = parentModifiedFields s.parentModifiedBit = parentModifiedBit - - s.object.init(parentModifiedFields, parentModifiedBit) - s.array.init(parentModifiedFields, parentModifiedBit) } func (s *JsonValue) initAlloc(parentModifiedFields *modifiedFields, parentModifiedBit uint64, allocators *Allocators) { s.parentModifiedFields = parentModifiedFields s.parentModifiedBit = parentModifiedBit +} - s.object.initAlloc(parentModifiedFields, parentModifiedBit, allocators) - s.array.initAlloc(parentModifiedFields, parentModifiedBit, allocators) +// clear the value and set the type. Must be followed by a "set" call which +// sets the value (except if the type is None). +func (s *JsonValue) clearValSetType(typ JsonValueType) { + s.ptr = nil + s.typLen = uint64(typ) + s.bits = 0 } // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *JsonValue) reset() { - s.typ = JsonValueTypeNone - // We don't need to reset the state of the field since that will be done - // when the type is changed, see SetType(). + s.clearValSetType(JsonValueTypeNone) } func (s *JsonValue) freeze() { - switch s.typ { + switch s.Type() { case JsonValueTypeObject: - s.object.freeze() + s.objectPtr().freeze() case JsonValueTypeArray: - s.array.freeze() + s.arrayPtr().freeze() } } @@ -77,8 +74,12 @@ func (s *JsonValue) freeze() { func (s *JsonValue) fixParent(parentModifiedFields *modifiedFields) { s.parentModifiedFields = parentModifiedFields - s.object.fixParent(parentModifiedFields) - s.array.fixParent(parentModifiedFields) + switch s.Type() { + case JsonValueTypeObject: + s.objectPtr().fixParent(parentModifiedFields) + case JsonValueTypeArray: + s.arrayPtr().fixParent(parentModifiedFields) + } } type JsonValueType byte @@ -93,87 +94,147 @@ const ( JsonValueTypeCount ) -// Type returns the type of the value currently contained in JsonValue. -func (s *JsonValue) Type() JsonValueType { - return s.typ +const ( + JsonValueTypLenTypeBits = 8 + JsonValueTypLenTypeMask = 1<> JsonValueTypLenTypeBits) } -// resetContained resets the currently contained value, if any. -// Normally used after switching to a different type to make sure -// the value contained is in blank state. -func (s *JsonValue) resetContained() { - switch s.typ { - case JsonValueTypeObject: - s.object.reset() - case JsonValueTypeArray: - s.array.reset() +func (s *JsonValue) setLen(n int) { + if uint64(n) > JsonValueTypLenLenMask { + panic("JsonValue length exceeds 56 bits") } + s.typLen = (uint64(n) << JsonValueTypLenTypeBits) | (s.typLen & JsonValueTypLenTypeMask) +} + +// Type returns the type of the value currently contained in JsonValue. +func (s *JsonValue) Type() JsonValueType { + return JsonValueType(s.typLen & JsonValueTypLenTypeMask) } // SetType sets the type of the value currently contained in JsonValue. func (s *JsonValue) SetType(typ JsonValueType) { - if s.typ != typ { - s.typ = typ - s.resetContained() + if s.Type() != typ { + s.clearValSetType(typ) switch typ { + case JsonValueTypeObject: + s.allocObject() + case JsonValueTypeArray: + s.allocArray() } s.markParentModified() } } +func (s *JsonValue) objectPtr() *JsonObject { + return (*JsonObject)(s.ptr) +} + +func (s *JsonValue) allocObject() *JsonObject { + v := &JsonObject{} + v.init(s.parentModifiedFields, s.parentModifiedBit) + s.ptr = unsafe.Pointer(v) + return v +} + +func (s *JsonValue) allocObjectAlloc(allocators *Allocators) *JsonObject { + v := &JsonObject{} + v.initAlloc(s.parentModifiedFields, s.parentModifiedBit, allocators) + s.ptr = unsafe.Pointer(v) + return v +} + // Object returns the value if the contained type is currently JsonValueTypeObject. // The caller must check the type via Type() before attempting to call this function. func (s *JsonValue) Object() *JsonObject { - return &s.object + return s.objectPtr() +} + +func (s *JsonValue) arrayPtr() *JsonValueArray { + return (*JsonValueArray)(s.ptr) +} + +func (s *JsonValue) allocArray() *JsonValueArray { + v := &JsonValueArray{} + v.init(s.parentModifiedFields, s.parentModifiedBit) + s.ptr = unsafe.Pointer(v) + return v +} + +func (s *JsonValue) allocArrayAlloc(allocators *Allocators) *JsonValueArray { + v := &JsonValueArray{} + v.initAlloc(s.parentModifiedFields, s.parentModifiedBit, allocators) + s.ptr = unsafe.Pointer(v) + return v } // Array returns the value if the contained type is currently JsonValueTypeArray. // The caller must check the type via Type() before attempting to call this function. func (s *JsonValue) Array() *JsonValueArray { - return &s.array + return s.arrayPtr() +} + +func (s *JsonValue) setString(v string) { + s.ptr = unsafe.Pointer(unsafe.StringData(string(v))) + s.setLen(len(v)) } // String returns the value if the contained type is currently JsonValueTypeString. // The caller must check the type via Type() before attempting to call this function. func (s *JsonValue) String() string { - return s.string + return string(unsafe.String((*byte)(s.ptr), s.len())) } // SetString sets the value to the specified value and sets the type to JsonValueTypeString. func (s *JsonValue) SetString(v string) { - if s.typ != JsonValueTypeString || s.string != v { - s.string = v - s.typ = JsonValueTypeString + stored := v + if s.Type() != JsonValueTypeString || s.String() != stored { + s.clearValSetType(JsonValueTypeString) + s.setString(stored) s.parentModifiedFields.markModified(s.parentModifiedBit) } } +func (s *JsonValue) numberPtr() *float64 { + return (*float64)(unsafe.Pointer(&s.bits)) +} + // Number returns the value if the contained type is currently JsonValueTypeNumber. // The caller must check the type via Type() before attempting to call this function. func (s *JsonValue) Number() float64 { - return s.number + return (*s.numberPtr()) } // SetNumber sets the value to the specified value and sets the type to JsonValueTypeNumber. func (s *JsonValue) SetNumber(v float64) { - if s.typ != JsonValueTypeNumber || s.number != v { - s.number = v - s.typ = JsonValueTypeNumber + stored := v + if s.Type() != JsonValueTypeNumber || *s.numberPtr() != stored { + s.clearValSetType(JsonValueTypeNumber) + *s.numberPtr() = stored s.parentModifiedFields.markModified(s.parentModifiedBit) } } +func (s *JsonValue) boolPtr() *bool { + return (*bool)(unsafe.Pointer(&s.bits)) +} + // Bool returns the value if the contained type is currently JsonValueTypeBool. // The caller must check the type via Type() before attempting to call this function. func (s *JsonValue) Bool() bool { - return s.bool + return (*s.boolPtr()) } // SetBool sets the value to the specified value and sets the type to JsonValueTypeBool. func (s *JsonValue) SetBool(v bool) { - if s.typ != JsonValueTypeBool || s.bool != v { - s.bool = v - s.typ = JsonValueTypeBool + stored := v + if s.Type() != JsonValueTypeBool || *s.boolPtr() != stored { + s.clearValSetType(JsonValueTypeBool) + *s.boolPtr() = stored s.parentModifiedFields.markModified(s.parentModifiedBit) } } @@ -191,18 +252,22 @@ func (s *JsonValue) cloneShared(allocators *Allocators) *JsonValue { func (s *JsonValue) Clone(allocators *Allocators) *JsonValue { allocators.allocSizeChecker.AddAllocSize(uint(unsafe.Sizeof(JsonValue{}))) c := allocators.JsonValue.Alloc() - c.typ = s.typ - switch s.typ { + c.clearValSetType(s.Type()) + switch s.Type() { case JsonValueTypeObject: - copyToNewJsonObject(&c.object, &s.object, allocators) + allocators.allocSizeChecker.AddAllocSize(uint(unsafe.Sizeof(JsonObject{}))) + c.allocObjectAlloc(allocators) + copyToNewJsonObject(c.objectPtr(), s.objectPtr(), allocators) case JsonValueTypeArray: - copyToNewJsonValueArray(&c.array, &s.array, allocators) + allocators.allocSizeChecker.AddAllocSize(uint(unsafe.Sizeof(JsonValueArray{}))) + c.allocArrayAlloc(allocators) + copyToNewJsonValueArray(c.arrayPtr(), s.arrayPtr(), allocators) case JsonValueTypeString: - c.string = s.string + c.setString(s.String()) case JsonValueTypeNumber: - c.number = s.number + *c.numberPtr() = *s.numberPtr() case JsonValueTypeBool: - c.bool = s.bool + *c.boolPtr() = *s.boolPtr() } return c } @@ -210,61 +275,72 @@ func (s *JsonValue) Clone(allocators *Allocators) *JsonValue { // ByteSize returns approximate memory usage in bytes. Used to calculate // memory used by dictionaries. func (s *JsonValue) byteSize() uint { - return uint(unsafe.Sizeof(*s)) + - s.object.byteSize() + s.array.byteSize() + 0 + size := uint(unsafe.Sizeof(*s)) + switch s.Type() { + case JsonValueTypeObject: + size += s.objectPtr().byteSize() + case JsonValueTypeArray: + size += s.arrayPtr().byteSize() + } + return size } // Copy from src to dst, overwriting existing data in dst. func copyJsonValue(dst *JsonValue, src *JsonValue) { - switch src.typ { + switch src.Type() { case JsonValueTypeObject: - dst.SetType(src.typ) - copyJsonObject(&dst.object, &src.object) + dst.SetType(src.Type()) + copyJsonObject(dst.objectPtr(), src.objectPtr()) case JsonValueTypeArray: - dst.SetType(src.typ) - copyJsonValueArray(&dst.array, &src.array) + dst.SetType(src.Type()) + copyJsonValueArray(dst.arrayPtr(), src.arrayPtr()) case JsonValueTypeString: - dst.SetString(src.string) + dst.SetString(src.String()) case JsonValueTypeNumber: - dst.SetNumber(src.number) + dst.SetNumber((*src.numberPtr())) case JsonValueTypeBool: - dst.SetBool(src.bool) + dst.SetBool((*src.boolPtr())) case JsonValueTypeNone: - if dst.typ != JsonValueTypeNone { - dst.typ = JsonValueTypeNone + if dst.Type() != JsonValueTypeNone { + dst.clearValSetType(JsonValueTypeNone) dst.markParentModified() } default: - panic("copyJsonValue: unexpected type: " + fmt.Sprint(src.typ)) + panic("copyJsonValue: unexpected type: " + fmt.Sprint(src.Type())) } } // Copy from src to dst. dst is assumed to be just inited. func copyToNewJsonValue(dst *JsonValue, src *JsonValue, allocators *Allocators) { - dst.typ = src.typ - switch src.typ { + dst.typLen = uint64(src.Type()) + switch src.Type() { case JsonValueTypeObject: - copyToNewJsonObject(&dst.object, &src.object, allocators) + allocators.allocSizeChecker.AddAllocSize(uint(unsafe.Sizeof(JsonObject{}))) + dst.allocObjectAlloc(allocators) + copyToNewJsonObject(dst.objectPtr(), src.objectPtr(), allocators) case JsonValueTypeArray: - copyToNewJsonValueArray(&dst.array, &src.array, allocators) + allocators.allocSizeChecker.AddAllocSize(uint(unsafe.Sizeof(JsonValueArray{}))) + dst.allocArrayAlloc(allocators) + copyToNewJsonValueArray(dst.arrayPtr(), src.arrayPtr(), allocators) case JsonValueTypeString: - if dst.string != src.string { - dst.string = src.string + srcVal := src.String() + if string(srcVal) != "" { + dst.setString(srcVal) dst.parentModifiedFields.markModified(dst.parentModifiedBit) } case JsonValueTypeNumber: - if dst.number != src.number { - dst.number = src.number + if *dst.numberPtr() != *src.numberPtr() { + *dst.numberPtr() = *src.numberPtr() dst.parentModifiedFields.markModified(dst.parentModifiedBit) } case JsonValueTypeBool: - if dst.bool != src.bool { - dst.bool = src.bool + if *dst.boolPtr() != *src.boolPtr() { + *dst.boolPtr() = *src.boolPtr() dst.parentModifiedFields.markModified(dst.parentModifiedBit) } case JsonValueTypeNone: default: - panic("copyJsonValue: unexpected type: " + fmt.Sprint(src.typ)) + panic("copyJsonValue: unexpected type: " + fmt.Sprint(src.Type())) } } @@ -278,48 +354,48 @@ func (s *JsonValue) markParentModified() { } func (s *JsonValue) setModifiedRecursively() { - switch s.typ { + switch s.Type() { case JsonValueTypeObject: - s.object.setModifiedRecursively() + s.objectPtr().setModifiedRecursively() case JsonValueTypeArray: - s.array.setModifiedRecursively() + s.arrayPtr().setModifiedRecursively() } } func (s *JsonValue) setUnmodifiedRecursively() { - switch s.typ { + switch s.Type() { case JsonValueTypeObject: - s.object.setUnmodifiedRecursively() + s.objectPtr().setUnmodifiedRecursively() case JsonValueTypeArray: - s.array.setUnmodifiedRecursively() + s.arrayPtr().setUnmodifiedRecursively() } } // computeDiff compares s and val and returns true if they differ. // All fields that are different in s will be marked as modified. func (s *JsonValue) computeDiff(val *JsonValue) (ret bool) { - if s.typ == val.typ { - switch s.typ { + if s.Type() == val.Type() { + switch s.Type() { case JsonValueTypeObject: - ret = s.object.computeDiff(&val.object) + ret = s.objectPtr().computeDiff(val.objectPtr()) case JsonValueTypeArray: - ret = s.array.computeDiff(&val.array) + ret = s.arrayPtr().computeDiff(val.arrayPtr()) case JsonValueTypeString: - ret = s.string != val.string + ret = s.String() != val.String() case JsonValueTypeNumber: - ret = s.number != val.number + ret = s.Number() != val.Number() case JsonValueTypeBool: - ret = s.bool != val.bool + ret = s.Bool() != val.Bool() } } else { ret = true - switch s.typ { + switch s.Type() { case JsonValueTypeObject: // val.object doesn't exist at all so mark the whole s.object subtree as modified. - s.object.setModifiedRecursively() + s.objectPtr().setModifiedRecursively() case JsonValueTypeArray: // val.array doesn't exist at all so mark the whole s.array subtree as modified. - s.array.setModifiedRecursively() + s.arrayPtr().setModifiedRecursively() } } return ret @@ -327,20 +403,20 @@ func (s *JsonValue) computeDiff(val *JsonValue) (ret bool) { // IsEqual performs deep comparison and returns true if struct is equal to val. func (e *JsonValue) IsEqual(val *JsonValue) bool { - if e.typ != val.typ { + if e.Type() != val.Type() { return false } - switch e.typ { + switch e.Type() { case JsonValueTypeObject: - return e.object.IsEqual(&val.object) + return e.objectPtr().IsEqual(val.objectPtr()) case JsonValueTypeArray: - return e.array.IsEqual(&val.array) + return e.arrayPtr().IsEqual(val.arrayPtr()) case JsonValueTypeString: - return pkg.StringEqual(e.string, val.string) + return pkg.StringEqual(e.String(), val.String()) case JsonValueTypeNumber: - return pkg.Float64Equal(e.number, val.number) + return pkg.Float64Equal(*e.numberPtr(), *val.numberPtr()) case JsonValueTypeBool: - return pkg.BoolEqual(e.bool, val.bool) + return pkg.BoolEqual(*e.boolPtr(), *val.boolPtr()) } return true @@ -349,21 +425,23 @@ func (e *JsonValue) IsEqual(val *JsonValue) bool { // CmpJsonValue performs deep comparison and returns an integer that // will be 0 if left == right, negative if left < right, positive if left > right. func CmpJsonValue(left, right *JsonValue) int { - c := pkg.Uint64Compare(uint64(left.typ), uint64(right.typ)) + c := pkg.Uint64Compare(uint64(left.Type()), uint64(right.Type())) if c != 0 { return c } - switch left.typ { + switch left.Type() { case JsonValueTypeObject: - return CmpJsonObject(&left.object, &right.object) + return CmpJsonObject( + left.objectPtr(), right.objectPtr()) case JsonValueTypeArray: - return CmpJsonValueArray(&left.array, &right.array) + return CmpJsonValueArray( + left.arrayPtr(), right.arrayPtr()) case JsonValueTypeString: - return strings.Compare(left.string, right.string) + return strings.Compare(left.String(), right.String()) case JsonValueTypeNumber: - return pkg.Float64Compare(left.number, right.number) + return pkg.Float64Compare(*left.numberPtr(), *right.numberPtr()) case JsonValueTypeBool: - return pkg.BoolCompare(left.bool, right.bool) + return pkg.BoolCompare(*left.boolPtr(), *right.boolPtr()) } return 0 @@ -387,14 +465,14 @@ func (s *JsonValue) mutateRandom(random *rand.Rand, schem *schema.Schema, limite typeChanged = true } - switch s.typ { + switch s.Type() { case JsonValueTypeObject: if typeChanged || random.IntN(2) == 0 { - s.object.mutateRandom(random, schem, limiter) + s.objectPtr().mutateRandom(random, schem, limiter) } case JsonValueTypeArray: if typeChanged || random.IntN(2) == 0 { - s.array.mutateRandom(random, schem, limiter) + s.arrayPtr().mutateRandom(random, schem, limiter) } case JsonValueTypeString: if typeChanged || random.IntN(2) == 0 { @@ -553,7 +631,7 @@ func (e *JsonValueEncoder) Reset() { // Encode encodes val into buf func (e *JsonValueEncoder) Encode(val *JsonValue) { - typ := val.typ + typ := val.Type() if uint(typ) > e.fieldCount { // The current field type is not supported in target schema. Encode the type as None. typ = JsonValueTypeNone @@ -568,19 +646,19 @@ func (e *JsonValueEncoder) Encode(val *JsonValue) { switch typ { case JsonValueTypeObject: // Encode Object - e.objectEncoder.Encode(&val.object) + e.objectEncoder.Encode(val.objectPtr()) case JsonValueTypeArray: // Encode Array - e.arrayEncoder.Encode(&val.array) + e.arrayEncoder.Encode(val.arrayPtr()) case JsonValueTypeString: // Encode String - e.stringEncoder.Encode(val.string) + e.stringEncoder.Encode(val.String()) case JsonValueTypeNumber: // Encode Number - e.numberEncoder.Encode(val.number) + e.numberEncoder.Encode(*val.numberPtr()) case JsonValueTypeBool: // Encode Bool - e.boolEncoder.Encode(val.bool) + e.boolEncoder.Encode(*val.boolPtr()) } } @@ -824,31 +902,49 @@ func (d *JsonValueDecoder) Decode(dstPtr *JsonValue) error { } dst := dstPtr - if dst.typ != JsonValueType(typ) { - dst.typ = JsonValueType(typ) + if dst.Type() != JsonValueType(typ) { // The type changed, we need to reset the contained value so that // it does not contain carry-over data from a previous record that // was of this same type. - dst.resetContained() + dst.clearValSetType(JsonValueType(typ)) + + switch JsonValueType(typ) { + case JsonValueTypeObject: + // Allocate Object + if err := d.allocators.allocSizeChecker.PrepAllocSize(uint(unsafe.Sizeof(JsonObject{}))); err != nil { + return err + } + dst.allocObjectAlloc(d.allocators) + case JsonValueTypeArray: + // Allocate Array + if err := d.allocators.allocSizeChecker.PrepAllocSize(uint(unsafe.Sizeof(JsonValueArray{}))); err != nil { + return err + } + dst.allocArrayAlloc(d.allocators) + } } // Decode selected field - switch dst.typ { + switch dst.Type() { case JsonValueTypeObject: // Decode Object - return d.objectDecoder.Decode(&dst.object) + return d.objectDecoder.Decode(dst.objectPtr()) case JsonValueTypeArray: // Decode Array - return d.arrayDecoder.Decode(&dst.array) + return d.arrayDecoder.Decode(dst.arrayPtr()) case JsonValueTypeString: // Decode String - return d.stringDecoder.Decode(&dst.string) + var v string + if err := d.stringDecoder.Decode(&v); err != nil { + return err + } + dst.setString(v) case JsonValueTypeNumber: // Decode Number - return d.numberDecoder.Decode(&dst.number) + return d.numberDecoder.Decode(dst.numberPtr()) case JsonValueTypeBool: // Decode Bool - return d.boolDecoder.Decode(&dst.bool) + return d.boolDecoder.Decode(dst.boolPtr()) } return nil } diff --git a/examples/jsonl/internal/jsonstef/record.go b/examples/jsonl/internal/jsonstef/record.go index bd85997f..b233bb83 100644 --- a/examples/jsonl/internal/jsonstef/record.go +++ b/examples/jsonl/internal/jsonstef/record.go @@ -60,6 +60,12 @@ func (s *Record) initAlloc(parentModifiedFields *modifiedFields, parentModifiedB s.value.initAlloc(&s.modifiedFields, fieldModifiedRecordValue, allocators) } +// Reset the struct to its initial state. +func (s *Record) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *Record) reset() { diff --git a/examples/profile/internal/profile/function.go b/examples/profile/internal/profile/function.go index c7f4b179..fc001788 100644 --- a/examples/profile/internal/profile/function.go +++ b/examples/profile/internal/profile/function.go @@ -72,6 +72,12 @@ func (s *Function) initAlloc(parentModifiedFields *modifiedFields, parentModifie } +// Reset the struct to its initial state. +func (s *Function) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *Function) reset() { diff --git a/examples/profile/internal/profile/labelvalue.go b/examples/profile/internal/profile/labelvalue.go index b76bfecc..4ad0c6e9 100644 --- a/examples/profile/internal/profile/labelvalue.go +++ b/examples/profile/internal/profile/labelvalue.go @@ -18,11 +18,10 @@ var _ = codecs.StringEncoder{} // LabelValue is a oneof struct. type LabelValue struct { - // The current type of the oneof. - typ LabelValueType - - str string - num NumValue + // The low 8 bits hold the current type. The remaining 56 bits hold + // string/bytes length when a choice needs it. + typLen uint64 + ptr unsafe.Pointer // Pointer to parent's modifiedFields parentModifiedFields *modifiedFields @@ -38,29 +37,30 @@ func (s *LabelValue) Init() { func (s *LabelValue) init(parentModifiedFields *modifiedFields, parentModifiedBit uint64) { s.parentModifiedFields = parentModifiedFields s.parentModifiedBit = parentModifiedBit - - s.num.init(parentModifiedFields, parentModifiedBit) } func (s *LabelValue) initAlloc(parentModifiedFields *modifiedFields, parentModifiedBit uint64, allocators *Allocators) { s.parentModifiedFields = parentModifiedFields s.parentModifiedBit = parentModifiedBit +} - s.num.initAlloc(parentModifiedFields, parentModifiedBit, allocators) +// clear the value and set the type. Must be followed by a "set" call which +// sets the value (except if the type is None). +func (s *LabelValue) clearValSetType(typ LabelValueType) { + s.ptr = nil + s.typLen = uint64(typ) } // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *LabelValue) reset() { - s.typ = LabelValueTypeNone - // We don't need to reset the state of the field since that will be done - // when the type is changed, see SetType(). + s.clearValSetType(LabelValueTypeNone) } func (s *LabelValue) freeze() { - switch s.typ { + switch s.Type() { case LabelValueTypeNum: - s.num.freeze() + s.numPtr().freeze() } } @@ -70,7 +70,10 @@ func (s *LabelValue) freeze() { func (s *LabelValue) fixParent(parentModifiedFields *modifiedFields) { s.parentModifiedFields = parentModifiedFields - s.num.fixParent(parentModifiedFields) + switch s.Type() { + case LabelValueTypeNum: + s.numPtr().fixParent(parentModifiedFields) + } } type LabelValueType byte @@ -82,51 +85,83 @@ const ( LabelValueTypeCount ) -// Type returns the type of the value currently contained in LabelValue. -func (s *LabelValue) Type() LabelValueType { - return s.typ +const ( + LabelValueTypLenTypeBits = 8 + LabelValueTypLenTypeMask = 1<> LabelValueTypLenTypeBits) } -// resetContained resets the currently contained value, if any. -// Normally used after switching to a different type to make sure -// the value contained is in blank state. -func (s *LabelValue) resetContained() { - switch s.typ { - case LabelValueTypeNum: - s.num.reset() +func (s *LabelValue) setLen(n int) { + if uint64(n) > LabelValueTypLenLenMask { + panic("LabelValue length exceeds 56 bits") } + s.typLen = (uint64(n) << LabelValueTypLenTypeBits) | (s.typLen & LabelValueTypLenTypeMask) +} + +// Type returns the type of the value currently contained in LabelValue. +func (s *LabelValue) Type() LabelValueType { + return LabelValueType(s.typLen & LabelValueTypLenTypeMask) } // SetType sets the type of the value currently contained in LabelValue. func (s *LabelValue) SetType(typ LabelValueType) { - if s.typ != typ { - s.typ = typ - s.resetContained() + if s.Type() != typ { + s.clearValSetType(typ) switch typ { + case LabelValueTypeNum: + s.allocNum() } s.markParentModified() } } +func (s *LabelValue) setStr(v string) { + s.ptr = unsafe.Pointer(unsafe.StringData(string(v))) + s.setLen(len(v)) +} + // Str returns the value if the contained type is currently LabelValueTypeStr. // The caller must check the type via Type() before attempting to call this function. func (s *LabelValue) Str() string { - return s.str + return string(unsafe.String((*byte)(s.ptr), s.len())) } // SetStr sets the value to the specified value and sets the type to LabelValueTypeStr. func (s *LabelValue) SetStr(v string) { - if s.typ != LabelValueTypeStr || s.str != v { - s.str = v - s.typ = LabelValueTypeStr + stored := v + if s.Type() != LabelValueTypeStr || s.Str() != stored { + s.clearValSetType(LabelValueTypeStr) + s.setStr(stored) s.parentModifiedFields.markModified(s.parentModifiedBit) } } +func (s *LabelValue) numPtr() *NumValue { + return (*NumValue)(s.ptr) +} + +func (s *LabelValue) allocNum() *NumValue { + v := &NumValue{} + v.init(s.parentModifiedFields, s.parentModifiedBit) + s.ptr = unsafe.Pointer(v) + return v +} + +func (s *LabelValue) allocNumAlloc(allocators *Allocators) *NumValue { + v := allocators.NumValue.Alloc() + v.initAlloc(s.parentModifiedFields, s.parentModifiedBit, allocators) + s.ptr = unsafe.Pointer(v) + return v +} + // Num returns the value if the contained type is currently LabelValueTypeNum. // The caller must check the type via Type() before attempting to call this function. func (s *LabelValue) Num() *NumValue { - return &s.num + return s.numPtr() } func (s *LabelValue) canBeShared() bool { @@ -141,12 +176,14 @@ func (s *LabelValue) cloneShared(allocators *Allocators) LabelValue { func (s *LabelValue) Clone(allocators *Allocators) LabelValue { c := LabelValue{} - c.typ = s.typ - switch s.typ { + c.clearValSetType(s.Type()) + switch s.Type() { case LabelValueTypeStr: - c.str = s.str + c.setStr(s.Str()) case LabelValueTypeNum: - copyToNewNumValue(&c.num, &s.num, allocators) + allocators.allocSizeChecker.AddAllocSize(uint(unsafe.Sizeof(NumValue{}))) + c.allocNumAlloc(allocators) + copyToNewNumValue(c.numPtr(), s.numPtr(), allocators) } return c } @@ -154,42 +191,49 @@ func (s *LabelValue) Clone(allocators *Allocators) LabelValue { // ByteSize returns approximate memory usage in bytes. Used to calculate // memory used by dictionaries. func (s *LabelValue) byteSize() uint { - return uint(unsafe.Sizeof(*s)) + - s.num.byteSize() + 0 + size := uint(unsafe.Sizeof(*s)) + switch s.Type() { + case LabelValueTypeNum: + size += s.numPtr().byteSize() + } + return size } // Copy from src to dst, overwriting existing data in dst. func copyLabelValue(dst *LabelValue, src *LabelValue) { - switch src.typ { + switch src.Type() { case LabelValueTypeStr: - dst.SetStr(src.str) + dst.SetStr(src.Str()) case LabelValueTypeNum: - dst.SetType(src.typ) - copyNumValue(&dst.num, &src.num) + dst.SetType(src.Type()) + copyNumValue(dst.numPtr(), src.numPtr()) case LabelValueTypeNone: - if dst.typ != LabelValueTypeNone { - dst.typ = LabelValueTypeNone + if dst.Type() != LabelValueTypeNone { + dst.clearValSetType(LabelValueTypeNone) dst.markParentModified() } default: - panic("copyLabelValue: unexpected type: " + fmt.Sprint(src.typ)) + panic("copyLabelValue: unexpected type: " + fmt.Sprint(src.Type())) } } // Copy from src to dst. dst is assumed to be just inited. func copyToNewLabelValue(dst *LabelValue, src *LabelValue, allocators *Allocators) { - dst.typ = src.typ - switch src.typ { + dst.typLen = uint64(src.Type()) + switch src.Type() { case LabelValueTypeStr: - if dst.str != src.str { - dst.str = src.str + srcVal := src.Str() + if string(srcVal) != "" { + dst.setStr(srcVal) dst.parentModifiedFields.markModified(dst.parentModifiedBit) } case LabelValueTypeNum: - copyToNewNumValue(&dst.num, &src.num, allocators) + allocators.allocSizeChecker.AddAllocSize(uint(unsafe.Sizeof(NumValue{}))) + dst.allocNumAlloc(allocators) + copyToNewNumValue(dst.numPtr(), src.numPtr(), allocators) case LabelValueTypeNone: default: - panic("copyLabelValue: unexpected type: " + fmt.Sprint(src.typ)) + panic("copyLabelValue: unexpected type: " + fmt.Sprint(src.Type())) } } @@ -203,35 +247,35 @@ func (s *LabelValue) markParentModified() { } func (s *LabelValue) setModifiedRecursively() { - switch s.typ { + switch s.Type() { case LabelValueTypeNum: - s.num.setModifiedRecursively() + s.numPtr().setModifiedRecursively() } } func (s *LabelValue) setUnmodifiedRecursively() { - switch s.typ { + switch s.Type() { case LabelValueTypeNum: - s.num.setUnmodifiedRecursively() + s.numPtr().setUnmodifiedRecursively() } } // computeDiff compares s and val and returns true if they differ. // All fields that are different in s will be marked as modified. func (s *LabelValue) computeDiff(val *LabelValue) (ret bool) { - if s.typ == val.typ { - switch s.typ { + if s.Type() == val.Type() { + switch s.Type() { case LabelValueTypeStr: - ret = s.str != val.str + ret = s.Str() != val.Str() case LabelValueTypeNum: - ret = s.num.computeDiff(&val.num) + ret = s.numPtr().computeDiff(val.numPtr()) } } else { ret = true - switch s.typ { + switch s.Type() { case LabelValueTypeNum: // val.num doesn't exist at all so mark the whole s.num subtree as modified. - s.num.setModifiedRecursively() + s.numPtr().setModifiedRecursively() } } return ret @@ -239,14 +283,14 @@ func (s *LabelValue) computeDiff(val *LabelValue) (ret bool) { // IsEqual performs deep comparison and returns true if struct is equal to val. func (e *LabelValue) IsEqual(val *LabelValue) bool { - if e.typ != val.typ { + if e.Type() != val.Type() { return false } - switch e.typ { + switch e.Type() { case LabelValueTypeStr: - return pkg.StringEqual(e.str, val.str) + return pkg.StringEqual(e.Str(), val.Str()) case LabelValueTypeNum: - return e.num.IsEqual(&val.num) + return e.numPtr().IsEqual(val.numPtr()) } return true @@ -255,15 +299,16 @@ func (e *LabelValue) IsEqual(val *LabelValue) bool { // CmpLabelValue performs deep comparison and returns an integer that // will be 0 if left == right, negative if left < right, positive if left > right. func CmpLabelValue(left, right *LabelValue) int { - c := pkg.Uint64Compare(uint64(left.typ), uint64(right.typ)) + c := pkg.Uint64Compare(uint64(left.Type()), uint64(right.Type())) if c != 0 { return c } - switch left.typ { + switch left.Type() { case LabelValueTypeStr: - return strings.Compare(left.str, right.str) + return strings.Compare(left.Str(), right.Str()) case LabelValueTypeNum: - return CmpNumValue(&left.num, &right.num) + return CmpNumValue( + left.numPtr(), right.numPtr()) } return 0 @@ -287,14 +332,14 @@ func (s *LabelValue) mutateRandom(random *rand.Rand, schem *schema.Schema, limit typeChanged = true } - switch s.typ { + switch s.Type() { case LabelValueTypeStr: if typeChanged || random.IntN(2) == 0 { s.SetStr(pkg.StringRandom(random)) } case LabelValueTypeNum: if typeChanged || random.IntN(2) == 0 { - s.num.mutateRandom(random, schem, limiter) + s.numPtr().mutateRandom(random, schem, limiter) } } } @@ -382,7 +427,7 @@ func (e *LabelValueEncoder) Reset() { // Encode encodes val into buf func (e *LabelValueEncoder) Encode(val *LabelValue) { - typ := val.typ + typ := val.Type() if uint(typ) > e.fieldCount { // The current field type is not supported in target schema. Encode the type as None. typ = LabelValueTypeNone @@ -397,10 +442,10 @@ func (e *LabelValueEncoder) Encode(val *LabelValue) { switch typ { case LabelValueTypeStr: // Encode Str - e.strEncoder.Encode(val.str) + e.strEncoder.Encode(val.Str()) case LabelValueTypeNum: // Encode Num - e.numEncoder.Encode(&val.num) + e.numEncoder.Encode(val.numPtr()) } } @@ -550,22 +595,34 @@ func (d *LabelValueDecoder) Decode(dstPtr *LabelValue) error { } dst := dstPtr - if dst.typ != LabelValueType(typ) { - dst.typ = LabelValueType(typ) + if dst.Type() != LabelValueType(typ) { // The type changed, we need to reset the contained value so that // it does not contain carry-over data from a previous record that // was of this same type. - dst.resetContained() + dst.clearValSetType(LabelValueType(typ)) + + switch LabelValueType(typ) { + case LabelValueTypeNum: + // Allocate Num + if err := d.allocators.allocSizeChecker.PrepAllocSize(uint(unsafe.Sizeof(NumValue{}))); err != nil { + return err + } + dst.allocNumAlloc(d.allocators) + } } // Decode selected field - switch dst.typ { + switch dst.Type() { case LabelValueTypeStr: // Decode Str - return d.strDecoder.Decode(&dst.str) + var v string + if err := d.strDecoder.Decode(&v); err != nil { + return err + } + dst.setStr(v) case LabelValueTypeNum: // Decode Num - return d.numDecoder.Decode(&dst.num) + return d.numDecoder.Decode(dst.numPtr()) } return nil } diff --git a/examples/profile/internal/profile/line.go b/examples/profile/internal/profile/line.go index b2245b25..5dac1f49 100644 --- a/examples/profile/internal/profile/line.go +++ b/examples/profile/internal/profile/line.go @@ -64,6 +64,12 @@ func (s *Line) initAlloc(parentModifiedFields *modifiedFields, parentModifiedBit s.function.initAlloc(&s.modifiedFields, fieldModifiedLineFunction, allocators) } +// Reset the struct to its initial state. +func (s *Line) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *Line) reset() { diff --git a/examples/profile/internal/profile/location.go b/examples/profile/internal/profile/location.go index f28d00e6..59b650a9 100644 --- a/examples/profile/internal/profile/location.go +++ b/examples/profile/internal/profile/location.go @@ -79,6 +79,12 @@ func (s *Location) initAlloc(parentModifiedFields *modifiedFields, parentModifie s.lines.initAlloc(&s.modifiedFields, fieldModifiedLocationLines, allocators) } +// Reset the struct to its initial state. +func (s *Location) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *Location) reset() { diff --git a/examples/profile/internal/profile/mapping.go b/examples/profile/internal/profile/mapping.go index 7d623e91..fd8f0b60 100644 --- a/examples/profile/internal/profile/mapping.go +++ b/examples/profile/internal/profile/mapping.go @@ -82,6 +82,12 @@ func (s *Mapping) initAlloc(parentModifiedFields *modifiedFields, parentModified } +// Reset the struct to its initial state. +func (s *Mapping) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *Mapping) reset() { diff --git a/examples/profile/internal/profile/numvalue.go b/examples/profile/internal/profile/numvalue.go index a50e3483..7be0cf36 100644 --- a/examples/profile/internal/profile/numvalue.go +++ b/examples/profile/internal/profile/numvalue.go @@ -57,6 +57,12 @@ func (s *NumValue) initAlloc(parentModifiedFields *modifiedFields, parentModifie } +// Reset the struct to its initial state. +func (s *NumValue) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *NumValue) reset() { diff --git a/examples/profile/internal/profile/profilemetadata.go b/examples/profile/internal/profile/profilemetadata.go index 6172c2ae..25f8763b 100644 --- a/examples/profile/internal/profile/profilemetadata.go +++ b/examples/profile/internal/profile/profilemetadata.go @@ -81,6 +81,12 @@ func (s *ProfileMetadata) initAlloc(parentModifiedFields *modifiedFields, parent s.defaultSampleType.initAlloc(&s.modifiedFields, fieldModifiedProfileMetadataDefaultSampleType, allocators) } +// Reset the struct to its initial state. +func (s *ProfileMetadata) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *ProfileMetadata) reset() { diff --git a/examples/profile/internal/profile/sample.go b/examples/profile/internal/profile/sample.go index 6cc74529..93f155ee 100644 --- a/examples/profile/internal/profile/sample.go +++ b/examples/profile/internal/profile/sample.go @@ -69,6 +69,12 @@ func (s *Sample) initAlloc(parentModifiedFields *modifiedFields, parentModifiedB s.labels.initAlloc(&s.modifiedFields, fieldModifiedSampleLabels, allocators) } +// Reset the struct to its initial state. +func (s *Sample) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *Sample) reset() { diff --git a/examples/profile/internal/profile/samplevalue.go b/examples/profile/internal/profile/samplevalue.go index 353dc898..2238a010 100644 --- a/examples/profile/internal/profile/samplevalue.go +++ b/examples/profile/internal/profile/samplevalue.go @@ -62,6 +62,12 @@ func (s *SampleValue) initAlloc(parentModifiedFields *modifiedFields, parentModi s.type_.initAlloc(&s.modifiedFields, fieldModifiedSampleValueType, allocators) } +// Reset the struct to its initial state. +func (s *SampleValue) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *SampleValue) reset() { diff --git a/examples/profile/internal/profile/samplevaluetype.go b/examples/profile/internal/profile/samplevaluetype.go index 23cccd8e..42e7387a 100644 --- a/examples/profile/internal/profile/samplevaluetype.go +++ b/examples/profile/internal/profile/samplevaluetype.go @@ -68,6 +68,12 @@ func (s *SampleValueType) initAlloc(parentModifiedFields *modifiedFields, parent } +// Reset the struct to its initial state. +func (s *SampleValueType) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *SampleValueType) reset() { diff --git a/go/otel/otelstef/anyvalue.go b/go/otel/otelstef/anyvalue.go index 2480becd..8920c8a6 100644 --- a/go/otel/otelstef/anyvalue.go +++ b/go/otel/otelstef/anyvalue.go @@ -18,16 +18,11 @@ var _ = codecs.StringEncoder{} // AnyValue is a oneof struct. type AnyValue struct { - // The current type of the oneof. - typ AnyValueType - - string string - bool bool - int64 int64 - float64 float64 - array AnyValueArray - kVList KeyValueList - bytes pkg.Bytes + // The low 8 bits hold the current type. The remaining 56 bits hold + // string/bytes length when a choice needs it. + typLen uint64 + ptr unsafe.Pointer + bits uint64 // Pointer to parent's modifiedFields parentModifiedFields *modifiedFields @@ -43,33 +38,33 @@ func (s *AnyValue) Init() { func (s *AnyValue) init(parentModifiedFields *modifiedFields, parentModifiedBit uint64) { s.parentModifiedFields = parentModifiedFields s.parentModifiedBit = parentModifiedBit - - s.array.init(parentModifiedFields, parentModifiedBit) - s.kVList.init(parentModifiedFields, parentModifiedBit) } func (s *AnyValue) initAlloc(parentModifiedFields *modifiedFields, parentModifiedBit uint64, allocators *Allocators) { s.parentModifiedFields = parentModifiedFields s.parentModifiedBit = parentModifiedBit +} - s.array.initAlloc(parentModifiedFields, parentModifiedBit, allocators) - s.kVList.initAlloc(parentModifiedFields, parentModifiedBit, allocators) +// clear the value and set the type. Must be followed by a "set" call which +// sets the value (except if the type is None). +func (s *AnyValue) clearValSetType(typ AnyValueType) { + s.ptr = nil + s.typLen = uint64(typ) + s.bits = 0 } // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *AnyValue) reset() { - s.typ = AnyValueTypeNone - // We don't need to reset the state of the field since that will be done - // when the type is changed, see SetType(). + s.clearValSetType(AnyValueTypeNone) } func (s *AnyValue) freeze() { - switch s.typ { + switch s.Type() { case AnyValueTypeArray: - s.array.freeze() + s.arrayPtr().freeze() case AnyValueTypeKVList: - s.kVList.freeze() + s.kVListPtr().freeze() } } @@ -79,8 +74,12 @@ func (s *AnyValue) freeze() { func (s *AnyValue) fixParent(parentModifiedFields *modifiedFields) { s.parentModifiedFields = parentModifiedFields - s.array.fixParent(parentModifiedFields) - s.kVList.fixParent(parentModifiedFields) + switch s.Type() { + case AnyValueTypeArray: + s.arrayPtr().fixParent(parentModifiedFields) + case AnyValueTypeKVList: + s.kVListPtr().fixParent(parentModifiedFields) + } } type AnyValueType byte @@ -97,117 +96,188 @@ const ( AnyValueTypeCount ) -// Type returns the type of the value currently contained in AnyValue. -func (s *AnyValue) Type() AnyValueType { - return s.typ +const ( + AnyValueTypLenTypeBits = 8 + AnyValueTypLenTypeMask = 1<> AnyValueTypLenTypeBits) } -// resetContained resets the currently contained value, if any. -// Normally used after switching to a different type to make sure -// the value contained is in blank state. -func (s *AnyValue) resetContained() { - switch s.typ { - case AnyValueTypeArray: - s.array.reset() - case AnyValueTypeKVList: - s.kVList.reset() +func (s *AnyValue) setLen(n int) { + if uint64(n) > AnyValueTypLenLenMask { + panic("AnyValue length exceeds 56 bits") } + s.typLen = (uint64(n) << AnyValueTypLenTypeBits) | (s.typLen & AnyValueTypLenTypeMask) +} + +// Type returns the type of the value currently contained in AnyValue. +func (s *AnyValue) Type() AnyValueType { + return AnyValueType(s.typLen & AnyValueTypLenTypeMask) } // SetType sets the type of the value currently contained in AnyValue. func (s *AnyValue) SetType(typ AnyValueType) { - if s.typ != typ { - s.typ = typ - s.resetContained() + if s.Type() != typ { + s.clearValSetType(typ) switch typ { + case AnyValueTypeArray: + s.allocArray() + case AnyValueTypeKVList: + s.allocKVList() } s.markParentModified() } } +func (s *AnyValue) setString(v string) { + s.ptr = unsafe.Pointer(unsafe.StringData(string(v))) + s.setLen(len(v)) +} + // String returns the value if the contained type is currently AnyValueTypeString. // The caller must check the type via Type() before attempting to call this function. func (s *AnyValue) String() string { - return s.string + return string(unsafe.String((*byte)(s.ptr), s.len())) } // SetString sets the value to the specified value and sets the type to AnyValueTypeString. func (s *AnyValue) SetString(v string) { - if s.typ != AnyValueTypeString || s.string != v { - s.string = v - s.typ = AnyValueTypeString + stored := v + if s.Type() != AnyValueTypeString || s.String() != stored { + s.clearValSetType(AnyValueTypeString) + s.setString(stored) s.parentModifiedFields.markModified(s.parentModifiedBit) } } +func (s *AnyValue) boolPtr() *bool { + return (*bool)(unsafe.Pointer(&s.bits)) +} + // Bool returns the value if the contained type is currently AnyValueTypeBool. // The caller must check the type via Type() before attempting to call this function. func (s *AnyValue) Bool() bool { - return s.bool + return (*s.boolPtr()) } // SetBool sets the value to the specified value and sets the type to AnyValueTypeBool. func (s *AnyValue) SetBool(v bool) { - if s.typ != AnyValueTypeBool || s.bool != v { - s.bool = v - s.typ = AnyValueTypeBool + stored := v + if s.Type() != AnyValueTypeBool || *s.boolPtr() != stored { + s.clearValSetType(AnyValueTypeBool) + *s.boolPtr() = stored s.parentModifiedFields.markModified(s.parentModifiedBit) } } +func (s *AnyValue) int64Ptr() *int64 { + return (*int64)(unsafe.Pointer(&s.bits)) +} + // Int64 returns the value if the contained type is currently AnyValueTypeInt64. // The caller must check the type via Type() before attempting to call this function. func (s *AnyValue) Int64() int64 { - return s.int64 + return (*s.int64Ptr()) } // SetInt64 sets the value to the specified value and sets the type to AnyValueTypeInt64. func (s *AnyValue) SetInt64(v int64) { - if s.typ != AnyValueTypeInt64 || s.int64 != v { - s.int64 = v - s.typ = AnyValueTypeInt64 + stored := v + if s.Type() != AnyValueTypeInt64 || *s.int64Ptr() != stored { + s.clearValSetType(AnyValueTypeInt64) + *s.int64Ptr() = stored s.parentModifiedFields.markModified(s.parentModifiedBit) } } +func (s *AnyValue) float64Ptr() *float64 { + return (*float64)(unsafe.Pointer(&s.bits)) +} + // Float64 returns the value if the contained type is currently AnyValueTypeFloat64. // The caller must check the type via Type() before attempting to call this function. func (s *AnyValue) Float64() float64 { - return s.float64 + return (*s.float64Ptr()) } // SetFloat64 sets the value to the specified value and sets the type to AnyValueTypeFloat64. func (s *AnyValue) SetFloat64(v float64) { - if s.typ != AnyValueTypeFloat64 || s.float64 != v { - s.float64 = v - s.typ = AnyValueTypeFloat64 + stored := v + if s.Type() != AnyValueTypeFloat64 || *s.float64Ptr() != stored { + s.clearValSetType(AnyValueTypeFloat64) + *s.float64Ptr() = stored s.parentModifiedFields.markModified(s.parentModifiedBit) } } +func (s *AnyValue) arrayPtr() *AnyValueArray { + return (*AnyValueArray)(s.ptr) +} + +func (s *AnyValue) allocArray() *AnyValueArray { + v := &AnyValueArray{} + v.init(s.parentModifiedFields, s.parentModifiedBit) + s.ptr = unsafe.Pointer(v) + return v +} + +func (s *AnyValue) allocArrayAlloc(allocators *Allocators) *AnyValueArray { + v := &AnyValueArray{} + v.initAlloc(s.parentModifiedFields, s.parentModifiedBit, allocators) + s.ptr = unsafe.Pointer(v) + return v +} + // Array returns the value if the contained type is currently AnyValueTypeArray. // The caller must check the type via Type() before attempting to call this function. func (s *AnyValue) Array() *AnyValueArray { - return &s.array + return s.arrayPtr() +} + +func (s *AnyValue) kVListPtr() *KeyValueList { + return (*KeyValueList)(s.ptr) +} + +func (s *AnyValue) allocKVList() *KeyValueList { + v := &KeyValueList{} + v.init(s.parentModifiedFields, s.parentModifiedBit) + s.ptr = unsafe.Pointer(v) + return v +} + +func (s *AnyValue) allocKVListAlloc(allocators *Allocators) *KeyValueList { + v := &KeyValueList{} + v.initAlloc(s.parentModifiedFields, s.parentModifiedBit, allocators) + s.ptr = unsafe.Pointer(v) + return v } // KVList returns the value if the contained type is currently AnyValueTypeKVList. // The caller must check the type via Type() before attempting to call this function. func (s *AnyValue) KVList() *KeyValueList { - return &s.kVList + return s.kVListPtr() +} + +func (s *AnyValue) setBytes(v pkg.Bytes) { + s.ptr = unsafe.Pointer(unsafe.StringData(string(v))) + s.setLen(len(v)) } // Bytes returns the value if the contained type is currently AnyValueTypeBytes. // The caller must check the type via Type() before attempting to call this function. func (s *AnyValue) Bytes() pkg.Bytes { - return s.bytes + return pkg.Bytes(unsafe.String((*byte)(s.ptr), s.len())) } // SetBytes sets the value to the specified value and sets the type to AnyValueTypeBytes. func (s *AnyValue) SetBytes(v pkg.Bytes) { - if s.typ != AnyValueTypeBytes || s.bytes != v { - s.bytes = v - s.typ = AnyValueTypeBytes + stored := v + if s.Type() != AnyValueTypeBytes || s.Bytes() != stored { + s.clearValSetType(AnyValueTypeBytes) + s.setBytes(stored) s.parentModifiedFields.markModified(s.parentModifiedBit) } } @@ -225,22 +295,26 @@ func (s *AnyValue) cloneShared(allocators *Allocators) *AnyValue { func (s *AnyValue) Clone(allocators *Allocators) *AnyValue { allocators.allocSizeChecker.AddAllocSize(uint(unsafe.Sizeof(AnyValue{}))) c := allocators.AnyValue.Alloc() - c.typ = s.typ - switch s.typ { + c.clearValSetType(s.Type()) + switch s.Type() { case AnyValueTypeString: - c.string = s.string + c.setString(s.String()) case AnyValueTypeBool: - c.bool = s.bool + *c.boolPtr() = *s.boolPtr() case AnyValueTypeInt64: - c.int64 = s.int64 + *c.int64Ptr() = *s.int64Ptr() case AnyValueTypeFloat64: - c.float64 = s.float64 + *c.float64Ptr() = *s.float64Ptr() case AnyValueTypeArray: - copyToNewAnyValueArray(&c.array, &s.array, allocators) + allocators.allocSizeChecker.AddAllocSize(uint(unsafe.Sizeof(AnyValueArray{}))) + c.allocArrayAlloc(allocators) + copyToNewAnyValueArray(c.arrayPtr(), s.arrayPtr(), allocators) case AnyValueTypeKVList: - copyToNewKeyValueList(&c.kVList, &s.kVList, allocators) + allocators.allocSizeChecker.AddAllocSize(uint(unsafe.Sizeof(KeyValueList{}))) + c.allocKVListAlloc(allocators) + copyToNewKeyValueList(c.kVListPtr(), s.kVListPtr(), allocators) case AnyValueTypeBytes: - c.bytes = s.bytes + c.setBytes(s.Bytes()) } return c } @@ -248,75 +322,87 @@ func (s *AnyValue) Clone(allocators *Allocators) *AnyValue { // ByteSize returns approximate memory usage in bytes. Used to calculate // memory used by dictionaries. func (s *AnyValue) byteSize() uint { - return uint(unsafe.Sizeof(*s)) + - s.array.byteSize() + s.kVList.byteSize() + 0 + size := uint(unsafe.Sizeof(*s)) + switch s.Type() { + case AnyValueTypeArray: + size += s.arrayPtr().byteSize() + case AnyValueTypeKVList: + size += s.kVListPtr().byteSize() + } + return size } // Copy from src to dst, overwriting existing data in dst. func copyAnyValue(dst *AnyValue, src *AnyValue) { - switch src.typ { + switch src.Type() { case AnyValueTypeString: - dst.SetString(src.string) + dst.SetString(src.String()) case AnyValueTypeBool: - dst.SetBool(src.bool) + dst.SetBool((*src.boolPtr())) case AnyValueTypeInt64: - dst.SetInt64(src.int64) + dst.SetInt64((*src.int64Ptr())) case AnyValueTypeFloat64: - dst.SetFloat64(src.float64) + dst.SetFloat64((*src.float64Ptr())) case AnyValueTypeArray: - dst.SetType(src.typ) - copyAnyValueArray(&dst.array, &src.array) + dst.SetType(src.Type()) + copyAnyValueArray(dst.arrayPtr(), src.arrayPtr()) case AnyValueTypeKVList: - dst.SetType(src.typ) - copyKeyValueList(&dst.kVList, &src.kVList) + dst.SetType(src.Type()) + copyKeyValueList(dst.kVListPtr(), src.kVListPtr()) case AnyValueTypeBytes: - dst.SetBytes(src.bytes) + dst.SetBytes(src.Bytes()) case AnyValueTypeNone: - if dst.typ != AnyValueTypeNone { - dst.typ = AnyValueTypeNone + if dst.Type() != AnyValueTypeNone { + dst.clearValSetType(AnyValueTypeNone) dst.markParentModified() } default: - panic("copyAnyValue: unexpected type: " + fmt.Sprint(src.typ)) + panic("copyAnyValue: unexpected type: " + fmt.Sprint(src.Type())) } } // Copy from src to dst. dst is assumed to be just inited. func copyToNewAnyValue(dst *AnyValue, src *AnyValue, allocators *Allocators) { - dst.typ = src.typ - switch src.typ { + dst.typLen = uint64(src.Type()) + switch src.Type() { case AnyValueTypeString: - if dst.string != src.string { - dst.string = src.string + srcVal := src.String() + if string(srcVal) != "" { + dst.setString(srcVal) dst.parentModifiedFields.markModified(dst.parentModifiedBit) } case AnyValueTypeBool: - if dst.bool != src.bool { - dst.bool = src.bool + if *dst.boolPtr() != *src.boolPtr() { + *dst.boolPtr() = *src.boolPtr() dst.parentModifiedFields.markModified(dst.parentModifiedBit) } case AnyValueTypeInt64: - if dst.int64 != src.int64 { - dst.int64 = src.int64 + if *dst.int64Ptr() != *src.int64Ptr() { + *dst.int64Ptr() = *src.int64Ptr() dst.parentModifiedFields.markModified(dst.parentModifiedBit) } case AnyValueTypeFloat64: - if dst.float64 != src.float64 { - dst.float64 = src.float64 + if *dst.float64Ptr() != *src.float64Ptr() { + *dst.float64Ptr() = *src.float64Ptr() dst.parentModifiedFields.markModified(dst.parentModifiedBit) } case AnyValueTypeArray: - copyToNewAnyValueArray(&dst.array, &src.array, allocators) + allocators.allocSizeChecker.AddAllocSize(uint(unsafe.Sizeof(AnyValueArray{}))) + dst.allocArrayAlloc(allocators) + copyToNewAnyValueArray(dst.arrayPtr(), src.arrayPtr(), allocators) case AnyValueTypeKVList: - copyToNewKeyValueList(&dst.kVList, &src.kVList, allocators) + allocators.allocSizeChecker.AddAllocSize(uint(unsafe.Sizeof(KeyValueList{}))) + dst.allocKVListAlloc(allocators) + copyToNewKeyValueList(dst.kVListPtr(), src.kVListPtr(), allocators) case AnyValueTypeBytes: - if dst.bytes != src.bytes { - dst.bytes = src.bytes + srcVal := src.Bytes() + if string(srcVal) != "" { + dst.setBytes(srcVal) dst.parentModifiedFields.markModified(dst.parentModifiedBit) } case AnyValueTypeNone: default: - panic("copyAnyValue: unexpected type: " + fmt.Sprint(src.typ)) + panic("copyAnyValue: unexpected type: " + fmt.Sprint(src.Type())) } } @@ -330,52 +416,52 @@ func (s *AnyValue) markParentModified() { } func (s *AnyValue) setModifiedRecursively() { - switch s.typ { + switch s.Type() { case AnyValueTypeArray: - s.array.setModifiedRecursively() + s.arrayPtr().setModifiedRecursively() case AnyValueTypeKVList: - s.kVList.setModifiedRecursively() + s.kVListPtr().setModifiedRecursively() } } func (s *AnyValue) setUnmodifiedRecursively() { - switch s.typ { + switch s.Type() { case AnyValueTypeArray: - s.array.setUnmodifiedRecursively() + s.arrayPtr().setUnmodifiedRecursively() case AnyValueTypeKVList: - s.kVList.setUnmodifiedRecursively() + s.kVListPtr().setUnmodifiedRecursively() } } // computeDiff compares s and val and returns true if they differ. // All fields that are different in s will be marked as modified. func (s *AnyValue) computeDiff(val *AnyValue) (ret bool) { - if s.typ == val.typ { - switch s.typ { + if s.Type() == val.Type() { + switch s.Type() { case AnyValueTypeString: - ret = s.string != val.string + ret = s.String() != val.String() case AnyValueTypeBool: - ret = s.bool != val.bool + ret = s.Bool() != val.Bool() case AnyValueTypeInt64: - ret = s.int64 != val.int64 + ret = s.Int64() != val.Int64() case AnyValueTypeFloat64: - ret = s.float64 != val.float64 + ret = s.Float64() != val.Float64() case AnyValueTypeArray: - ret = s.array.computeDiff(&val.array) + ret = s.arrayPtr().computeDiff(val.arrayPtr()) case AnyValueTypeKVList: - ret = s.kVList.computeDiff(&val.kVList) + ret = s.kVListPtr().computeDiff(val.kVListPtr()) case AnyValueTypeBytes: - ret = s.bytes != val.bytes + ret = s.Bytes() != val.Bytes() } } else { ret = true - switch s.typ { + switch s.Type() { case AnyValueTypeArray: // val.array doesn't exist at all so mark the whole s.array subtree as modified. - s.array.setModifiedRecursively() + s.arrayPtr().setModifiedRecursively() case AnyValueTypeKVList: // val.kVList doesn't exist at all so mark the whole s.kVList subtree as modified. - s.kVList.setModifiedRecursively() + s.kVListPtr().setModifiedRecursively() } } return ret @@ -383,24 +469,24 @@ func (s *AnyValue) computeDiff(val *AnyValue) (ret bool) { // IsEqual performs deep comparison and returns true if struct is equal to val. func (e *AnyValue) IsEqual(val *AnyValue) bool { - if e.typ != val.typ { + if e.Type() != val.Type() { return false } - switch e.typ { + switch e.Type() { case AnyValueTypeString: - return pkg.StringEqual(e.string, val.string) + return pkg.StringEqual(e.String(), val.String()) case AnyValueTypeBool: - return pkg.BoolEqual(e.bool, val.bool) + return pkg.BoolEqual(*e.boolPtr(), *val.boolPtr()) case AnyValueTypeInt64: - return pkg.Int64Equal(e.int64, val.int64) + return pkg.Int64Equal(*e.int64Ptr(), *val.int64Ptr()) case AnyValueTypeFloat64: - return pkg.Float64Equal(e.float64, val.float64) + return pkg.Float64Equal(*e.float64Ptr(), *val.float64Ptr()) case AnyValueTypeArray: - return e.array.IsEqual(&val.array) + return e.arrayPtr().IsEqual(val.arrayPtr()) case AnyValueTypeKVList: - return e.kVList.IsEqual(&val.kVList) + return e.kVListPtr().IsEqual(val.kVListPtr()) case AnyValueTypeBytes: - return pkg.BytesEqual(e.bytes, val.bytes) + return pkg.BytesEqual(e.Bytes(), val.Bytes()) } return true @@ -409,25 +495,27 @@ func (e *AnyValue) IsEqual(val *AnyValue) bool { // CmpAnyValue performs deep comparison and returns an integer that // will be 0 if left == right, negative if left < right, positive if left > right. func CmpAnyValue(left, right *AnyValue) int { - c := pkg.Uint64Compare(uint64(left.typ), uint64(right.typ)) + c := pkg.Uint64Compare(uint64(left.Type()), uint64(right.Type())) if c != 0 { return c } - switch left.typ { + switch left.Type() { case AnyValueTypeString: - return strings.Compare(left.string, right.string) + return strings.Compare(left.String(), right.String()) case AnyValueTypeBool: - return pkg.BoolCompare(left.bool, right.bool) + return pkg.BoolCompare(*left.boolPtr(), *right.boolPtr()) case AnyValueTypeInt64: - return pkg.Int64Compare(left.int64, right.int64) + return pkg.Int64Compare(*left.int64Ptr(), *right.int64Ptr()) case AnyValueTypeFloat64: - return pkg.Float64Compare(left.float64, right.float64) + return pkg.Float64Compare(*left.float64Ptr(), *right.float64Ptr()) case AnyValueTypeArray: - return CmpAnyValueArray(&left.array, &right.array) + return CmpAnyValueArray( + left.arrayPtr(), right.arrayPtr()) case AnyValueTypeKVList: - return CmpKeyValueList(&left.kVList, &right.kVList) + return CmpKeyValueList( + left.kVListPtr(), right.kVListPtr()) case AnyValueTypeBytes: - return pkg.BytesCompare(left.bytes, right.bytes) + return pkg.BytesCompare(left.Bytes(), right.Bytes()) } return 0 @@ -451,7 +539,7 @@ func (s *AnyValue) mutateRandom(random *rand.Rand, schem *schema.Schema, limiter typeChanged = true } - switch s.typ { + switch s.Type() { case AnyValueTypeString: if typeChanged || random.IntN(2) == 0 { s.SetString(pkg.StringRandom(random)) @@ -470,11 +558,11 @@ func (s *AnyValue) mutateRandom(random *rand.Rand, schem *schema.Schema, limiter } case AnyValueTypeArray: if typeChanged || random.IntN(2) == 0 { - s.array.mutateRandom(random, schem, limiter) + s.arrayPtr().mutateRandom(random, schem, limiter) } case AnyValueTypeKVList: if typeChanged || random.IntN(2) == 0 { - s.kVList.mutateRandom(random, schem, limiter) + s.kVListPtr().mutateRandom(random, schem, limiter) } case AnyValueTypeBytes: if typeChanged || random.IntN(2) == 0 { @@ -657,7 +745,7 @@ func (e *AnyValueEncoder) Reset() { // Encode encodes val into buf func (e *AnyValueEncoder) Encode(val *AnyValue) { - typ := val.typ + typ := val.Type() if uint(typ) > e.fieldCount { // The current field type is not supported in target schema. Encode the type as None. typ = AnyValueTypeNone @@ -672,25 +760,25 @@ func (e *AnyValueEncoder) Encode(val *AnyValue) { switch typ { case AnyValueTypeString: // Encode String - e.stringEncoder.Encode(val.string) + e.stringEncoder.Encode(val.String()) case AnyValueTypeBool: // Encode Bool - e.boolEncoder.Encode(val.bool) + e.boolEncoder.Encode(*val.boolPtr()) case AnyValueTypeInt64: // Encode Int64 - e.int64Encoder.Encode(val.int64) + e.int64Encoder.Encode(*val.int64Ptr()) case AnyValueTypeFloat64: // Encode Float64 - e.float64Encoder.Encode(val.float64) + e.float64Encoder.Encode(*val.float64Ptr()) case AnyValueTypeArray: // Encode Array - e.arrayEncoder.Encode(&val.array) + e.arrayEncoder.Encode(val.arrayPtr()) case AnyValueTypeKVList: // Encode KVList - e.kVListEncoder.Encode(&val.kVList) + e.kVListEncoder.Encode(val.kVListPtr()) case AnyValueTypeBytes: // Encode Bytes - e.bytesEncoder.Encode(val.bytes) + e.bytesEncoder.Encode(val.Bytes()) } } @@ -986,37 +1074,59 @@ func (d *AnyValueDecoder) Decode(dstPtr *AnyValue) error { } dst := dstPtr - if dst.typ != AnyValueType(typ) { - dst.typ = AnyValueType(typ) + if dst.Type() != AnyValueType(typ) { // The type changed, we need to reset the contained value so that // it does not contain carry-over data from a previous record that // was of this same type. - dst.resetContained() + dst.clearValSetType(AnyValueType(typ)) + + switch AnyValueType(typ) { + case AnyValueTypeArray: + // Allocate Array + if err := d.allocators.allocSizeChecker.PrepAllocSize(uint(unsafe.Sizeof(AnyValueArray{}))); err != nil { + return err + } + dst.allocArrayAlloc(d.allocators) + case AnyValueTypeKVList: + // Allocate KVList + if err := d.allocators.allocSizeChecker.PrepAllocSize(uint(unsafe.Sizeof(KeyValueList{}))); err != nil { + return err + } + dst.allocKVListAlloc(d.allocators) + } } // Decode selected field - switch dst.typ { + switch dst.Type() { case AnyValueTypeString: // Decode String - return d.stringDecoder.Decode(&dst.string) + var v string + if err := d.stringDecoder.Decode(&v); err != nil { + return err + } + dst.setString(v) case AnyValueTypeBool: // Decode Bool - return d.boolDecoder.Decode(&dst.bool) + return d.boolDecoder.Decode(dst.boolPtr()) case AnyValueTypeInt64: // Decode Int64 - return d.int64Decoder.Decode(&dst.int64) + return d.int64Decoder.Decode(dst.int64Ptr()) case AnyValueTypeFloat64: // Decode Float64 - return d.float64Decoder.Decode(&dst.float64) + return d.float64Decoder.Decode(dst.float64Ptr()) case AnyValueTypeArray: // Decode Array - return d.arrayDecoder.Decode(&dst.array) + return d.arrayDecoder.Decode(dst.arrayPtr()) case AnyValueTypeKVList: // Decode KVList - return d.kVListDecoder.Decode(&dst.kVList) + return d.kVListDecoder.Decode(dst.kVListPtr()) case AnyValueTypeBytes: // Decode Bytes - return d.bytesDecoder.Decode(&dst.bytes) + var v pkg.Bytes + if err := d.bytesDecoder.Decode(&v); err != nil { + return err + } + dst.setBytes(v) } return nil } diff --git a/go/otel/otelstef/envelope.go b/go/otel/otelstef/envelope.go index 0ecf20aa..2862a67a 100644 --- a/go/otel/otelstef/envelope.go +++ b/go/otel/otelstef/envelope.go @@ -57,6 +57,12 @@ func (s *Envelope) initAlloc(parentModifiedFields *modifiedFields, parentModifie s.attributes.initAlloc(&s.modifiedFields, fieldModifiedEnvelopeAttributes, allocators) } +// Reset the struct to its initial state. +func (s *Envelope) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *Envelope) reset() { diff --git a/go/otel/otelstef/event.go b/go/otel/otelstef/event.go index 2f904eae..f371c7ab 100644 --- a/go/otel/otelstef/event.go +++ b/go/otel/otelstef/event.go @@ -63,6 +63,12 @@ func (s *Event) initAlloc(parentModifiedFields *modifiedFields, parentModifiedBi s.attributes.initAlloc(&s.modifiedFields, fieldModifiedEventAttributes, allocators) } +// Reset the struct to its initial state. +func (s *Event) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *Event) reset() { diff --git a/go/otel/otelstef/exemplar.go b/go/otel/otelstef/exemplar.go index 7b5f20ac..bd7ea3fb 100644 --- a/go/otel/otelstef/exemplar.go +++ b/go/otel/otelstef/exemplar.go @@ -67,6 +67,12 @@ func (s *Exemplar) initAlloc(parentModifiedFields *modifiedFields, parentModifie s.filteredAttributes.initAlloc(&s.modifiedFields, fieldModifiedExemplarFilteredAttributes, allocators) } +// Reset the struct to its initial state. +func (s *Exemplar) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *Exemplar) reset() { diff --git a/go/otel/otelstef/exemplarvalue.go b/go/otel/otelstef/exemplarvalue.go index e323dd92..ff9d6c87 100644 --- a/go/otel/otelstef/exemplarvalue.go +++ b/go/otel/otelstef/exemplarvalue.go @@ -18,11 +18,11 @@ var _ = codecs.StringEncoder{} // ExemplarValue is a oneof struct. type ExemplarValue struct { - // The current type of the oneof. - typ ExemplarValueType + // The low 8 bits hold the current type. The remaining 56 bits hold + // string/bytes length when a choice needs it. + typLen uint64 - int64 int64 - float64 float64 + bits uint64 // Pointer to parent's modifiedFields parentModifiedFields *modifiedFields @@ -38,25 +38,28 @@ func (s *ExemplarValue) Init() { func (s *ExemplarValue) init(parentModifiedFields *modifiedFields, parentModifiedBit uint64) { s.parentModifiedFields = parentModifiedFields s.parentModifiedBit = parentModifiedBit - } func (s *ExemplarValue) initAlloc(parentModifiedFields *modifiedFields, parentModifiedBit uint64, allocators *Allocators) { s.parentModifiedFields = parentModifiedFields s.parentModifiedBit = parentModifiedBit +} +// clear the value and set the type. Must be followed by a "set" call which +// sets the value (except if the type is None). +func (s *ExemplarValue) clearValSetType(typ ExemplarValueType) { + s.typLen = uint64(typ) + s.bits = 0 } // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *ExemplarValue) reset() { - s.typ = ExemplarValueTypeNone - // We don't need to reset the state of the field since that will be done - // when the type is changed, see SetType(). + s.clearValSetType(ExemplarValueTypeNone) } func (s *ExemplarValue) freeze() { - switch s.typ { + switch s.Type() { } } @@ -66,6 +69,8 @@ func (s *ExemplarValue) freeze() { func (s *ExemplarValue) fixParent(parentModifiedFields *modifiedFields) { s.parentModifiedFields = parentModifiedFields + switch s.Type() { + } } type ExemplarValueType byte @@ -77,56 +82,74 @@ const ( ExemplarValueTypeCount ) -// Type returns the type of the value currently contained in ExemplarValue. -func (s *ExemplarValue) Type() ExemplarValueType { - return s.typ +const ( + ExemplarValueTypLenTypeBits = 8 + ExemplarValueTypLenTypeMask = 1<> ExemplarValueTypLenTypeBits) } -// resetContained resets the currently contained value, if any. -// Normally used after switching to a different type to make sure -// the value contained is in blank state. -func (s *ExemplarValue) resetContained() { - switch s.typ { +func (s *ExemplarValue) setLen(n int) { + if uint64(n) > ExemplarValueTypLenLenMask { + panic("ExemplarValue length exceeds 56 bits") } + s.typLen = (uint64(n) << ExemplarValueTypLenTypeBits) | (s.typLen & ExemplarValueTypLenTypeMask) +} + +// Type returns the type of the value currently contained in ExemplarValue. +func (s *ExemplarValue) Type() ExemplarValueType { + return ExemplarValueType(s.typLen & ExemplarValueTypLenTypeMask) } // SetType sets the type of the value currently contained in ExemplarValue. func (s *ExemplarValue) SetType(typ ExemplarValueType) { - if s.typ != typ { - s.typ = typ - s.resetContained() + if s.Type() != typ { + s.clearValSetType(typ) switch typ { } s.markParentModified() } } +func (s *ExemplarValue) int64Ptr() *int64 { + return (*int64)(unsafe.Pointer(&s.bits)) +} + // Int64 returns the value if the contained type is currently ExemplarValueTypeInt64. // The caller must check the type via Type() before attempting to call this function. func (s *ExemplarValue) Int64() int64 { - return s.int64 + return (*s.int64Ptr()) } // SetInt64 sets the value to the specified value and sets the type to ExemplarValueTypeInt64. func (s *ExemplarValue) SetInt64(v int64) { - if s.typ != ExemplarValueTypeInt64 || s.int64 != v { - s.int64 = v - s.typ = ExemplarValueTypeInt64 + stored := v + if s.Type() != ExemplarValueTypeInt64 || *s.int64Ptr() != stored { + s.clearValSetType(ExemplarValueTypeInt64) + *s.int64Ptr() = stored s.parentModifiedFields.markModified(s.parentModifiedBit) } } +func (s *ExemplarValue) float64Ptr() *float64 { + return (*float64)(unsafe.Pointer(&s.bits)) +} + // Float64 returns the value if the contained type is currently ExemplarValueTypeFloat64. // The caller must check the type via Type() before attempting to call this function. func (s *ExemplarValue) Float64() float64 { - return s.float64 + return (*s.float64Ptr()) } // SetFloat64 sets the value to the specified value and sets the type to ExemplarValueTypeFloat64. func (s *ExemplarValue) SetFloat64(v float64) { - if s.typ != ExemplarValueTypeFloat64 || s.float64 != v { - s.float64 = v - s.typ = ExemplarValueTypeFloat64 + stored := v + if s.Type() != ExemplarValueTypeFloat64 || *s.float64Ptr() != stored { + s.clearValSetType(ExemplarValueTypeFloat64) + *s.float64Ptr() = stored s.parentModifiedFields.markModified(s.parentModifiedBit) } } @@ -143,12 +166,12 @@ func (s *ExemplarValue) cloneShared(allocators *Allocators) ExemplarValue { func (s *ExemplarValue) Clone(allocators *Allocators) ExemplarValue { c := ExemplarValue{} - c.typ = s.typ - switch s.typ { + c.clearValSetType(s.Type()) + switch s.Type() { case ExemplarValueTypeInt64: - c.int64 = s.int64 + *c.int64Ptr() = *s.int64Ptr() case ExemplarValueTypeFloat64: - c.float64 = s.float64 + *c.float64Ptr() = *s.float64Ptr() } return c } @@ -156,44 +179,46 @@ func (s *ExemplarValue) Clone(allocators *Allocators) ExemplarValue { // ByteSize returns approximate memory usage in bytes. Used to calculate // memory used by dictionaries. func (s *ExemplarValue) byteSize() uint { - return uint(unsafe.Sizeof(*s)) + - 0 + size := uint(unsafe.Sizeof(*s)) + switch s.Type() { + } + return size } // Copy from src to dst, overwriting existing data in dst. func copyExemplarValue(dst *ExemplarValue, src *ExemplarValue) { - switch src.typ { + switch src.Type() { case ExemplarValueTypeInt64: - dst.SetInt64(src.int64) + dst.SetInt64((*src.int64Ptr())) case ExemplarValueTypeFloat64: - dst.SetFloat64(src.float64) + dst.SetFloat64((*src.float64Ptr())) case ExemplarValueTypeNone: - if dst.typ != ExemplarValueTypeNone { - dst.typ = ExemplarValueTypeNone + if dst.Type() != ExemplarValueTypeNone { + dst.clearValSetType(ExemplarValueTypeNone) dst.markParentModified() } default: - panic("copyExemplarValue: unexpected type: " + fmt.Sprint(src.typ)) + panic("copyExemplarValue: unexpected type: " + fmt.Sprint(src.Type())) } } // Copy from src to dst. dst is assumed to be just inited. func copyToNewExemplarValue(dst *ExemplarValue, src *ExemplarValue, allocators *Allocators) { - dst.typ = src.typ - switch src.typ { + dst.typLen = uint64(src.Type()) + switch src.Type() { case ExemplarValueTypeInt64: - if dst.int64 != src.int64 { - dst.int64 = src.int64 + if *dst.int64Ptr() != *src.int64Ptr() { + *dst.int64Ptr() = *src.int64Ptr() dst.parentModifiedFields.markModified(dst.parentModifiedBit) } case ExemplarValueTypeFloat64: - if dst.float64 != src.float64 { - dst.float64 = src.float64 + if *dst.float64Ptr() != *src.float64Ptr() { + *dst.float64Ptr() = *src.float64Ptr() dst.parentModifiedFields.markModified(dst.parentModifiedBit) } case ExemplarValueTypeNone: default: - panic("copyExemplarValue: unexpected type: " + fmt.Sprint(src.typ)) + panic("copyExemplarValue: unexpected type: " + fmt.Sprint(src.Type())) } } @@ -207,28 +232,28 @@ func (s *ExemplarValue) markParentModified() { } func (s *ExemplarValue) setModifiedRecursively() { - switch s.typ { + switch s.Type() { } } func (s *ExemplarValue) setUnmodifiedRecursively() { - switch s.typ { + switch s.Type() { } } // computeDiff compares s and val and returns true if they differ. // All fields that are different in s will be marked as modified. func (s *ExemplarValue) computeDiff(val *ExemplarValue) (ret bool) { - if s.typ == val.typ { - switch s.typ { + if s.Type() == val.Type() { + switch s.Type() { case ExemplarValueTypeInt64: - ret = s.int64 != val.int64 + ret = s.Int64() != val.Int64() case ExemplarValueTypeFloat64: - ret = s.float64 != val.float64 + ret = s.Float64() != val.Float64() } } else { ret = true - switch s.typ { + switch s.Type() { } } return ret @@ -236,14 +261,14 @@ func (s *ExemplarValue) computeDiff(val *ExemplarValue) (ret bool) { // IsEqual performs deep comparison and returns true if struct is equal to val. func (e *ExemplarValue) IsEqual(val *ExemplarValue) bool { - if e.typ != val.typ { + if e.Type() != val.Type() { return false } - switch e.typ { + switch e.Type() { case ExemplarValueTypeInt64: - return pkg.Int64Equal(e.int64, val.int64) + return pkg.Int64Equal(*e.int64Ptr(), *val.int64Ptr()) case ExemplarValueTypeFloat64: - return pkg.Float64Equal(e.float64, val.float64) + return pkg.Float64Equal(*e.float64Ptr(), *val.float64Ptr()) } return true @@ -252,15 +277,15 @@ func (e *ExemplarValue) IsEqual(val *ExemplarValue) bool { // CmpExemplarValue performs deep comparison and returns an integer that // will be 0 if left == right, negative if left < right, positive if left > right. func CmpExemplarValue(left, right *ExemplarValue) int { - c := pkg.Uint64Compare(uint64(left.typ), uint64(right.typ)) + c := pkg.Uint64Compare(uint64(left.Type()), uint64(right.Type())) if c != 0 { return c } - switch left.typ { + switch left.Type() { case ExemplarValueTypeInt64: - return pkg.Int64Compare(left.int64, right.int64) + return pkg.Int64Compare(*left.int64Ptr(), *right.int64Ptr()) case ExemplarValueTypeFloat64: - return pkg.Float64Compare(left.float64, right.float64) + return pkg.Float64Compare(*left.float64Ptr(), *right.float64Ptr()) } return 0 @@ -284,7 +309,7 @@ func (s *ExemplarValue) mutateRandom(random *rand.Rand, schem *schema.Schema, li typeChanged = true } - switch s.typ { + switch s.Type() { case ExemplarValueTypeInt64: if typeChanged || random.IntN(2) == 0 { s.SetInt64(pkg.Int64Random(random)) @@ -366,7 +391,7 @@ func (e *ExemplarValueEncoder) Reset() { // Encode encodes val into buf func (e *ExemplarValueEncoder) Encode(val *ExemplarValue) { - typ := val.typ + typ := val.Type() if uint(typ) > e.fieldCount { // The current field type is not supported in target schema. Encode the type as None. typ = ExemplarValueTypeNone @@ -381,10 +406,10 @@ func (e *ExemplarValueEncoder) Encode(val *ExemplarValue) { switch typ { case ExemplarValueTypeInt64: // Encode Int64 - e.int64Encoder.Encode(val.int64) + e.int64Encoder.Encode(*val.int64Ptr()) case ExemplarValueTypeFloat64: // Encode Float64 - e.float64Encoder.Encode(val.float64) + e.float64Encoder.Encode(*val.float64Ptr()) } } @@ -518,22 +543,24 @@ func (d *ExemplarValueDecoder) Decode(dstPtr *ExemplarValue) error { } dst := dstPtr - if dst.typ != ExemplarValueType(typ) { - dst.typ = ExemplarValueType(typ) + if dst.Type() != ExemplarValueType(typ) { // The type changed, we need to reset the contained value so that // it does not contain carry-over data from a previous record that // was of this same type. - dst.resetContained() + dst.clearValSetType(ExemplarValueType(typ)) + + switch ExemplarValueType(typ) { + } } // Decode selected field - switch dst.typ { + switch dst.Type() { case ExemplarValueTypeInt64: // Decode Int64 - return d.int64Decoder.Decode(&dst.int64) + return d.int64Decoder.Decode(dst.int64Ptr()) case ExemplarValueTypeFloat64: // Decode Float64 - return d.float64Decoder.Decode(&dst.float64) + return d.float64Decoder.Decode(dst.float64Ptr()) } return nil } diff --git a/go/otel/otelstef/exphistogrambuckets.go b/go/otel/otelstef/exphistogrambuckets.go index 9b84a719..dc06b7a6 100644 --- a/go/otel/otelstef/exphistogrambuckets.go +++ b/go/otel/otelstef/exphistogrambuckets.go @@ -59,6 +59,12 @@ func (s *ExpHistogramBuckets) initAlloc(parentModifiedFields *modifiedFields, pa s.bucketCounts.initAlloc(&s.modifiedFields, fieldModifiedExpHistogramBucketsBucketCounts, allocators) } +// Reset the struct to its initial state. +func (s *ExpHistogramBuckets) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *ExpHistogramBuckets) reset() { diff --git a/go/otel/otelstef/exphistogramvalue.go b/go/otel/otelstef/exphistogramvalue.go index d0df7ce4..73151ea8 100644 --- a/go/otel/otelstef/exphistogramvalue.go +++ b/go/otel/otelstef/exphistogramvalue.go @@ -86,6 +86,12 @@ func (s *ExpHistogramValue) initAlloc(parentModifiedFields *modifiedFields, pare s.negativeBuckets.initAlloc(&s.modifiedFields, fieldModifiedExpHistogramValueNegativeBuckets, allocators) } +// Reset the struct to its initial state. +func (s *ExpHistogramValue) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *ExpHistogramValue) reset() { diff --git a/go/otel/otelstef/histogramvalue.go b/go/otel/otelstef/histogramvalue.go index 2c255da5..18a9f9a1 100644 --- a/go/otel/otelstef/histogramvalue.go +++ b/go/otel/otelstef/histogramvalue.go @@ -76,6 +76,12 @@ func (s *HistogramValue) initAlloc(parentModifiedFields *modifiedFields, parentM s.bucketCounts.initAlloc(&s.modifiedFields, fieldModifiedHistogramValueBucketCounts, allocators) } +// Reset the struct to its initial state. +func (s *HistogramValue) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *HistogramValue) reset() { diff --git a/go/otel/otelstef/link.go b/go/otel/otelstef/link.go index 55f9f20f..beffd4f8 100644 --- a/go/otel/otelstef/link.go +++ b/go/otel/otelstef/link.go @@ -67,6 +67,12 @@ func (s *Link) initAlloc(parentModifiedFields *modifiedFields, parentModifiedBit s.attributes.initAlloc(&s.modifiedFields, fieldModifiedLinkAttributes, allocators) } +// Reset the struct to its initial state. +func (s *Link) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *Link) reset() { diff --git a/go/otel/otelstef/metric.go b/go/otel/otelstef/metric.go index d2fc426a..171db392 100644 --- a/go/otel/otelstef/metric.go +++ b/go/otel/otelstef/metric.go @@ -84,6 +84,12 @@ func (s *Metric) initAlloc(parentModifiedFields *modifiedFields, parentModifiedB s.histogramBounds.initAlloc(&s.modifiedFields, fieldModifiedMetricHistogramBounds, allocators) } +// Reset the struct to its initial state. +func (s *Metric) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *Metric) reset() { diff --git a/go/otel/otelstef/metrics.go b/go/otel/otelstef/metrics.go index b9dd9b1f..b86f5934 100644 --- a/go/otel/otelstef/metrics.go +++ b/go/otel/otelstef/metrics.go @@ -86,6 +86,12 @@ func (s *Metrics) initAlloc(parentModifiedFields *modifiedFields, parentModified s.point.initAlloc(&s.modifiedFields, fieldModifiedMetricsPoint, allocators) } +// Reset the struct to its initial state. +func (s *Metrics) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *Metrics) reset() { diff --git a/go/otel/otelstef/point.go b/go/otel/otelstef/point.go index cb047e39..bfc50857 100644 --- a/go/otel/otelstef/point.go +++ b/go/otel/otelstef/point.go @@ -65,6 +65,12 @@ func (s *Point) initAlloc(parentModifiedFields *modifiedFields, parentModifiedBi s.exemplars.initAlloc(&s.modifiedFields, fieldModifiedPointExemplars, allocators) } +// Reset the struct to its initial state. +func (s *Point) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *Point) reset() { diff --git a/go/otel/otelstef/pointvalue.go b/go/otel/otelstef/pointvalue.go index 32d832af..2d6a71f5 100644 --- a/go/otel/otelstef/pointvalue.go +++ b/go/otel/otelstef/pointvalue.go @@ -18,14 +18,11 @@ var _ = codecs.StringEncoder{} // PointValue is a oneof struct. type PointValue struct { - // The current type of the oneof. - typ PointValueType - - int64 int64 - float64 float64 - histogram HistogramValue - expHistogram ExpHistogramValue - summary SummaryValue + // The low 8 bits hold the current type. The remaining 56 bits hold + // string/bytes length when a choice needs it. + typLen uint64 + ptr unsafe.Pointer + bits uint64 // Pointer to parent's modifiedFields parentModifiedFields *modifiedFields @@ -41,37 +38,35 @@ func (s *PointValue) Init() { func (s *PointValue) init(parentModifiedFields *modifiedFields, parentModifiedBit uint64) { s.parentModifiedFields = parentModifiedFields s.parentModifiedBit = parentModifiedBit - - s.histogram.init(parentModifiedFields, parentModifiedBit) - s.expHistogram.init(parentModifiedFields, parentModifiedBit) - s.summary.init(parentModifiedFields, parentModifiedBit) } func (s *PointValue) initAlloc(parentModifiedFields *modifiedFields, parentModifiedBit uint64, allocators *Allocators) { s.parentModifiedFields = parentModifiedFields s.parentModifiedBit = parentModifiedBit +} - s.histogram.initAlloc(parentModifiedFields, parentModifiedBit, allocators) - s.expHistogram.initAlloc(parentModifiedFields, parentModifiedBit, allocators) - s.summary.initAlloc(parentModifiedFields, parentModifiedBit, allocators) +// clear the value and set the type. Must be followed by a "set" call which +// sets the value (except if the type is None). +func (s *PointValue) clearValSetType(typ PointValueType) { + s.ptr = nil + s.typLen = uint64(typ) + s.bits = 0 } // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *PointValue) reset() { - s.typ = PointValueTypeNone - // We don't need to reset the state of the field since that will be done - // when the type is changed, see SetType(). + s.clearValSetType(PointValueTypeNone) } func (s *PointValue) freeze() { - switch s.typ { + switch s.Type() { case PointValueTypeHistogram: - s.histogram.freeze() + s.histogramPtr().freeze() case PointValueTypeExpHistogram: - s.expHistogram.freeze() + s.expHistogramPtr().freeze() case PointValueTypeSummary: - s.summary.freeze() + s.summaryPtr().freeze() } } @@ -81,9 +76,14 @@ func (s *PointValue) freeze() { func (s *PointValue) fixParent(parentModifiedFields *modifiedFields) { s.parentModifiedFields = parentModifiedFields - s.histogram.fixParent(parentModifiedFields) - s.expHistogram.fixParent(parentModifiedFields) - s.summary.fixParent(parentModifiedFields) + switch s.Type() { + case PointValueTypeHistogram: + s.histogramPtr().fixParent(parentModifiedFields) + case PointValueTypeExpHistogram: + s.expHistogramPtr().fixParent(parentModifiedFields) + case PointValueTypeSummary: + s.summaryPtr().fixParent(parentModifiedFields) + } } type PointValueType byte @@ -98,82 +98,154 @@ const ( PointValueTypeCount ) -// Type returns the type of the value currently contained in PointValue. -func (s *PointValue) Type() PointValueType { - return s.typ +const ( + PointValueTypLenTypeBits = 8 + PointValueTypLenTypeMask = 1<> PointValueTypLenTypeBits) } -// resetContained resets the currently contained value, if any. -// Normally used after switching to a different type to make sure -// the value contained is in blank state. -func (s *PointValue) resetContained() { - switch s.typ { - case PointValueTypeHistogram: - s.histogram.reset() - case PointValueTypeExpHistogram: - s.expHistogram.reset() - case PointValueTypeSummary: - s.summary.reset() +func (s *PointValue) setLen(n int) { + if uint64(n) > PointValueTypLenLenMask { + panic("PointValue length exceeds 56 bits") } + s.typLen = (uint64(n) << PointValueTypLenTypeBits) | (s.typLen & PointValueTypLenTypeMask) +} + +// Type returns the type of the value currently contained in PointValue. +func (s *PointValue) Type() PointValueType { + return PointValueType(s.typLen & PointValueTypLenTypeMask) } // SetType sets the type of the value currently contained in PointValue. func (s *PointValue) SetType(typ PointValueType) { - if s.typ != typ { - s.typ = typ - s.resetContained() + if s.Type() != typ { + s.clearValSetType(typ) switch typ { + case PointValueTypeHistogram: + s.allocHistogram() + case PointValueTypeExpHistogram: + s.allocExpHistogram() + case PointValueTypeSummary: + s.allocSummary() } s.markParentModified() } } +func (s *PointValue) int64Ptr() *int64 { + return (*int64)(unsafe.Pointer(&s.bits)) +} + // Int64 returns the value if the contained type is currently PointValueTypeInt64. // The caller must check the type via Type() before attempting to call this function. func (s *PointValue) Int64() int64 { - return s.int64 + return (*s.int64Ptr()) } // SetInt64 sets the value to the specified value and sets the type to PointValueTypeInt64. func (s *PointValue) SetInt64(v int64) { - if s.typ != PointValueTypeInt64 || s.int64 != v { - s.int64 = v - s.typ = PointValueTypeInt64 + stored := v + if s.Type() != PointValueTypeInt64 || *s.int64Ptr() != stored { + s.clearValSetType(PointValueTypeInt64) + *s.int64Ptr() = stored s.parentModifiedFields.markModified(s.parentModifiedBit) } } +func (s *PointValue) float64Ptr() *float64 { + return (*float64)(unsafe.Pointer(&s.bits)) +} + // Float64 returns the value if the contained type is currently PointValueTypeFloat64. // The caller must check the type via Type() before attempting to call this function. func (s *PointValue) Float64() float64 { - return s.float64 + return (*s.float64Ptr()) } // SetFloat64 sets the value to the specified value and sets the type to PointValueTypeFloat64. func (s *PointValue) SetFloat64(v float64) { - if s.typ != PointValueTypeFloat64 || s.float64 != v { - s.float64 = v - s.typ = PointValueTypeFloat64 + stored := v + if s.Type() != PointValueTypeFloat64 || *s.float64Ptr() != stored { + s.clearValSetType(PointValueTypeFloat64) + *s.float64Ptr() = stored s.parentModifiedFields.markModified(s.parentModifiedBit) } } +func (s *PointValue) histogramPtr() *HistogramValue { + return (*HistogramValue)(s.ptr) +} + +func (s *PointValue) allocHistogram() *HistogramValue { + v := &HistogramValue{} + v.init(s.parentModifiedFields, s.parentModifiedBit) + s.ptr = unsafe.Pointer(v) + return v +} + +func (s *PointValue) allocHistogramAlloc(allocators *Allocators) *HistogramValue { + v := allocators.HistogramValue.Alloc() + v.initAlloc(s.parentModifiedFields, s.parentModifiedBit, allocators) + s.ptr = unsafe.Pointer(v) + return v +} + // Histogram returns the value if the contained type is currently PointValueTypeHistogram. // The caller must check the type via Type() before attempting to call this function. func (s *PointValue) Histogram() *HistogramValue { - return &s.histogram + return s.histogramPtr() +} + +func (s *PointValue) expHistogramPtr() *ExpHistogramValue { + return (*ExpHistogramValue)(s.ptr) +} + +func (s *PointValue) allocExpHistogram() *ExpHistogramValue { + v := &ExpHistogramValue{} + v.init(s.parentModifiedFields, s.parentModifiedBit) + s.ptr = unsafe.Pointer(v) + return v +} + +func (s *PointValue) allocExpHistogramAlloc(allocators *Allocators) *ExpHistogramValue { + v := allocators.ExpHistogramValue.Alloc() + v.initAlloc(s.parentModifiedFields, s.parentModifiedBit, allocators) + s.ptr = unsafe.Pointer(v) + return v } // ExpHistogram returns the value if the contained type is currently PointValueTypeExpHistogram. // The caller must check the type via Type() before attempting to call this function. func (s *PointValue) ExpHistogram() *ExpHistogramValue { - return &s.expHistogram + return s.expHistogramPtr() +} + +func (s *PointValue) summaryPtr() *SummaryValue { + return (*SummaryValue)(s.ptr) +} + +func (s *PointValue) allocSummary() *SummaryValue { + v := &SummaryValue{} + v.init(s.parentModifiedFields, s.parentModifiedBit) + s.ptr = unsafe.Pointer(v) + return v +} + +func (s *PointValue) allocSummaryAlloc(allocators *Allocators) *SummaryValue { + v := allocators.SummaryValue.Alloc() + v.initAlloc(s.parentModifiedFields, s.parentModifiedBit, allocators) + s.ptr = unsafe.Pointer(v) + return v } // Summary returns the value if the contained type is currently PointValueTypeSummary. // The caller must check the type via Type() before attempting to call this function. func (s *PointValue) Summary() *SummaryValue { - return &s.summary + return s.summaryPtr() } func (s *PointValue) canBeShared() bool { @@ -188,18 +260,24 @@ func (s *PointValue) cloneShared(allocators *Allocators) PointValue { func (s *PointValue) Clone(allocators *Allocators) PointValue { c := PointValue{} - c.typ = s.typ - switch s.typ { + c.clearValSetType(s.Type()) + switch s.Type() { case PointValueTypeInt64: - c.int64 = s.int64 + *c.int64Ptr() = *s.int64Ptr() case PointValueTypeFloat64: - c.float64 = s.float64 + *c.float64Ptr() = *s.float64Ptr() case PointValueTypeHistogram: - copyToNewHistogramValue(&c.histogram, &s.histogram, allocators) + allocators.allocSizeChecker.AddAllocSize(uint(unsafe.Sizeof(HistogramValue{}))) + c.allocHistogramAlloc(allocators) + copyToNewHistogramValue(c.histogramPtr(), s.histogramPtr(), allocators) case PointValueTypeExpHistogram: - copyToNewExpHistogramValue(&c.expHistogram, &s.expHistogram, allocators) + allocators.allocSizeChecker.AddAllocSize(uint(unsafe.Sizeof(ExpHistogramValue{}))) + c.allocExpHistogramAlloc(allocators) + copyToNewExpHistogramValue(c.expHistogramPtr(), s.expHistogramPtr(), allocators) case PointValueTypeSummary: - copyToNewSummaryValue(&c.summary, &s.summary, allocators) + allocators.allocSizeChecker.AddAllocSize(uint(unsafe.Sizeof(SummaryValue{}))) + c.allocSummaryAlloc(allocators) + copyToNewSummaryValue(c.summaryPtr(), s.summaryPtr(), allocators) } return c } @@ -207,59 +285,73 @@ func (s *PointValue) Clone(allocators *Allocators) PointValue { // ByteSize returns approximate memory usage in bytes. Used to calculate // memory used by dictionaries. func (s *PointValue) byteSize() uint { - return uint(unsafe.Sizeof(*s)) + - s.histogram.byteSize() + s.expHistogram.byteSize() + s.summary.byteSize() + 0 + size := uint(unsafe.Sizeof(*s)) + switch s.Type() { + case PointValueTypeHistogram: + size += s.histogramPtr().byteSize() + case PointValueTypeExpHistogram: + size += s.expHistogramPtr().byteSize() + case PointValueTypeSummary: + size += s.summaryPtr().byteSize() + } + return size } // Copy from src to dst, overwriting existing data in dst. func copyPointValue(dst *PointValue, src *PointValue) { - switch src.typ { + switch src.Type() { case PointValueTypeInt64: - dst.SetInt64(src.int64) + dst.SetInt64((*src.int64Ptr())) case PointValueTypeFloat64: - dst.SetFloat64(src.float64) + dst.SetFloat64((*src.float64Ptr())) case PointValueTypeHistogram: - dst.SetType(src.typ) - copyHistogramValue(&dst.histogram, &src.histogram) + dst.SetType(src.Type()) + copyHistogramValue(dst.histogramPtr(), src.histogramPtr()) case PointValueTypeExpHistogram: - dst.SetType(src.typ) - copyExpHistogramValue(&dst.expHistogram, &src.expHistogram) + dst.SetType(src.Type()) + copyExpHistogramValue(dst.expHistogramPtr(), src.expHistogramPtr()) case PointValueTypeSummary: - dst.SetType(src.typ) - copySummaryValue(&dst.summary, &src.summary) + dst.SetType(src.Type()) + copySummaryValue(dst.summaryPtr(), src.summaryPtr()) case PointValueTypeNone: - if dst.typ != PointValueTypeNone { - dst.typ = PointValueTypeNone + if dst.Type() != PointValueTypeNone { + dst.clearValSetType(PointValueTypeNone) dst.markParentModified() } default: - panic("copyPointValue: unexpected type: " + fmt.Sprint(src.typ)) + panic("copyPointValue: unexpected type: " + fmt.Sprint(src.Type())) } } // Copy from src to dst. dst is assumed to be just inited. func copyToNewPointValue(dst *PointValue, src *PointValue, allocators *Allocators) { - dst.typ = src.typ - switch src.typ { + dst.typLen = uint64(src.Type()) + switch src.Type() { case PointValueTypeInt64: - if dst.int64 != src.int64 { - dst.int64 = src.int64 + if *dst.int64Ptr() != *src.int64Ptr() { + *dst.int64Ptr() = *src.int64Ptr() dst.parentModifiedFields.markModified(dst.parentModifiedBit) } case PointValueTypeFloat64: - if dst.float64 != src.float64 { - dst.float64 = src.float64 + if *dst.float64Ptr() != *src.float64Ptr() { + *dst.float64Ptr() = *src.float64Ptr() dst.parentModifiedFields.markModified(dst.parentModifiedBit) } case PointValueTypeHistogram: - copyToNewHistogramValue(&dst.histogram, &src.histogram, allocators) + allocators.allocSizeChecker.AddAllocSize(uint(unsafe.Sizeof(HistogramValue{}))) + dst.allocHistogramAlloc(allocators) + copyToNewHistogramValue(dst.histogramPtr(), src.histogramPtr(), allocators) case PointValueTypeExpHistogram: - copyToNewExpHistogramValue(&dst.expHistogram, &src.expHistogram, allocators) + allocators.allocSizeChecker.AddAllocSize(uint(unsafe.Sizeof(ExpHistogramValue{}))) + dst.allocExpHistogramAlloc(allocators) + copyToNewExpHistogramValue(dst.expHistogramPtr(), src.expHistogramPtr(), allocators) case PointValueTypeSummary: - copyToNewSummaryValue(&dst.summary, &src.summary, allocators) + allocators.allocSizeChecker.AddAllocSize(uint(unsafe.Sizeof(SummaryValue{}))) + dst.allocSummaryAlloc(allocators) + copyToNewSummaryValue(dst.summaryPtr(), src.summaryPtr(), allocators) case PointValueTypeNone: default: - panic("copyPointValue: unexpected type: " + fmt.Sprint(src.typ)) + panic("copyPointValue: unexpected type: " + fmt.Sprint(src.Type())) } } @@ -273,55 +365,55 @@ func (s *PointValue) markParentModified() { } func (s *PointValue) setModifiedRecursively() { - switch s.typ { + switch s.Type() { case PointValueTypeHistogram: - s.histogram.setModifiedRecursively() + s.histogramPtr().setModifiedRecursively() case PointValueTypeExpHistogram: - s.expHistogram.setModifiedRecursively() + s.expHistogramPtr().setModifiedRecursively() case PointValueTypeSummary: - s.summary.setModifiedRecursively() + s.summaryPtr().setModifiedRecursively() } } func (s *PointValue) setUnmodifiedRecursively() { - switch s.typ { + switch s.Type() { case PointValueTypeHistogram: - s.histogram.setUnmodifiedRecursively() + s.histogramPtr().setUnmodifiedRecursively() case PointValueTypeExpHistogram: - s.expHistogram.setUnmodifiedRecursively() + s.expHistogramPtr().setUnmodifiedRecursively() case PointValueTypeSummary: - s.summary.setUnmodifiedRecursively() + s.summaryPtr().setUnmodifiedRecursively() } } // computeDiff compares s and val and returns true if they differ. // All fields that are different in s will be marked as modified. func (s *PointValue) computeDiff(val *PointValue) (ret bool) { - if s.typ == val.typ { - switch s.typ { + if s.Type() == val.Type() { + switch s.Type() { case PointValueTypeInt64: - ret = s.int64 != val.int64 + ret = s.Int64() != val.Int64() case PointValueTypeFloat64: - ret = s.float64 != val.float64 + ret = s.Float64() != val.Float64() case PointValueTypeHistogram: - ret = s.histogram.computeDiff(&val.histogram) + ret = s.histogramPtr().computeDiff(val.histogramPtr()) case PointValueTypeExpHistogram: - ret = s.expHistogram.computeDiff(&val.expHistogram) + ret = s.expHistogramPtr().computeDiff(val.expHistogramPtr()) case PointValueTypeSummary: - ret = s.summary.computeDiff(&val.summary) + ret = s.summaryPtr().computeDiff(val.summaryPtr()) } } else { ret = true - switch s.typ { + switch s.Type() { case PointValueTypeHistogram: // val.histogram doesn't exist at all so mark the whole s.histogram subtree as modified. - s.histogram.setModifiedRecursively() + s.histogramPtr().setModifiedRecursively() case PointValueTypeExpHistogram: // val.expHistogram doesn't exist at all so mark the whole s.expHistogram subtree as modified. - s.expHistogram.setModifiedRecursively() + s.expHistogramPtr().setModifiedRecursively() case PointValueTypeSummary: // val.summary doesn't exist at all so mark the whole s.summary subtree as modified. - s.summary.setModifiedRecursively() + s.summaryPtr().setModifiedRecursively() } } return ret @@ -329,20 +421,20 @@ func (s *PointValue) computeDiff(val *PointValue) (ret bool) { // IsEqual performs deep comparison and returns true if struct is equal to val. func (e *PointValue) IsEqual(val *PointValue) bool { - if e.typ != val.typ { + if e.Type() != val.Type() { return false } - switch e.typ { + switch e.Type() { case PointValueTypeInt64: - return pkg.Int64Equal(e.int64, val.int64) + return pkg.Int64Equal(*e.int64Ptr(), *val.int64Ptr()) case PointValueTypeFloat64: - return pkg.Float64Equal(e.float64, val.float64) + return pkg.Float64Equal(*e.float64Ptr(), *val.float64Ptr()) case PointValueTypeHistogram: - return e.histogram.IsEqual(&val.histogram) + return e.histogramPtr().IsEqual(val.histogramPtr()) case PointValueTypeExpHistogram: - return e.expHistogram.IsEqual(&val.expHistogram) + return e.expHistogramPtr().IsEqual(val.expHistogramPtr()) case PointValueTypeSummary: - return e.summary.IsEqual(&val.summary) + return e.summaryPtr().IsEqual(val.summaryPtr()) } return true @@ -351,21 +443,24 @@ func (e *PointValue) IsEqual(val *PointValue) bool { // CmpPointValue performs deep comparison and returns an integer that // will be 0 if left == right, negative if left < right, positive if left > right. func CmpPointValue(left, right *PointValue) int { - c := pkg.Uint64Compare(uint64(left.typ), uint64(right.typ)) + c := pkg.Uint64Compare(uint64(left.Type()), uint64(right.Type())) if c != 0 { return c } - switch left.typ { + switch left.Type() { case PointValueTypeInt64: - return pkg.Int64Compare(left.int64, right.int64) + return pkg.Int64Compare(*left.int64Ptr(), *right.int64Ptr()) case PointValueTypeFloat64: - return pkg.Float64Compare(left.float64, right.float64) + return pkg.Float64Compare(*left.float64Ptr(), *right.float64Ptr()) case PointValueTypeHistogram: - return CmpHistogramValue(&left.histogram, &right.histogram) + return CmpHistogramValue( + left.histogramPtr(), right.histogramPtr()) case PointValueTypeExpHistogram: - return CmpExpHistogramValue(&left.expHistogram, &right.expHistogram) + return CmpExpHistogramValue( + left.expHistogramPtr(), right.expHistogramPtr()) case PointValueTypeSummary: - return CmpSummaryValue(&left.summary, &right.summary) + return CmpSummaryValue( + left.summaryPtr(), right.summaryPtr()) } return 0 @@ -389,7 +484,7 @@ func (s *PointValue) mutateRandom(random *rand.Rand, schem *schema.Schema, limit typeChanged = true } - switch s.typ { + switch s.Type() { case PointValueTypeInt64: if typeChanged || random.IntN(2) == 0 { s.SetInt64(pkg.Int64Random(random)) @@ -400,15 +495,15 @@ func (s *PointValue) mutateRandom(random *rand.Rand, schem *schema.Schema, limit } case PointValueTypeHistogram: if typeChanged || random.IntN(2) == 0 { - s.histogram.mutateRandom(random, schem, limiter) + s.histogramPtr().mutateRandom(random, schem, limiter) } case PointValueTypeExpHistogram: if typeChanged || random.IntN(2) == 0 { - s.expHistogram.mutateRandom(random, schem, limiter) + s.expHistogramPtr().mutateRandom(random, schem, limiter) } case PointValueTypeSummary: if typeChanged || random.IntN(2) == 0 { - s.summary.mutateRandom(random, schem, limiter) + s.summaryPtr().mutateRandom(random, schem, limiter) } } } @@ -568,7 +663,7 @@ func (e *PointValueEncoder) Reset() { // Encode encodes val into buf func (e *PointValueEncoder) Encode(val *PointValue) { - typ := val.typ + typ := val.Type() if uint(typ) > e.fieldCount { // The current field type is not supported in target schema. Encode the type as None. typ = PointValueTypeNone @@ -583,19 +678,19 @@ func (e *PointValueEncoder) Encode(val *PointValue) { switch typ { case PointValueTypeInt64: // Encode Int64 - e.int64Encoder.Encode(val.int64) + e.int64Encoder.Encode(*val.int64Ptr()) case PointValueTypeFloat64: // Encode Float64 - e.float64Encoder.Encode(val.float64) + e.float64Encoder.Encode(*val.float64Ptr()) case PointValueTypeHistogram: // Encode Histogram - e.histogramEncoder.Encode(&val.histogram) + e.histogramEncoder.Encode(val.histogramPtr()) case PointValueTypeExpHistogram: // Encode ExpHistogram - e.expHistogramEncoder.Encode(&val.expHistogram) + e.expHistogramEncoder.Encode(val.expHistogramPtr()) case PointValueTypeSummary: // Encode Summary - e.summaryEncoder.Encode(&val.summary) + e.summaryEncoder.Encode(val.summaryPtr()) } } @@ -855,31 +950,51 @@ func (d *PointValueDecoder) Decode(dstPtr *PointValue) error { } dst := dstPtr - if dst.typ != PointValueType(typ) { - dst.typ = PointValueType(typ) + if dst.Type() != PointValueType(typ) { // The type changed, we need to reset the contained value so that // it does not contain carry-over data from a previous record that // was of this same type. - dst.resetContained() + dst.clearValSetType(PointValueType(typ)) + + switch PointValueType(typ) { + case PointValueTypeHistogram: + // Allocate Histogram + if err := d.allocators.allocSizeChecker.PrepAllocSize(uint(unsafe.Sizeof(HistogramValue{}))); err != nil { + return err + } + dst.allocHistogramAlloc(d.allocators) + case PointValueTypeExpHistogram: + // Allocate ExpHistogram + if err := d.allocators.allocSizeChecker.PrepAllocSize(uint(unsafe.Sizeof(ExpHistogramValue{}))); err != nil { + return err + } + dst.allocExpHistogramAlloc(d.allocators) + case PointValueTypeSummary: + // Allocate Summary + if err := d.allocators.allocSizeChecker.PrepAllocSize(uint(unsafe.Sizeof(SummaryValue{}))); err != nil { + return err + } + dst.allocSummaryAlloc(d.allocators) + } } // Decode selected field - switch dst.typ { + switch dst.Type() { case PointValueTypeInt64: // Decode Int64 - return d.int64Decoder.Decode(&dst.int64) + return d.int64Decoder.Decode(dst.int64Ptr()) case PointValueTypeFloat64: // Decode Float64 - return d.float64Decoder.Decode(&dst.float64) + return d.float64Decoder.Decode(dst.float64Ptr()) case PointValueTypeHistogram: // Decode Histogram - return d.histogramDecoder.Decode(&dst.histogram) + return d.histogramDecoder.Decode(dst.histogramPtr()) case PointValueTypeExpHistogram: // Decode ExpHistogram - return d.expHistogramDecoder.Decode(&dst.expHistogram) + return d.expHistogramDecoder.Decode(dst.expHistogramPtr()) case PointValueTypeSummary: // Decode Summary - return d.summaryDecoder.Decode(&dst.summary) + return d.summaryDecoder.Decode(dst.summaryPtr()) } return nil } diff --git a/go/otel/otelstef/quantilevalue.go b/go/otel/otelstef/quantilevalue.go index 13225df6..b684b99e 100644 --- a/go/otel/otelstef/quantilevalue.go +++ b/go/otel/otelstef/quantilevalue.go @@ -57,6 +57,12 @@ func (s *QuantileValue) initAlloc(parentModifiedFields *modifiedFields, parentMo } +// Reset the struct to its initial state. +func (s *QuantileValue) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *QuantileValue) reset() { diff --git a/go/otel/otelstef/resource.go b/go/otel/otelstef/resource.go index 73d6b6e2..89b0131a 100644 --- a/go/otel/otelstef/resource.go +++ b/go/otel/otelstef/resource.go @@ -72,6 +72,12 @@ func (s *Resource) initAlloc(parentModifiedFields *modifiedFields, parentModifie s.attributes.initAlloc(&s.modifiedFields, fieldModifiedResourceAttributes, allocators) } +// Reset the struct to its initial state. +func (s *Resource) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *Resource) reset() { diff --git a/go/otel/otelstef/scope.go b/go/otel/otelstef/scope.go index 41e23d9b..3b6e4315 100644 --- a/go/otel/otelstef/scope.go +++ b/go/otel/otelstef/scope.go @@ -76,6 +76,12 @@ func (s *Scope) initAlloc(parentModifiedFields *modifiedFields, parentModifiedBi s.attributes.initAlloc(&s.modifiedFields, fieldModifiedScopeAttributes, allocators) } +// Reset the struct to its initial state. +func (s *Scope) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *Scope) reset() { diff --git a/go/otel/otelstef/span.go b/go/otel/otelstef/span.go index 092a6d83..8049820d 100644 --- a/go/otel/otelstef/span.go +++ b/go/otel/otelstef/span.go @@ -89,6 +89,12 @@ func (s *Span) initAlloc(parentModifiedFields *modifiedFields, parentModifiedBit s.status.initAlloc(&s.modifiedFields, fieldModifiedSpanStatus, allocators) } +// Reset the struct to its initial state. +func (s *Span) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *Span) reset() { diff --git a/go/otel/otelstef/spans.go b/go/otel/otelstef/spans.go index 66806449..d64d4422 100644 --- a/go/otel/otelstef/spans.go +++ b/go/otel/otelstef/spans.go @@ -75,6 +75,12 @@ func (s *Spans) initAlloc(parentModifiedFields *modifiedFields, parentModifiedBi s.span.initAlloc(&s.modifiedFields, fieldModifiedSpansSpan, allocators) } +// Reset the struct to its initial state. +func (s *Spans) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *Spans) reset() { diff --git a/go/otel/otelstef/spanstatus.go b/go/otel/otelstef/spanstatus.go index c76ea8a1..83f84e59 100644 --- a/go/otel/otelstef/spanstatus.go +++ b/go/otel/otelstef/spanstatus.go @@ -57,6 +57,12 @@ func (s *SpanStatus) initAlloc(parentModifiedFields *modifiedFields, parentModif } +// Reset the struct to its initial state. +func (s *SpanStatus) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *SpanStatus) reset() { diff --git a/go/otel/otelstef/summaryvalue.go b/go/otel/otelstef/summaryvalue.go index 36818ae1..ed023a62 100644 --- a/go/otel/otelstef/summaryvalue.go +++ b/go/otel/otelstef/summaryvalue.go @@ -61,6 +61,12 @@ func (s *SummaryValue) initAlloc(parentModifiedFields *modifiedFields, parentMod s.quantileValues.initAlloc(&s.modifiedFields, fieldModifiedSummaryValueQuantileValues, allocators) } +// Reset the struct to its initial state. +func (s *SummaryValue) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *SummaryValue) reset() { diff --git a/stefc/templates/go/oneof.go.tmpl b/stefc/templates/go/oneof.go.tmpl index 1741a374..86afa5a1 100644 --- a/stefc/templates/go/oneof.go.tmpl +++ b/stefc/templates/go/oneof.go.tmpl @@ -19,13 +19,34 @@ import ( var _ = strings.Compare var _ = codecs.StringEncoder{} +{{/* Calculate what storage this oneof needs based on types of values it needs to store */}} +{{- $structNeedsPtr := false -}} +{{- $structNeedsBits := false -}} + +{{/* Iterate over all fields and check field types */}} +{{- range .Fields -}} + +{{- $isPtr := not .Type.IsPrimitive -}} +{{- $isStringOrBytes := or (eq .Type.Storage "string") (eq .Type.Storage "pkg.Bytes") -}} +{{- $isBits := and .Type.IsPrimitive (not $isStringOrBytes) -}} + +{{- if or $isPtr $isStringOrBytes -}} + {{- $structNeedsPtr = true -}} +{{- end -}} + +{{- if $isBits -}} + {{- $structNeedsBits = true -}} +{{- end -}} + +{{- end }} + // {{ .StructName }} is a oneof struct. type {{ .StructName }} struct { - // The current type of the oneof. - typ {{$.StructName }}Type - {{ range .Fields }} - {{.name}} {{if .Type.Flags.StoreByPtr}}*{{end}}{{ .Type.Storage }} - {{- end }} + // The low 8 bits hold the current type. The remaining 56 bits hold + // string/bytes length when a choice needs it. + typLen uint64 + {{if $structNeedsPtr}}ptr unsafe.Pointer{{end}} + {{if $structNeedsBits}}bits uint64{{end}} // Pointer to parent's modifiedFields parentModifiedFields *modifiedFields @@ -41,45 +62,37 @@ func (s *{{.StructName }}) Init() { func (s *{{ $.StructName }}) init(parentModifiedFields *modifiedFields, parentModifiedBit uint64) { s.parentModifiedFields = parentModifiedFields s.parentModifiedBit = parentModifiedBit - - {{ range .Fields }} - {{- if and (not .Type.IsPrimitive) (not .Type.Flags.StoreByPtr) }} - s.{{.name}}.init(parentModifiedFields, parentModifiedBit) - {{- end -}} - {{- end -}} } func (s *{{ $.StructName }}) initAlloc(parentModifiedFields *modifiedFields, parentModifiedBit uint64, allocators *Allocators) { s.parentModifiedFields = parentModifiedFields s.parentModifiedBit = parentModifiedBit +} - {{ range .Fields }} - {{- if and (not .Type.IsPrimitive) (not .Type.Flags.StoreByPtr) }} - s.{{.name}}.initAlloc(parentModifiedFields, parentModifiedBit, allocators) - {{- end -}} - {{- end -}} +// clear the value and set the type. Must be followed by a "set" call which +// sets the value (except if the type is None). +func (s *{{ $.StructName }}) clearValSetType(typ {{$.StructName }}Type) { + {{- if $structNeedsPtr }} + s.ptr = nil + {{- end }} + s.typLen = uint64(typ) + {{- if $structNeedsBits }} + s.bits = 0 + {{- end }} } // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *{{ $.StructName }}) reset() { - s.typ = {{$.StructName }}TypeNone - // We don't need to reset the state of the field since that will be done - // when the type is changed, see SetType(). + s.clearValSetType({{$.StructName }}TypeNone) } func (s *{{ $.StructName }}) freeze() { - switch s.typ { + switch s.Type() { {{- range .Fields }} {{- if not .Type.IsPrimitive}} case {{ $.StructName }}Type{{.Name}}: - {{- if .Type.Flags.StoreByPtr}} - if s.{{.name}} != nil { - s.{{.name}}.freeze() - } - {{- else}} - s.{{.name}}.freeze() - {{- end -}} + s.{{.name}}Ptr().freeze() {{- end -}} {{- end }} } @@ -91,11 +104,14 @@ func (s *{{ $.StructName }}) freeze() { func (s *{{ $.StructName }}) fixParent(parentModifiedFields *modifiedFields) { s.parentModifiedFields = parentModifiedFields - {{ range .Fields }} - {{- if and (not .Type.IsPrimitive) (not .Type.Flags.StoreByPtr) }} - s.{{.name}}.fixParent(parentModifiedFields) - {{- end -}} - {{- end -}} + switch s.Type() { + {{- range .Fields }} + {{- if not .Type.IsPrimitive }} + case {{ $.StructName }}Type{{.Name}}: + s.{{.name}}Ptr().fixParent(parentModifiedFields) + {{- end }} + {{- end }} + } } type {{$.StructName }}Type byte @@ -107,45 +123,38 @@ const ( {{.StructName}}TypeCount ) -// Type returns the type of the value currently contained in {{ $.StructName }}. -func (s *{{ $.StructName }}) Type() {{$.StructName }}Type { - return s.typ +const ( + {{.StructName}}TypLenTypeBits = 8 + {{.StructName}}TypLenTypeMask = 1<<{{.StructName}}TypLenTypeBits - 1 + {{.StructName}}TypLenLenMask = 1<<(64-{{.StructName}}TypLenTypeBits) - 1 +) + +func (s *{{ $.StructName }}) len() int { + return int(s.typLen >> {{.StructName}}TypLenTypeBits) } -// resetContained resets the currently contained value, if any. -// Normally used after switching to a different type to make sure -// the value contained is in blank state. -func (s *{{ $.StructName }}) resetContained() { - switch s.typ { - {{- range .Fields }} - {{- if not .Type.IsPrimitive}} - case {{ $.StructName }}Type{{.Name}}: - {{- if .Type.Flags.StoreByPtr}} - if s.{{.name}} != nil { - s.{{.name}}.reset() - } - {{- else}} - s.{{.name}}.reset() - {{- end -}} - {{- end -}} - {{- end }} - } +func (s *{{ $.StructName }}) setLen(n int) { + if uint64(n) > {{.StructName}}TypLenLenMask { + panic("{{.StructName}} length exceeds 56 bits") + } + s.typLen = (uint64(n) << {{.StructName}}TypLenTypeBits) | (s.typLen & {{.StructName}}TypLenTypeMask) +} + +// Type returns the type of the value currently contained in {{ $.StructName }}. +func (s *{{ $.StructName }}) Type() {{$.StructName }}Type { + return {{$.StructName }}Type(s.typLen & {{.StructName}}TypLenTypeMask) } // SetType sets the type of the value currently contained in {{ $.StructName }}. func (s *{{ $.StructName }}) SetType(typ {{$.StructName }}Type) { - if s.typ != typ { - s.typ = typ - s.resetContained() + if s.Type() != typ { + s.clearValSetType(typ) switch typ { {{- range .Fields }} - {{- if .Type.Flags.StoreByPtr}} + {{- if not .Type.IsPrimitive }} case {{ $.StructName }}Type{{.Name}}: - if s.{{.name}} == nil { - s.{{.name}} = &{{ .Type.Storage }}{} - s.{{.name}}.init(s.parentModifiedFields, s.parentModifiedBit) - } - {{- end -}} + s.alloc{{.Name}}() + {{- end }} {{- end }} } s.markParentModified() @@ -153,24 +162,73 @@ func (s *{{ $.StructName }}) SetType(typ {{$.StructName }}Type) { } {{ range .Fields }} +{{- $isStringOrBytes := or (eq .Type.Storage "string") (eq .Type.Storage "pkg.Bytes") -}} +{{- $isBits := and .Type.IsPrimitive (not $isStringOrBytes) -}} + +{{if $isBits}} +func (s *{{ $.StructName }}) {{.name}}Ptr() *{{.Type.Storage}} { + return (*{{.Type.Storage}})(unsafe.Pointer(&s.bits)) +} +{{else if $isStringOrBytes}} +func (s *{{ $.StructName }}) set{{.Name}}(v {{.Type.Storage}}) { + s.ptr = unsafe.Pointer(unsafe.StringData(string(v))) + s.setLen(len(v)) +} +{{else}} +func (s *{{ $.StructName }}) {{.name}}Ptr() *{{.Type.Storage}} { + return (*{{.Type.Storage}})(s.ptr) +} + +func (s *{{ $.StructName }}) alloc{{.Name}}() *{{.Type.Storage}} { + v := &{{.Type.Storage}}{} + v.init(s.parentModifiedFields, s.parentModifiedBit) + s.ptr = unsafe.Pointer(v) + return v +} + +func (s *{{ $.StructName }}) alloc{{.Name}}Alloc(allocators *Allocators) *{{.Type.Storage}} { + {{- if .IsStructType }} + v := allocators.{{.Type.Storage}}.Alloc() + {{- else }} + v := &{{.Type.Storage}}{} + {{- end }} + v.initAlloc(s.parentModifiedFields, s.parentModifiedBit, allocators) + s.ptr = unsafe.Pointer(v) + return v +} +{{end}} + // {{.Name}} returns the value if the contained type is currently {{$.StructName }}Type{{.Name}}. // The caller must check the type via Type() before attempting to call this function. func (s *{{ $.StructName }}) {{.Name}}() {{if .Type.Flags.PassByPtr}}*{{end}}{{.Type.Exported}} { {{- if .Type.IsPrimitive}} - return {{.Type.ToExported (print "s." .name)}} + {{- if $isStringOrBytes}} + return {{.Type.Storage}}(unsafe.String((*byte)(s.ptr), s.len())) + {{- else}} + return {{.Type.ToExported (print "(*s." .name "Ptr())")}} + {{- end}} {{- else}} - return {{if and .Type.Flags.PassByPtr (not .Type.Flags.StoreByPtr)}}&{{end}}s.{{.name}} + return s.{{.name}}Ptr() {{- end}} } {{if .Type.IsPrimitive}} // Set{{.Name}} sets the value to the specified value and sets the type to {{$.StructName }}Type{{.Name}}. func (s *{{ $.StructName }}) Set{{.Name}}(v {{if .PassByPointer}}*{{end}}{{.Type.Exported}}) { - if s.typ!={{$.StructName }}Type{{.Name}} || s.{{.name}} != {{.Type.ToStorage "v"}} { - s.{{.name}} = {{.Type.ToStorage "v"}} - s.typ = {{$.StructName }}Type{{.Name}} + stored := {{.Type.ToStorage "v"}} + {{- if $isStringOrBytes}} + if s.Type()!={{$.StructName }}Type{{.Name}} || s.{{.Name}}() != stored { + s.clearValSetType({{$.StructName }}Type{{.Name}}) + s.set{{.Name}}(stored) + s.parentModifiedFields.markModified(s.parentModifiedBit) + } + {{- else}} + if s.Type()!={{$.StructName }}Type{{.Name}} || *s.{{.name}}Ptr() != stored { + s.clearValSetType({{$.StructName }}Type{{.Name}}) + *s.{{.name}}Ptr() = stored s.parentModifiedFields.markModified(s.parentModifiedBit) } + {{- end}} } {{end}} {{ end }} @@ -192,16 +250,23 @@ func (s *{{ .StructName }}) Clone(allocators *Allocators) {{if .Type.Flags.Store {{- else}} c := {{.StructName}}{} {{- end}} - c.typ = s.typ - switch s.typ { + c.clearValSetType(s.Type()) + switch s.Type() { {{- range .Fields }} + {{- $isStringOrBytes := or (eq .Type.Storage "string") (eq .Type.Storage "pkg.Bytes") }} case {{ $.StructName }}Type{{.Name}}: {{- if .Type.IsPrimitive}} - c.{{.name}} = s.{{.name}} + {{- if $isStringOrBytes}} + c.set{{.Name}}(s.{{.Name}}()) + {{- else}} + *c.{{.name}}Ptr() = *s.{{.name}}Ptr() + {{- end}} {{- else if .Type.Flags.StoreByPtr}} - c.{{.name}} = s.{{.name}}.cloneShared(allocators) + c.ptr = unsafe.Pointer(s.{{.name}}Ptr().cloneShared(allocators)) {{- else}} - copyToNew{{.Type.Storage}}({{if .Type.Flags.TakePtr}}&{{end}}c.{{.name}}, {{if .Type.Flags.TakePtr}}&{{end}}s.{{.name}}, allocators) + allocators.allocSizeChecker.AddAllocSize(uint(unsafe.Sizeof({{.Type.Storage}}{}))) + c.alloc{{.Name}}Alloc(allocators) + copyToNew{{.Type.Storage}}(c.{{.name}}Ptr(), s.{{.name}}Ptr(), allocators) {{- end}} {{- end }} } @@ -211,59 +276,72 @@ func (s *{{ .StructName }}) Clone(allocators *Allocators) {{if .Type.Flags.Store // ByteSize returns approximate memory usage in bytes. Used to calculate // memory used by dictionaries. func (s *{{ .StructName }}) byteSize() uint { - return uint(unsafe.Sizeof(*s))+ - {{range .Fields -}} - {{- if .Type.MustClone}}s.{{.name}}.byteSize()+{{end}} - {{- end }}0 + size := uint(unsafe.Sizeof(*s)) + switch s.Type() { + {{- range .Fields }} + {{- if .Type.MustClone}} + case {{ $.StructName }}Type{{.Name}}: + size += s.{{.name}}Ptr().byteSize() + {{- end}} + {{- end }} + } + return size } // Copy from src to dst, overwriting existing data in dst. func copy{{.StructName}}(dst *{{.StructName}}, src *{{.StructName}}) { - switch src.typ { + switch src.Type() { {{- range .Fields}} + {{- $isStringOrBytes := or (eq .Type.Storage "string") (eq .Type.Storage "pkg.Bytes") }} case {{ $.StructName }}Type{{.Name}}: {{- if .Type.MustClone}} - dst.SetType(src.typ) - copy{{.Type.TypeName}}( - {{- if .Type.Flags.TakePtr}}&{{end}}dst.{{.name }}, - {{- if .Type.Flags.TakePtr}}&{{end}}src.{{.name}}) + dst.SetType(src.Type()) + copy{{.Type.TypeName}}(dst.{{.name}}Ptr(), src.{{.name}}Ptr()) {{- else}} - dst.Set{{.Name}}({{.Type.ToExported (print "src." .name)}}) + {{- if $isStringOrBytes}} + dst.Set{{.Name}}({{.Type.ToExported (print "src." .Name "()")}}) + {{- else}} + dst.Set{{.Name}}({{.Type.ToExported (print "(*src." .name "Ptr())")}}) + {{- end}} {{- end}} {{- end}} case {{ $.StructName }}TypeNone: - if dst.typ != {{ $.StructName }}TypeNone { - dst.typ = {{ $.StructName }}TypeNone + if dst.Type() != {{ $.StructName }}TypeNone { + dst.clearValSetType({{ $.StructName }}TypeNone) dst.markParentModified() } - default: panic("copy{{.StructName}}: unexpected type: " + fmt.Sprint(src.typ)) + default: panic("copy{{.StructName}}: unexpected type: " + fmt.Sprint(src.Type())) } } // Copy from src to dst. dst is assumed to be just inited. func copyToNew{{.StructName}}(dst *{{.StructName}}, src *{{.StructName}}, allocators *Allocators) { - dst.typ = src.typ - switch src.typ { + dst.typLen = uint64(src.Type()) + switch src.Type() { {{- range .Fields}} + {{- $isStringOrBytes := or (eq .Type.Storage "string") (eq .Type.Storage "pkg.Bytes") }} case {{ $.StructName }}Type{{.Name}}: {{- if .Type.MustClone}} - {{- if .Type.Flags.StoreByPtr}} allocators.allocSizeChecker.AddAllocSize(uint(unsafe.Sizeof({{.Type.Storage}}{}))) - dst.{{.name}} = allocators.{{ .Type.Storage }}.Alloc() - dst.{{.name}}.init(dst.parentModifiedFields, dst.parentModifiedBit) - {{- end}} - copyToNew{{.Type.TypeName}}( - {{- if .Type.Flags.TakePtr}}&{{end}}dst.{{.name }}, - {{- if .Type.Flags.TakePtr}}&{{end}}src.{{.name}}, allocators) + dst.alloc{{.Name}}Alloc(allocators) + copyToNew{{.Type.TypeName}}(dst.{{.name}}Ptr(), src.{{.name}}Ptr(), allocators) + {{- else}} + {{- if $isStringOrBytes}} + srcVal := src.{{.Name}}() + if string(srcVal) != "" { + dst.set{{.Name}}(srcVal) + dst.parentModifiedFields.markModified(dst.parentModifiedBit) + } {{- else}} - if dst.{{.name}} != src.{{.name}} { - dst.{{.name}} = src.{{.name}} + if *dst.{{.name}}Ptr() != *src.{{.name}}Ptr() { + *dst.{{.name}}Ptr() = *src.{{.name}}Ptr() dst.parentModifiedFields.markModified(dst.parentModifiedBit) } {{- end}} + {{- end}} {{- end}} case {{ $.StructName }}TypeNone: - default: panic("copy{{.StructName}}: unexpected type: " + fmt.Sprint(src.typ)) + default: panic("copy{{.StructName}}: unexpected type: " + fmt.Sprint(src.Type())) } } @@ -277,22 +355,22 @@ func (s* {{.StructName}}) markParentModified() { } func (s *{{ $.StructName }}) setModifiedRecursively() { - switch s.typ { + switch s.Type() { {{- range .Fields}} {{- if not .Type.IsPrimitive}} case {{ $.StructName }}Type{{.Name}}: - s.{{.name}}.setModifiedRecursively() + s.{{.name}}Ptr().setModifiedRecursively() {{- end}} {{- end}} } } func (s *{{ $.StructName }}) setUnmodifiedRecursively() { - switch s.typ { + switch s.Type() { {{- range .Fields}} {{- if not .Type.IsPrimitive}} case {{ $.StructName }}Type{{.Name}}: - s.{{.name}}.setUnmodifiedRecursively() + s.{{.name}}Ptr().setUnmodifiedRecursively() {{- end}} {{- end}} } @@ -301,25 +379,26 @@ func (s *{{ $.StructName }}) setUnmodifiedRecursively() { // computeDiff compares s and val and returns true if they differ. // All fields that are different in s will be marked as modified. func (s *{{ $.StructName }}) computeDiff(val *{{ $.StructName }}) (ret bool) { - if s.typ == val.typ { - switch s.typ { + if s.Type() == val.Type() { + switch s.Type() { {{- range .Fields}} + {{- $isStringOrBytes := or (eq .Type.Storage "string") (eq .Type.Storage "pkg.Bytes") }} case {{ $.StructName }}Type{{.Name}}: {{- if .Type.IsPrimitive}} - ret = s.{{.name}} != val.{{.name}} + ret = s.{{.Name}}() != val.{{.Name}}() {{- else}} - ret = s.{{.name}}.computeDiff({{- if .Type.Flags.TakePtr}}&{{end}}val.{{.name}}) + ret = s.{{.name}}Ptr().computeDiff(val.{{.name}}Ptr()) {{- end}} {{- end }} } } else { ret = true - switch s.typ { + switch s.Type() { {{- range .Fields}} {{- if not .Type.IsPrimitive}} case {{ $.StructName }}Type{{.Name}}: // val.{{.name}} doesn't exist at all so mark the whole s.{{.name}} subtree as modified. - s.{{.name}}.setModifiedRecursively() + s.{{.name}}Ptr().setModifiedRecursively() {{- end}} {{- end }} } @@ -329,16 +408,21 @@ func (s *{{ $.StructName }}) computeDiff(val *{{ $.StructName }}) (ret bool) { // IsEqual performs deep comparison and returns true if struct is equal to val. func (e *{{ .StructName }}) IsEqual(val *{{ .StructName }}) bool { - if e.typ != val.typ { + if e.Type() != val.Type() { return false } - switch e.typ { + switch e.Type() { {{- range .Fields }} + {{- $isStringOrBytes := or (eq .Type.Storage "string") (eq .Type.Storage "pkg.Bytes") }} case {{ $.StructName }}Type{{.Name}}: {{- if .Type.IsPrimitive }} - return {{ .Type.EqualFunc }}(e.{{.name}}, val.{{.name}}) + {{- if $isStringOrBytes}} + return {{ .Type.EqualFunc }}(e.{{.Name}}(), val.{{.Name}}()) + {{- else}} + return {{ .Type.EqualFunc }}(*e.{{.name}}Ptr(), *val.{{.name}}Ptr()) + {{- end}} {{- else }} - return e.{{.name}}.IsEqual({{- if .Type.Flags.TakePtr}}&{{end}}val.{{.name}}) + return e.{{.name}}Ptr().IsEqual(val.{{.name}}Ptr()) {{- end }} {{- end }} } @@ -349,16 +433,22 @@ func (e *{{ .StructName }}) IsEqual(val *{{ .StructName }}) bool { // Cmp{{.StructName}} performs deep comparison and returns an integer that // will be 0 if left == right, negative if left < right, positive if left > right. func Cmp{{.StructName}}(left, right *{{.StructName}}) int { - c := pkg.Uint64Compare(uint64(left.typ), uint64(right.typ)) + c := pkg.Uint64Compare(uint64(left.Type()), uint64(right.Type())) if c != 0 { return c } - switch left.typ { + switch left.Type() { {{- range .Fields }} + {{- $isStringOrBytes := or (eq .Type.Storage "string") (eq .Type.Storage "pkg.Bytes") }} case {{ $.StructName }}Type{{.Name}}: return {{ .Type.CompareFunc }}( - {{- if .Type.Flags.TakePtr}}&{{end}}left.{{.name}}, - {{- if .Type.Flags.TakePtr}}&{{end}}right.{{.name}}) + {{- if .Type.IsPrimitive}} + {{- if $isStringOrBytes}}left.{{.Name}}(), right.{{.Name}}() + {{- else}}*left.{{.name}}Ptr(), *right.{{.name}}Ptr() + {{- end}} + {{- else}} + left.{{.name}}Ptr(), right.{{.name}}Ptr() + {{- end}}) {{- end }} } @@ -384,12 +474,12 @@ typeChanged := false typeChanged = true } {{ end }} - switch s.typ { + switch s.Type() { {{- range .Fields }} case {{ $.StructName }}Type{{.Name}}: if typeChanged || random.IntN(2)==0 { {{- if not .Type.IsPrimitive }} - s.{{.name}}.mutateRandom(random, schem, limiter) + s.{{.name}}Ptr().mutateRandom(random, schem, limiter) {{- else }} s.Set{{.Name}}({{.Type.ToExported (print .Type.RandomFunc)}}) {{- end}} @@ -507,7 +597,7 @@ func (e *{{ .StructName }}Encoder) Reset() { // Encode encodes val into buf func (e *{{ .StructName }}Encoder) Encode(val *{{ .StructName }}) { - typ := val.typ + typ := val.Type() if uint(typ) > e.fieldCount { // The current field type is not supported in target schema. Encode the type as None. typ = {{ $.StructName }}TypeNone @@ -521,9 +611,18 @@ func (e *{{ .StructName }}Encoder) Encode(val *{{ .StructName }}) { // Encode currently selected field. switch typ { {{- range .Fields }} + {{- $isStringOrBytes := or (eq .Type.Storage "string") (eq .Type.Storage "pkg.Bytes") }} case {{ $.StructName }}Type{{.Name}}: // Encode {{.Name}} - e.{{.name}}Encoder.Encode({{- if .Type.Flags.TakePtr}}&{{end}}val.{{.name}}) + {{- if .Type.IsPrimitive}} + {{- if $isStringOrBytes}} + e.{{.name}}Encoder.Encode(val.{{.Name}}()) + {{- else}} + e.{{.name}}Encoder.Encode(*val.{{.name}}Ptr()) + {{- end}} + {{- else}} + e.{{.name}}Encoder.Encode(val.{{.name}}Ptr()) + {{- end}} {{- end }} } } @@ -682,37 +781,55 @@ func (d *{{ .StructName }}Decoder) Decode(dstPtr {{if.DictName}}*{{end}}*{{.Stru } dst := dstPtr - if dst.typ != {{.StructName}}Type(typ) { - dst.typ = {{.StructName}}Type(typ) + if dst.Type() != {{.StructName}}Type(typ) { // The type changed, we need to reset the contained value so that // it does not contain carry-over data from a previous record that // was of this same type. - dst.resetContained() + dst.clearValSetType({{.StructName}}Type(typ)) + + switch {{.StructName}}Type(typ) { + {{- range .Fields }} + {{- if and (not .Type.IsPrimitive) (not .Type.DictName)}} + case {{ $.StructName }}Type{{.Name}}: + // Allocate {{.Name}} + if err := d.allocators.allocSizeChecker.PrepAllocSize(uint(unsafe.Sizeof({{.Type.Storage}}{}))); err != nil { + return err + } + dst.alloc{{.Name}}Alloc(d.allocators) + {{- end}} + {{- end }} + } } // Decode selected field - switch dst.typ { + switch dst.Type() { {{- range .Fields }} + {{- $isStringOrBytes := or (eq .Type.Storage "string") (eq .Type.Storage "pkg.Bytes") }} + {{- $isBits := and .Type.IsPrimitive (not $isStringOrBytes) }} case {{ $.StructName }}Type{{.Name}}: // Decode {{.Name}} - {{- if .Type.Flags.StoreByPtr}} - if dst.{{.name}} == nil { - if err := d.allocators.allocSizeChecker.PrepAllocSize(uint(unsafe.Sizeof(*dst.{{.name}}))); err != nil { - return err - } - dst.{{.name}} = d.allocators.{{ .Type.Storage }}.Alloc() - dst.{{.name}}.init(dst.parentModifiedFields, dst.parentModifiedBit) + {{- if $isStringOrBytes}} + var v {{.Type.Storage}} + if err := d.{{.name}}Decoder.Decode(&v); err != nil { + return err } - {{end}} - {{ $isDictPtr := and .Type.Flags.StoreByPtr .Type.DictName -}} - {{if $isDictPtr}}err := {{else}}return {{end}}d.{{.name}}Decoder.Decode({{if .Type.Flags.DecodeByPtr}}&{{end}}dst.{{.name}}) - {{- if $isDictPtr}} + dst.set{{.Name}}(v) + {{- else if $isBits}} + return d.{{.name}}Decoder.Decode(dst.{{.name}}Ptr()) + {{- else}} + {{- if .Type.DictName}} + v := dst.{{.name}}Ptr() + err := d.{{.name}}Decoder.Decode(&v) if err != nil { return err } - if dst.{{.name}} == nil { + if v == nil { return pkg.ErrDecodeError // This may happen with dict-encoded fields, with invalid input data. } + dst.ptr = unsafe.Pointer(v) + {{- else}} + return d.{{.name}}Decoder.Decode(dst.{{.name}}Ptr()) + {{- end}} {{- end}} {{- end }} } diff --git a/stefc/templates/go/struct.go.tmpl b/stefc/templates/go/struct.go.tmpl index 6e69b5b0..27a60cf6 100644 --- a/stefc/templates/go/struct.go.tmpl +++ b/stefc/templates/go/struct.go.tmpl @@ -114,6 +114,12 @@ func (s *{{ $.StructName }}) initAlloc(parentModifiedFields *modifiedFields, par {{- end -}} } +// Reset the struct to its initial state. +func (s *{{ $.StructName }}) Reset() { + s.reset() + s.setUnmodifiedRecursively() +} + // reset the struct to its initial state, as if init() was just called. // Will not reset internal fields such as parentModifiedFields. func (s *{{ $.StructName }}) reset() {