github.com/moontrade/nogc@v0.1.7/pointer_set.go (about)

     1  package nogc
     2  
     3  import (
     4  	"github.com/moontrade/nogc/hash"
     5  	"unsafe"
     6  )
     7  
     8  // PointerSet is a hashset that uses the robinhood algorithm. This
     9  // implementation is not concurrent safe.
    10  type PointerSet struct {
    11  	// items are the slots of the hashmap for items.
    12  	items uintptr
    13  	end   uintptr
    14  	size  uintptr
    15  
    16  	// Number of keys in the PointerSet.
    17  	count uintptr
    18  	// When any item's distance gets too large, Grow the PointerSet.
    19  	// Defaults to 10.
    20  	maxDistance int32
    21  	growBy      int32
    22  	// Number of hash slots to Grow by
    23  	growthFactor float32
    24  }
    25  
    26  // Item represents an entry in the PointerSet.
    27  type pointerSetItem struct {
    28  	key      uintptr
    29  	distance int32 // How far item is from its best position.
    30  }
    31  
    32  // pointerSetHash uses the fnv hashing algorithm for 32bit integers.
    33  // fnv was chosen for its consistent low collision rate even with tight monotonic numbers (WASM)
    34  // and for its performance. Other potential candidates are wyhash, metro, and adler32. Each of these
    35  // have optimized 32bit version in hash.go in this package.
    36  //go:inline
    37  var pointerSetHash = hash.FNV32a
    38  
    39  //func pointerSetHash(v uint32) uint32 {
    40  //	return fnv32(v)
    41  //}
    42  
    43  // NewPointerSet returns a new robinhood hashmap.
    44  //goland:noinspection ALL
    45  func NewPointerSet(size uintptr) PointerSet {
    46  	items := AllocZeroed(unsafe.Sizeof(pointerSetItem{}) * size)
    47  	Zero(unsafe.Pointer(items), unsafe.Sizeof(pointerSetItem{})*uintptr(size))
    48  	return PointerSet{
    49  		items:        uintptr(items),
    50  		size:         uintptr(size),
    51  		end:          uintptr(items) + (uintptr(size) * unsafe.Sizeof(pointerSetItem{})),
    52  		maxDistance:  10,
    53  		growBy:       64,
    54  		growthFactor: 2.0,
    55  	}
    56  }
    57  
    58  //goland:noinspection GoVetUnsafePointer
    59  func (ps *PointerSet) Close() error {
    60  	if ps == nil {
    61  		return nil
    62  	}
    63  	if ps.items == 0 {
    64  		return nil
    65  	}
    66  	Free(Pointer(ps.items))
    67  	ps.items = 0
    68  	return nil
    69  }
    70  
    71  // Reset clears PointerSet, where already allocated memory will be reused.
    72  //goland:noinspection ALL
    73  func (ps *PointerSet) Reset() {
    74  	Zero(unsafe.Pointer(ps.items), unsafe.Sizeof(pointerSetItem{})*ps.size)
    75  	ps.count = 0
    76  }
    77  
    78  //goland:noinspection GoVetUnsafePointer
    79  func (ps *PointerSet) isCollision(key uintptr) bool {
    80  	return *(*uintptr)(unsafe.Pointer(
    81  		ps.items + (uintptr(pointerSetHash(uint32(key))%uint32(ps.size)) * unsafe.Sizeof(pointerSetItem{})))) != 0
    82  }
    83  
    84  // Has returns whether the key exists in the Add.
    85  //goland:noinspection ALL
    86  func (ps *PointerSet) Has(key uintptr) bool {
    87  	var (
    88  		idx      = ps.items + (uintptr(pointerSetHash(uint32(key))%uint32(ps.size)) * unsafe.Sizeof(pointerSetItem{}))
    89  		idxStart = idx
    90  	)
    91  	for {
    92  		entry := *(*uintptr)(unsafe.Pointer(idx))
    93  		if entry == 0 {
    94  			return false
    95  		}
    96  		if entry == key {
    97  			return true
    98  		}
    99  		idx += unsafe.Sizeof(pointerSetItem{})
   100  		if idx >= ps.end {
   101  			idx = ps.items
   102  		}
   103  		// Went all the way around?
   104  		if idx == idxStart {
   105  			return false
   106  		}
   107  	}
   108  }
   109  
   110  func (ps *PointerSet) Set(key uintptr) (bool, bool) {
   111  	return ps.Add(key, 0)
   112  }
   113  
   114  // Add inserts or updates a key into the PointerSet. The returned
   115  // wasNew will be true if the mutation was on a newly seen, inserted
   116  // key, and wasNew will be false if the mutation was an update to an
   117  // existing key.
   118  //goland:noinspection GoVetUnsafePointer
   119  func (ps *PointerSet) Add(key uintptr, depth int) (bool, bool) {
   120  	var (
   121  		idx      = ps.items + (uintptr(pointerSetHash(uint32(key))%uint32(ps.size)) * unsafe.Sizeof(pointerSetItem{}))
   122  		idxStart = idx
   123  		incoming = pointerSetItem{key, 0}
   124  	)
   125  	for {
   126  		entry := (*pointerSetItem)(unsafe.Pointer(idx))
   127  		if entry.key == 0 {
   128  			*(*pointerSetItem)(unsafe.Pointer(idx)) = incoming
   129  			ps.count++
   130  			return true, true
   131  		}
   132  
   133  		if entry.key == incoming.key {
   134  			entry.distance = incoming.distance
   135  			return false, true
   136  		}
   137  
   138  		// Swap if the incoming item is further from its best idx.
   139  		if entry.distance < incoming.distance {
   140  			incoming, *(*pointerSetItem)(unsafe.Pointer(idx)) = *(*pointerSetItem)(unsafe.Pointer(idx)), incoming
   141  		}
   142  
   143  		// One step further away from best idx.
   144  		incoming.distance++
   145  
   146  		idx += unsafe.Sizeof(pointerSetItem{})
   147  		if idx >= ps.end {
   148  			idx = ps.items
   149  		}
   150  
   151  		// Grow if distances become big or we went all the way around.
   152  		if incoming.distance > ps.maxDistance || idx == idxStart {
   153  			if depth > 5 {
   154  				return false, false
   155  			}
   156  			if !ps.Grow() {
   157  				return false, false
   158  			}
   159  			return ps.Add(incoming.key, depth+1)
   160  		}
   161  	}
   162  }
   163  
   164  // Delete removes a key from the PointerSet.
   165  //goland:noinspection GoVetUnsafePointer
   166  func (ps *PointerSet) Delete(key uintptr) (uintptr, bool) {
   167  	if key == 0 {
   168  		return 0, false
   169  	}
   170  
   171  	var (
   172  		idx      = ps.items + (uintptr(pointerSetHash(uint32(key))%uint32(ps.size)) * unsafe.Sizeof(pointerSetItem{}))
   173  		idxStart = idx
   174  	)
   175  	for {
   176  		entry := (*pointerSetItem)(unsafe.Pointer(idx))
   177  		if entry.key == 0 {
   178  			return 0, false
   179  		}
   180  		if entry.key == key {
   181  			break // Found the item.
   182  		}
   183  		idx += unsafe.Sizeof(pointerSetItem{})
   184  		if idx >= ps.end {
   185  			idx = ps.items
   186  		}
   187  		if idx == idxStart {
   188  			return 0, false
   189  		}
   190  	}
   191  	// Left-shift succeeding items in the linear chain.
   192  	for {
   193  		next := idx + unsafe.Sizeof(pointerSetItem{})
   194  		if next >= ps.end {
   195  			next = ps.items
   196  		}
   197  		// Went all the way around?
   198  		if next == idx {
   199  			break
   200  		}
   201  		f := (*pointerSetItem)(unsafe.Pointer(next))
   202  		if f.key == 0 || f.distance <= 0 {
   203  			break
   204  		}
   205  		f.distance--
   206  		*(*pointerSetItem)(unsafe.Pointer(idx)) = *f
   207  		idx = next
   208  	}
   209  	// Clear entry
   210  	*(*pointerSetItem)(unsafe.Pointer(idx)) = pointerSetItem{}
   211  	ps.count--
   212  	return idxStart, true
   213  }
   214  
   215  //goland:noinspection GoVetUnsafePointer
   216  func (ps *PointerSet) Grow() bool {
   217  	// Calculate new size
   218  	// ps.size + 128
   219  	if ps.growthFactor <= 1.0 {
   220  		ps.growthFactor = 2.0
   221  	}
   222  	//newSize := ps.size + 32 // uintptr(float32(ps.size) * ps.growthFactor)
   223  	newSize := uintptr(float32(ps.size) * ps.growthFactor)
   224  
   225  	//if gc_TRACE {
   226  	//	println("PointerSet.Grow", "newSize", uint(newSize), "oldSize", uint(ps.size))
   227  	//}
   228  
   229  	// Allocate new items table
   230  	items := uintptr(AllocZeroed(newSize * unsafe.Sizeof(pointerSetItem{})))
   231  	// Calculate end
   232  	itemsEnd := items + (newSize * unsafe.Sizeof(pointerSetItem{}))
   233  	// Init next structure
   234  	next := PointerSet{
   235  		items:        items,
   236  		size:         newSize,
   237  		end:          itemsEnd,
   238  		count:        0,
   239  		growthFactor: ps.growthFactor,
   240  		maxDistance:  ps.maxDistance,
   241  	}
   242  
   243  	// Add all entries from old to next
   244  	var success bool
   245  	for i := ps.items; i < ps.end; i += unsafe.Sizeof(pointerSetItem{}) {
   246  		if _, success = next.Add(*(*uintptr)(unsafe.Pointer(i)), 0); !success {
   247  			return false
   248  		}
   249  	}
   250  
   251  	// Free old items
   252  	Free(Pointer(ps.items))
   253  	ps.items = 0
   254  
   255  	// Update to next
   256  	*ps = next
   257  	return true
   258  }