github.com/facebookincubator/go-belt@v0.0.0-20230703220935-39cd348f1a38/pkg/field/searchable_fields.go (about) 1 // Copyright 2022 Meta Platforms, Inc. and affiliates. 2 // 3 // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 // 5 // 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 // 7 // 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 // 9 // 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 // 11 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 13 //go:build go_obs_experimental 14 // +build go_obs_experimental 15 16 package field 17 18 import ( 19 "sync" 20 21 "github.com/go-ng/sort" 22 "github.com/go-ng/xsort" 23 ) 24 25 // NewSearchableFields wraps Fields to add functionality to search 26 // for specific fields. 27 func NewSearchableFields(s Fields) SearchableFields { 28 return SearchableFields{ 29 fields: s, 30 } 31 } 32 33 // SearchableFields is a Fields which allows to search a specific field by key 34 // and deduplicate the collection. 35 type SearchableFields struct { 36 fields Fields 37 lastSortLen uint 38 } 39 40 var _ AbstractFields = (*SearchableFields)(nil) 41 42 // ForEachField implements AbstractFields. 43 func (s *SearchableFields) ForEachField(callback func(f *Field) bool) bool { 44 return s.fields.ForEachField(callback) 45 } 46 47 // Fields just returns Fields as they are currently stored. 48 // 49 // DO NOT MODIFY THE OUTPUT OF THIS FUNCTION UNLESS YOU 50 // KNOW WHAT ARE YOU DOING. IT IS NOT COPIED ONLY DUE 51 // TO PERFORMANCE REASONS. 52 func (s *SearchableFields) Fields() Fields { 53 return s.fields 54 } 55 56 // Copy returns a copy of the SearchFields. 57 func (s *SearchableFields) Copy() SearchableFields { 58 return s.WithPreallocate(0) 59 } 60 61 // WithPreallocate returns a copy of the SearchFields with the specified space preallocated 62 // (could be useful to append). 63 func (s *SearchableFields) WithPreallocate(preallocateLen uint) SearchableFields { 64 return SearchableFields{fields: s.fields.WithPreallocate(preallocateLen), lastSortLen: s.lastSortLen} 65 } 66 67 // UnsafeOverwriteFields overwrites the collection of fields, but 68 // does not update the index. It could be used ONLY AND ONLY 69 // if the collection of fields was appended and if methods Get 70 // and Deduplicate were not used, yet. 71 // 72 // DO NOT USE THIS FUNCTION UNLESS YOU KNOW WHAT ARE YOU DOING. 73 // THIS FUNCTION IS AN IMPLEMENTATION LEAK AND WAS ADDED ONLY 74 // DUE TO STRONG _PERFORMANCE_ REQUIREMENTS OF PACKAGE "obs". 75 func (s *SearchableFields) UnsafeOverwriteFields(fields Fields) { 76 if len(fields) <= len(s.fields) { 77 panic("you definitely use this function wrong, stop using it!") 78 } 79 s.fields = fields 80 } 81 82 // Add just adds Field-s to the collection of Fields. 83 // 84 // The Fields are not sorted at this stage. They will 85 // be sorted only on first Get call. 86 func (s *SearchableFields) Add(fields ...Field) { 87 s.fields = append(s.fields, fields...) 88 } 89 90 // Get returns a Field with the specific Key from the collection of Fields. 91 func (s *SearchableFields) Get(key Key) *Field { 92 // see https://github.com/xaionaro-go/benchmarks/blob/master/search/README.md 93 if len(s.fields)-int(s.lastSortLen) > 256 { 94 s.sort() 95 } 96 97 if s.lastSortLen > 0 { 98 // binary search 99 idx := sort.Search(int(s.lastSortLen), func(i int) bool { 100 return s.fields[i].Key >= key 101 }) 102 if idx >= 0 && idx < int(s.lastSortLen) { 103 return &s.fields[idx] 104 } 105 } 106 107 // linear search 108 for idx := len(s.fields) - 1; idx >= int(s.lastSortLen); idx-- { 109 if s.fields[idx].Key == key { 110 return &s.fields[idx] 111 } 112 } 113 114 return nil 115 } 116 117 var ( 118 // EnableSortBufPool improves performance of Get and Deduplicate 119 // methods of SearchFields, but consumes more memory. 120 // 121 // If you have a high performance application then you probably 122 // need to enable this feature (it is unlikely to consume 123 // a lot of RAM) 124 EnableSortBufPool = false 125 126 sortBufPool = sync.Pool{ 127 New: func() any { 128 return &Fields{} 129 }, 130 } 131 ) 132 133 func (s *SearchableFields) sort() { 134 unsortedCount := len(s.fields) - int(s.lastSortLen) 135 if unsortedCount == 0 { 136 return 137 } 138 if unsortedCount < 0 { 139 panic("should not happen, ever") 140 } 141 142 // See: https://raw.githubusercontent.com/go-ng/xsort/main/BENCHMARKS.txt 143 if EnableSortBufPool { 144 buf := sortBufPool.Get().(*Fields) 145 if cap(*buf) < unsortedCount { 146 *buf = make(Fields, unsortedCount) 147 } else { 148 *buf = (*buf)[:unsortedCount] 149 } 150 xsort.AppendedWithBuf(s.fields, *buf) 151 for idx := range *buf { 152 (*buf)[idx] = Field{} 153 } 154 sortBufPool.Put(buf) 155 } else { 156 xsort.Appended(s.fields, uint(unsortedCount)) 157 } 158 159 s.lastSortLen = uint(len(s.fields)) 160 } 161 162 // Len returns the amount of Field-s in the collection. 163 func (s *SearchableFields) Len() int { 164 return len(s.fields) 165 } 166 167 // Cap returns the capacity of the storage of the Fields. 168 func (s *SearchableFields) Cap() int { 169 return cap(s.fields) 170 } 171 172 // Build sorts the fields and removes the duplicates earlier fields 173 // with the same key values as later fields (preserving the order) 174 // 175 // It is not recommended to call this function in high performance pieces of code, 176 // unless it was called shortly before that. 177 func (s *SearchableFields) Build() { 178 if s.Len() < 2 { 179 return 180 } 181 s.sort() 182 183 outIdx := s.Len() - 2 184 for idx := s.Len() - 2; idx >= 0; idx-- { 185 prevField := &s.fields[outIdx+1] 186 curField := &s.fields[idx] 187 if curField.Key == prevField.Key { 188 continue 189 } 190 if outIdx != idx { 191 s.fields[outIdx] = *curField 192 } 193 outIdx-- 194 } 195 s.fields = s.fields[outIdx+1:] 196 }