@@ -24,27 +24,31 @@ import (
2424 "strings"
2525)
2626
27- // FieldInfo stores field metadata computed ENTIRELY at init time.
28- // All flags and decisions are pre-computed to eliminate runtime checks.
29- type FieldInfo struct {
27+ // PrimitiveFieldInfo contains only the fields needed for hot primitive serialization loops.
28+ // This minimal struct improves cache efficiency during iteration.
29+ // Size: 16 bytes (vs full FieldInfo)
30+ type PrimitiveFieldInfo struct {
31+ Offset uintptr // Field offset for unsafe access
32+ DispatchId DispatchId // Type dispatch ID
33+ WriteOffset uint8 // Offset within fixed-fields buffer (0-255, sufficient for fixed primitives)
34+ }
35+
36+ // FieldMeta contains cold/rarely-accessed field metadata.
37+ // Accessed via pointer from FieldInfo to keep FieldInfo small for cache efficiency.
38+ type FieldMeta struct {
3039 Name string
31- Offset uintptr
3240 Type reflect.Type
33- DispatchId DispatchId
34- TypeId TypeId // Fory type ID for the serializer
35- Serializer Serializer
41+ TypeId TypeId // Fory type ID for the serializer
3642 Nullable bool
3743 FieldIndex int // -1 if field doesn't exist in current struct (for compatible mode)
3844 FieldDef FieldDef // original FieldDef from remote TypeDef (for compatible mode skip)
3945
40- // Pre-computed sizes and offsets (for fixed primitives)
41- FixedSize int // 0 if not fixed-size, else 1/2/4/8
42- WriteOffset int // Offset within fixed-fields buffer region (sum of preceding field sizes)
46+ // Pre-computed sizes (for fixed primitives)
47+ FixedSize int // 0 if not fixed-size, else 1/2/4/8
4348
4449 // Pre-computed flags for serialization (computed at init time)
45- RefMode RefMode // ref mode for serializer.Write/Read
46- WriteType bool // whether to write type info (true for struct fields in compatible mode)
47- HasGenerics bool // whether element types are known from TypeDef (for container fields)
50+ WriteType bool // whether to write type info (true for struct fields in compatible mode)
51+ HasGenerics bool // whether element types are known from TypeDef (for container fields)
4852
4953 // Tag-based configuration (from fory struct tags)
5054 TagID int // -1 = use field name, >=0 = use tag ID
@@ -53,9 +57,21 @@ type FieldInfo struct {
5357 TagRef bool // The ref value from fory tag (only valid if TagRefSet is true)
5458 TagNullableSet bool // Whether nullable was explicitly set via fory tag
5559 TagNullable bool // The nullable value from fory tag (only valid if TagNullableSet is true)
60+ }
5661
57- // Pre-computed type flags (computed at init time to avoid runtime reflection)
58- IsPtr bool // True if field.Type.Kind() == reflect.Ptr
62+ // FieldInfo stores field metadata computed ENTIRELY at init time.
63+ // Hot fields are kept inline for cache efficiency, cold fields accessed via Meta pointer.
64+ type FieldInfo struct {
65+ // Hot fields - accessed frequently during serialization
66+ Offset uintptr // Field offset for unsafe access
67+ DispatchId DispatchId // Type dispatch ID
68+ WriteOffset int // Offset within fixed-fields buffer region (sum of preceding field sizes)
69+ RefMode RefMode // ref mode for serializer.Write/Read
70+ IsPtr bool // True if field.Type.Kind() == reflect.Ptr
71+ Serializer Serializer // Serializer for this field
72+
73+ // Cold fields - accessed less frequently
74+ Meta * FieldMeta
5975}
6076
6177// FieldGroup holds categorized and sorted fields for optimized serialization.
@@ -65,6 +81,11 @@ type FieldInfo struct {
6581// - VarintFields: non-nullable varint primitives (varint32/64, var_uint32/64, tagged_int64/uint64)
6682// - RemainingFields: all other fields (nullable primitives, strings, collections, structs, etc.)
6783type FieldGroup struct {
84+ // Primitive field slices - minimal data for fast iteration in hot loops
85+ PrimitiveFixedFields []PrimitiveFieldInfo // Minimal fixed field info for hot loop
86+ PrimitiveVarintFields []PrimitiveFieldInfo // Minimal varint field info for hot loop
87+
88+ // Full field info for remaining fields and fallback paths
6889 FixedFields []FieldInfo // Non-nullable fixed-size primitives
6990 VarintFields []FieldInfo // Non-nullable varint primitives
7091 RemainingFields []FieldInfo // All other fields
@@ -100,19 +121,19 @@ func (g *FieldGroup) DebugPrint(typeName string) {
100121 for i := range g .FixedFields {
101122 f := & g .FixedFields [i ]
102123 fmt .Printf ("[Go] [%d] %s -> dispatchId=%d, typeId=%d, size=%d, nullable=%v\n " ,
103- i , f .Name , f .DispatchId , f .TypeId , f .FixedSize , f .Nullable )
124+ i , f .Meta . Name , f .DispatchId , f .Meta . TypeId , f .Meta . FixedSize , f . Meta .Nullable )
104125 }
105126 fmt .Printf ("[Go] Go sorted varintFields (%d):\n " , len (g .VarintFields ))
106127 for i := range g .VarintFields {
107128 f := & g .VarintFields [i ]
108129 fmt .Printf ("[Go] [%d] %s -> dispatchId=%d, typeId=%d, nullable=%v\n " ,
109- i , f .Name , f .DispatchId , f .TypeId , f .Nullable )
130+ i , f .Meta . Name , f .DispatchId , f .Meta . TypeId , f . Meta .Nullable )
110131 }
111132 fmt .Printf ("[Go] Go sorted remainingFields (%d):\n " , len (g .RemainingFields ))
112133 for i := range g .RemainingFields {
113134 f := & g .RemainingFields [i ]
114135 fmt .Printf ("[Go] [%d] %s -> dispatchId=%d, typeId=%d, nullable=%v\n " ,
115- i , f .Name , f .DispatchId , f .TypeId , f .Nullable )
136+ i , f .Meta . Name , f .DispatchId , f .Meta . TypeId , f . Meta .Nullable )
116137 }
117138 fmt .Printf ("[Go] ===========================================\n " )
118139}
@@ -126,11 +147,11 @@ func GroupFields(fields []FieldInfo) FieldGroup {
126147 // Categorize fields
127148 for i := range fields {
128149 field := & fields [i ]
129- if isFixedSizePrimitive (field .DispatchId , field .Nullable ) {
150+ if isFixedSizePrimitive (field .DispatchId , field .Meta . Nullable ) {
130151 // Non-nullable fixed-size primitives only
131- field .FixedSize = getFixedSizeByDispatchId (field .DispatchId )
152+ field .Meta . FixedSize = getFixedSizeByDispatchId (field .DispatchId )
132153 g .FixedFields = append (g .FixedFields , * field )
133- } else if isVarintPrimitive (field .DispatchId , field .Nullable ) {
154+ } else if isVarintPrimitive (field .DispatchId , field .Meta . Nullable ) {
134155 // Non-nullable varint primitives only
135156 g .VarintFields = append (g .VarintFields , * field )
136157 } else {
@@ -142,19 +163,25 @@ func GroupFields(fields []FieldInfo) FieldGroup {
142163 // Sort fixedFields: size desc, typeId desc, name asc
143164 sort .SliceStable (g .FixedFields , func (i , j int ) bool {
144165 fi , fj := & g .FixedFields [i ], & g .FixedFields [j ]
145- if fi .FixedSize != fj .FixedSize {
146- return fi .FixedSize > fj .FixedSize // size descending
166+ if fi .Meta . FixedSize != fj . Meta .FixedSize {
167+ return fi .Meta . FixedSize > fj . Meta .FixedSize // size descending
147168 }
148- if fi .TypeId != fj .TypeId {
149- return fi .TypeId > fj .TypeId // typeId descending
169+ if fi .Meta . TypeId != fj . Meta .TypeId {
170+ return fi .Meta . TypeId > fj . Meta .TypeId // typeId descending
150171 }
151- return fi .Name < fj .Name // name ascending
172+ return fi .Meta . Name < fj . Meta .Name // name ascending
152173 })
153174
154- // Compute WriteOffset after sorting
175+ // Compute WriteOffset after sorting and build primitive field slice
176+ g .PrimitiveFixedFields = make ([]PrimitiveFieldInfo , len (g .FixedFields ))
155177 for i := range g .FixedFields {
156178 g .FixedFields [i ].WriteOffset = g .FixedSize
157- g .FixedSize += g .FixedFields [i ].FixedSize
179+ g .PrimitiveFixedFields [i ] = PrimitiveFieldInfo {
180+ Offset : g .FixedFields [i ].Offset ,
181+ DispatchId : g .FixedFields [i ].DispatchId ,
182+ WriteOffset : uint8 (g .FixedSize ),
183+ }
184+ g .FixedSize += g .FixedFields [i ].Meta .FixedSize
158185 }
159186
160187 // Sort varintFields: underlying type size desc, typeId desc, name asc
@@ -166,15 +193,21 @@ func GroupFields(fields []FieldInfo) FieldGroup {
166193 if sizeI != sizeJ {
167194 return sizeI > sizeJ // size descending
168195 }
169- if fi .TypeId != fj .TypeId {
170- return fi .TypeId > fj .TypeId // typeId descending
196+ if fi .Meta . TypeId != fj . Meta .TypeId {
197+ return fi .Meta . TypeId > fj . Meta .TypeId // typeId descending
171198 }
172- return fi .Name < fj .Name // name ascending
199+ return fi .Meta . Name < fj . Meta .Name // name ascending
173200 })
174201
175- // Compute maxVarintSize
202+ // Compute maxVarintSize and build primitive varint field slice
203+ g .PrimitiveVarintFields = make ([]PrimitiveFieldInfo , len (g .VarintFields ))
176204 for i := range g .VarintFields {
177205 g .MaxVarintSize += getVarintMaxSizeByDispatchId (g .VarintFields [i ].DispatchId )
206+ g .PrimitiveVarintFields [i ] = PrimitiveFieldInfo {
207+ Offset : g .VarintFields [i ].Offset ,
208+ DispatchId : g .VarintFields [i ].DispatchId ,
209+ // WriteOffset not used for varint fields (variable length)
210+ }
178211 }
179212
180213 // Sort remainingFields: nullable primitives first (by primitiveComparator),
@@ -192,8 +225,8 @@ func GroupFields(fields []FieldInfo) FieldGroup {
192225 // Within other internal types category (STRING, BINARY, LIST, SET, MAP),
193226 // sort by typeId then by sort key (tagID if available, otherwise name).
194227 if catI == 1 {
195- if fi .TypeId != fj .TypeId {
196- return fi .TypeId < fj .TypeId
228+ if fi .Meta . TypeId != fj . Meta .TypeId {
229+ return fi .Meta . TypeId < fj . Meta .TypeId
197230 }
198231 return getFieldSortKey (fi ) < getFieldSortKey (fj )
199232 }
@@ -215,7 +248,7 @@ func fieldHasNonPrimitiveSerializer(field *FieldInfo) bool {
215248 // all require special serialization and should not use the primitive fast path
216249 // Note: ENUM uses unsigned Varuint32Small7 for ordinals, not signed zigzag varint
217250 // Use internal type ID (low 8 bits) since registered types have composite TypeIds like (userID << 8) | internalID
218- internalTypeId := TypeId (field .TypeId & 0xFF )
251+ internalTypeId := TypeId (field .Meta . TypeId & 0xFF )
219252 switch internalTypeId {
220253 case ENUM , NAMED_ENUM , NAMED_STRUCT , NAMED_COMPATIBLE_STRUCT , NAMED_EXT :
221254 return true
@@ -229,7 +262,7 @@ func isEnumField(field *FieldInfo) bool {
229262 if field .Serializer == nil {
230263 return false
231264 }
232- internalTypeId := field .TypeId & 0xFF
265+ internalTypeId := field .Meta . TypeId & 0xFF
233266 return internalTypeId == ENUM || internalTypeId == NAMED_ENUM
234267}
235268
@@ -241,7 +274,7 @@ func getFieldCategory(field *FieldInfo) int {
241274 if isNullableFixedSizePrimitive (field .DispatchId ) || isNullableVarintPrimitive (field .DispatchId ) {
242275 return 0
243276 }
244- internalId := field .TypeId & 0xFF
277+ internalId := field .Meta . TypeId & 0xFF
245278 switch TypeId (internalId ) {
246279 case STRING , BINARY , LIST , SET , MAP :
247280 // Internal types: sorted by typeId, then name
@@ -267,10 +300,10 @@ func comparePrimitiveFields(fi, fj *FieldInfo) bool {
267300 if sizeI != sizeJ {
268301 return sizeI > sizeJ // size descending
269302 }
270- if fi .TypeId != fj .TypeId {
271- return fi .TypeId > fj .TypeId // typeId descending
303+ if fi .Meta . TypeId != fj . Meta .TypeId {
304+ return fi .Meta . TypeId > fj . Meta .TypeId // typeId descending
272305 }
273- return fi .Name < fj .Name // name ascending
306+ return fi .Meta . Name < fj . Meta .Name // name ascending
274307}
275308
276309// getNullableFixedSize returns the fixed size for nullable fixed primitives
@@ -532,10 +565,10 @@ func (t triple) getSortKey() string {
532565// If TagID >= 0, returns the tag ID as string (for tag-based sorting).
533566// Otherwise returns the field name (which is already snake_case).
534567func getFieldSortKey (f * FieldInfo ) string {
535- if f .TagID >= 0 {
536- return fmt .Sprintf ("%d" , f .TagID )
568+ if f .Meta . TagID >= 0 {
569+ return fmt .Sprintf ("%d" , f .Meta . TagID )
537570 }
538- return f .Name
571+ return f .Meta . Name
539572}
540573
541574// sortFields sorts fields with nullable information to match Java's field ordering.
0 commit comments