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  }