github.com/cloudwego/dynamicgo@v0.2.6-0.20240519101509-707f41b6b834/proto/utils.go (about)

     1  package proto
     2  
     3  import (
     4  	"unsafe"
     5  
     6  	"github.com/cloudwego/dynamicgo/internal/caching"
     7  )
     8  
     9  const (
    10  	defaultMaxBucketSize     float64 = 10
    11  	defaultMapSize           int     = 4
    12  	defaultHashMapLoadFactor int     = 4
    13  	defaultMaxFieldID                = 256
    14  	defaultMaxNestedDepth            = 1024
    15  )
    16  
    17  // FieldNameMap is a map for field name and field descriptor
    18  type FieldNameMap struct {
    19  	maxKeyLength int
    20  	all          []caching.Pair
    21  	trie         *caching.TrieTree
    22  	hash         *caching.HashMap
    23  }
    24  
    25  // Set sets the field descriptor for the given key
    26  func (ft *FieldNameMap) Set(key string, field *FieldDescriptor) (exist bool) {
    27  	if len(key) > ft.maxKeyLength {
    28  		ft.maxKeyLength = len(key)
    29  	}
    30  	for i, v := range ft.all {
    31  		if v.Key == key {
    32  			exist = true
    33  			ft.all[i].Val = unsafe.Pointer(field)
    34  			return
    35  		}
    36  	}
    37  	ft.all = append(ft.all, caching.Pair{Val: unsafe.Pointer(field), Key: key})
    38  	return
    39  }
    40  
    41  // Get gets the field descriptor for the given key
    42  func (ft FieldNameMap) Get(k string) *FieldDescriptor {
    43  	if ft.trie != nil {
    44  		return (*FieldDescriptor)(ft.trie.Get(k))
    45  	} else if ft.hash != nil {
    46  		return (*FieldDescriptor)(ft.hash.Get(k))
    47  	}
    48  	return nil
    49  }
    50  
    51  // All returns all field descriptors
    52  func (ft FieldNameMap) All() []*FieldDescriptor {
    53  	return *(*[]*FieldDescriptor)(unsafe.Pointer(&ft.all))
    54  }
    55  
    56  // Size returns the size of the map
    57  func (ft FieldNameMap) Size() int {
    58  	if ft.hash != nil {
    59  		return ft.hash.Size()
    60  	} else {
    61  		return ft.trie.Size()
    62  	}
    63  }
    64  
    65  // Build builds the map.
    66  // It will try to build a trie tree if the dispersion of keys is higher enough (min).
    67  func (ft *FieldNameMap) Build() {
    68  	var empty unsafe.Pointer
    69  
    70  	// statistics the distrubution for each position:
    71  	//   - primary slice store the position as its index
    72  	//   - secondary map used to merge values with same char at the same position
    73  	var positionDispersion = make([]map[byte][]int, ft.maxKeyLength)
    74  
    75  	for i, v := range ft.all {
    76  		for j := ft.maxKeyLength - 1; j >= 0; j-- {
    77  			if v.Key == "" {
    78  				// empty key, especially store
    79  				empty = v.Val
    80  			}
    81  			// get the char at the position, defualt (position beyonds key range) is ASCII 0
    82  			var c = byte(0)
    83  			if j < len(v.Key) {
    84  				c = v.Key[j]
    85  			}
    86  
    87  			if positionDispersion[j] == nil {
    88  				positionDispersion[j] = make(map[byte][]int, 16)
    89  			}
    90  			// recoder the index i of the value with same char c at the same position j
    91  			positionDispersion[j][c] = append(positionDispersion[j][c], i)
    92  		}
    93  	}
    94  
    95  	// calculate the best position which has the highest dispersion
    96  	var idealPos = -1
    97  	var min = defaultMaxBucketSize
    98  	var count = len(ft.all)
    99  
   100  	for i := ft.maxKeyLength - 1; i >= 0; i-- {
   101  		cd := positionDispersion[i]
   102  		l := len(cd)
   103  		// calculate the dispersion (average bucket size)
   104  		f := float64(count) / float64(l)
   105  		if f < min {
   106  			min = f
   107  			idealPos = i
   108  		}
   109  		// 1 means all the value store in different bucket, no need to continue calulating
   110  		if min == 1 {
   111  			break
   112  		}
   113  	}
   114  
   115  	if idealPos != -1 {
   116  		// find the best position, build a trie tree
   117  		ft.hash = nil
   118  		ft.trie = &caching.TrieTree{}
   119  		// NOTICE: we only use a two-layer tree here, for better performance
   120  		ft.trie.Positions = append(ft.trie.Positions, idealPos)
   121  		// set all key-values to the trie tree
   122  		for _, v := range ft.all {
   123  			ft.trie.Set(v.Key, v.Val)
   124  		}
   125  		if empty != nil {
   126  			ft.trie.Empty = empty
   127  		}
   128  
   129  	} else {
   130  		// no ideal position, build a hash map
   131  		ft.trie = nil
   132  		ft.hash = caching.NewHashMap(len(ft.all), defaultHashMapLoadFactor)
   133  		// set all key-values to the trie tree
   134  		for _, v := range ft.all {
   135  			// caching.HashMap does not support duplicate key, so must check if the key exists before set
   136  			// WARN: if the key exists, the value WON'T be replaced
   137  			o := ft.hash.Get(v.Key)
   138  			if o == nil {
   139  				ft.hash.Set(v.Key, v.Val)
   140  			}
   141  		}
   142  		if empty != nil {
   143  			ft.hash.Set("", empty)
   144  		}
   145  	}
   146  }
   147  
   148  // FieldIDMap is a map from field id to field descriptor
   149  type FieldNumberMap struct {
   150  	m   []*FieldDescriptor
   151  	all []*FieldDescriptor
   152  }
   153  
   154  // All returns all field descriptors
   155  func (fd FieldNumberMap) All() (ret []*FieldDescriptor) {
   156  	return fd.all
   157  }
   158  
   159  // Size returns the size of the map
   160  func (fd FieldNumberMap) Size() int {
   161  	return len(fd.m)
   162  }
   163  
   164  // Get gets the field descriptor for the given id
   165  func (fd FieldNumberMap) Get(id FieldNumber) *FieldDescriptor {
   166  	if int(id) >= len(fd.m) {
   167  		return nil
   168  	}
   169  	return fd.m[id]
   170  }
   171  
   172  // Set sets the field descriptor for the given id
   173  func (fd *FieldNumberMap) Set(id FieldNumber, f *FieldDescriptor) {
   174  	if int(id) >= len(fd.m) {
   175  		len := int(id) + 1
   176  		tmp := make([]*FieldDescriptor, len)
   177  		copy(tmp, fd.m)
   178  		fd.m = tmp
   179  	}
   180  	o := (fd.m)[id]
   181  	if o == nil {
   182  		fd.all = append(fd.all, f)
   183  	} else {
   184  		for i, v := range fd.all {
   185  			if v == o {
   186  				fd.all[i] = f
   187  				break
   188  			}
   189  		}
   190  	}
   191  	fd.m[id] = f
   192  }