github.com/moontrade/nogc@v0.1.7/collections/rhmap/map.go (about)

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