/* * Copyright 2021 ByteDance Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package caching import ( "strings" "unicode" "unsafe" "github.com/bytedance/sonic/internal/envs" "github.com/bytedance/sonic/internal/native" "github.com/bytedance/sonic/internal/resolver" "github.com/bytedance/sonic/internal/rt" ) const _AlignSize = 32 const _PaddingSize = 32 type FieldLookup interface { Set(fields []resolver.FieldMeta) Get(name string) int } func isAscii(s string) bool { for i :=0; i < len(s); i++ { if s[i] > unicode.MaxASCII { return false } } return true } func NewFieldLookup(fields []resolver.FieldMeta) FieldLookup { var f FieldLookup isAsc := true n := len(fields) // when field name has non-ascii, use the fallback methods to use strings.ToLower for _, f := range fields { if !isAscii(f.Name) { isAsc = false break } } if n <= 8 { f = NewSmallFieldMap(n) } else if envs.UseFastMap && n <= 128 && isAsc { f = NewNormalFieldMap(n) } else { f = NewFallbackFieldMap(n) } f.Set(fields) return f } // Map for keys nums max 8, idx is in [0, 8) type SmallFieldMap struct { keys []string lowerKeys []string } func NewSmallFieldMap (hint int) *SmallFieldMap { return &SmallFieldMap{ keys: make([]string, hint, hint), lowerKeys: make([]string, hint, hint), } } func (self *SmallFieldMap) Set(fields []resolver.FieldMeta) { if len(fields) > 8 { panic("small field map shoud use in small struct") } for i, f := range fields { self.keys[i] = f.Name self.lowerKeys[i] = strings.ToLower(f.Name) } } func (self *SmallFieldMap) Get(name string) int { for i, k := range self.keys { if len(k) == len(name) && k == name { return i } } name = strings.ToLower(name) for i, k := range self.lowerKeys { if len(k) == len(name) && k == name { return i } } return -1 } /* 1. select by the length: 0 ~ 32 and larger lengths 2. simd match the aligned prefix of the keys: 4/8/16/32 bytes or larger keys 3. check the key with strict match 4. check the key with case-insensitive match 5. find the index Mem Layout: fixed 33 * 5 bytes 165 bytes ||| variable keys ||| variable lowerkeys | length metadata array[33] ||| key0.0 | u8 | key0.1 | u8 | ... || key1.0 | u8 | key1.1 | u8 | ... ||| lowerkeys info ... */ // Map for keys nums max 255, idx is in [0, 255), idx 255 means not found. // keysoffset // | metadata | aligned key0 | aligned key1 | ... | // 1 ~ 8 // 8 ~ 16 // 16 ~ 32 // > 32 keys use the long keys entry lists // use bytes to reduce GC type NormalFieldMap struct { keys []byte longKeys []keyEntry // offset for lower lowOffset int } type keyEntry struct { key string lowerKey string index uint } func NewNormalFieldMap(n int) *NormalFieldMap { return &NormalFieldMap{ } } const _HdrSlot = 33 const _HdrSize = _HdrSlot * 5 // use native SIMD to accelerate it func (self *NormalFieldMap) Get(name string) int { // small keys use native C if len(name) <= 32 { _ = native.LookupSmallKey return native.LookupSmallKey(&name, &self.keys, self.lowOffset); } return self.getLongKey(name) } func (self *NormalFieldMap) getLongKey(name string) int { for _, k := range self.longKeys { if len(k.key) != len(name) { continue; } if k.key == name { return int(k.index) } } lower := strings.ToLower(name) for _, k := range self.longKeys { if len(k.key) != len(name) { continue; } if k.lowerKey == lower { return int(k.index) } } return -1 } func (self *NormalFieldMap) Getdouble(name string) int { if len(name) > 32 { for _, k := range self.longKeys { if len(k.key) != len(name) { continue; } if k.key == name { return int(k.index) } } return self.getCaseInsensitive(name) } // check the fixed length keys, not found the target length cnt := int(self.keys[5 * len(name)]) if cnt == 0 { return -1 } p := ((*rt.GoSlice)(unsafe.Pointer(&self.keys))).Ptr offset := int(*(*int32)(unsafe.Pointer(uintptr(p) + uintptr(5 * len(name) + 1)))) + _HdrSize for i := 0; i < cnt; i++ { key := rt.Mem2Str(self.keys[offset: offset + len(name)]) if key == name { return int(self.keys[offset + len(name)]) } offset += len(name) + 1 } return self.getCaseInsensitive(name) } func (self *NormalFieldMap) getCaseInsensitive(name string) int { lower := strings.ToLower(name) if len(name) > 32 { for _, k := range self.longKeys { if len(k.key) != len(name) { continue; } if k.lowerKey == lower { return int(k.index) } } return -1 } cnt := int(self.keys[5 * len(name)]) p := ((*rt.GoSlice)(unsafe.Pointer(&self.keys))).Ptr offset := int(*(*int32)(unsafe.Pointer(uintptr(p) + uintptr(5 * len(name) + 1)))) + self.lowOffset for i := 0; i < cnt; i++ { key := rt.Mem2Str(self.keys[offset: offset + len(name)]) if key == lower { return int(self.keys[offset + len(name)]) } offset += len(name) + 1 } return -1 } type keysInfo struct { counts int lenSum int offset int cur int } func (self *NormalFieldMap) Set(fields []resolver.FieldMeta) { if len(fields) <=8 || len(fields) > 128 { panic("normal field map shoud use in small struct") } // allocate the flat map in []byte var keyLenSum [_HdrSlot]keysInfo for i := 0; i < _HdrSlot; i++ { keyLenSum[i].offset = 0 keyLenSum[i].counts = 0 keyLenSum[i].lenSum = 0 keyLenSum[i].cur = 0 } kvLen := 0 for _, f := range(fields) { len := len(f.Name) if len <= 32 { kvLen += len + 1 // key + index keyLenSum[len].counts++ keyLenSum[len].lenSum += len + 1 } } // add a padding size at last to make it firendly for SIMD. self.keys = make([]byte, _HdrSize + 2 * kvLen, _HdrSize + 2 * kvLen + _PaddingSize) self.lowOffset = _HdrSize + kvLen // initialize all keys offset self.keys[0] = byte(keyLenSum[0].counts) // self.keys[1:5] = 0 // offset is always zero here. i := 1 p := ((*rt.GoSlice)(unsafe.Pointer(&self.keys))).Ptr for i < _HdrSlot { keyLenSum[i].offset = keyLenSum[i-1].offset + keyLenSum[i-1].lenSum self.keys[i * 5] = byte(keyLenSum[i].counts) // write the offset into []byte *(*int32)(unsafe.Pointer(uintptr(p) + uintptr(i * 5 + 1))) = int32(keyLenSum[i].offset) i += 1 } // fill the key into bytes for i, f := range(fields) { len := len(f.Name) if len <= 32 { offset := keyLenSum[len].offset + keyLenSum[len].cur copy(self.keys[_HdrSize + offset: ], f.Name) copy(self.keys[self.lowOffset + offset: ], strings.ToLower(f.Name)) self.keys[_HdrSize + offset + len] = byte(i) self.keys[self.lowOffset + offset + len] = byte(i) keyLenSum[len].cur += len + 1 } else { self.longKeys = append(self.longKeys, keyEntry{f.Name, strings.ToLower(f.Name), uint(i)}) } } } // use hashnap type FallbackFieldMap struct { oders []string inner map[string]int backup map[string]int } func NewFallbackFieldMap(n int) *FallbackFieldMap { return &FallbackFieldMap{ oders: make([]string, n, n), inner: make(map[string]int, n*2), backup: make(map[string]int, n*2), } } func (self *FallbackFieldMap) Get(name string) int { if i, ok := self.inner[name]; ok { return i } else { return self.getCaseInsensitive(name) } } func (self *FallbackFieldMap) Set(fields []resolver.FieldMeta) { for i, f := range(fields) { name := f.Name self.oders[i] = name self.inner[name] = i /* add the case-insensitive version, prefer the one with smaller field ID */ key := strings.ToLower(name) if v, ok := self.backup[key]; !ok || i < v { self.backup[key] = i } } } func (self *FallbackFieldMap) getCaseInsensitive(name string) int { if i, ok := self.backup[strings.ToLower(name)]; ok { return i } else { return -1 } }