Skip to content

Commit

Permalink
Add ToPlainMap/ToPlainMapOf utility functions
Browse files Browse the repository at this point in the history
  • Loading branch information
puzpuzpuz committed Jan 18, 2025
1 parent 5f67a12 commit 08bec51
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 1 deletion.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,14 @@ m.Store(Point{42, 42}, 42)
v, ok := m.Load(point{42, 42})
```

Both maps use the built-in Golang's hash function which has DDOS protection. This means that each map instance gets its own seed number and the hash function uses that seed for hash code calculation. However, for smaller keys this hash function has some overhead. So, if you don't need DDOS protection, you may provide a custom hash function when creating a `MapOf`. For instance, Murmur3 finalizer does a decent job when it comes to integers:
Apart from `Range` method available for map iteration, there are also `ToPlainMap`/`ToPlainMapOf` utility functions to convert a `Map`/`MapOf` to a built-in Go's `map`:
```go
m := xsync.NewMapOf[int, int]()
m.Store(42, 42)
pm := xsync.ToPlainMapOf(m)
```

Both `Map` and `MapOf` use the built-in Golang's hash function which has DDOS protection. This means that each map instance gets its own seed number and the hash function uses that seed for hash code calculation. However, for smaller keys this hash function has some overhead. So, if you don't need DDOS protection, you may provide a custom hash function when creating a `MapOf`. For instance, Murmur3 finalizer does a decent job when it comes to integers:

```go
m := NewMapOfWithHasher[int, int](func(i int, _ uint64) uint64 {
Expand Down
15 changes: 15 additions & 0 deletions map.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,21 @@ func newMapTable(minTableLen int) *mapTable {
return t
}

// ToPlainMap returns a native map with a copy of xsync Map's
// contents. The copied xsync Map should not be modified while
// this call is made. If the copied Map is modified, the copying
// behavior is the same as in the Range method.
func ToPlainMap(m *Map) map[string]interface{} {
pm := make(map[string]interface{})
if m != nil {
m.Range(func(key string, value interface{}) bool {
pm[key] = value
return true
})
}
return pm
}

// Load returns the value stored in the map for a key, or nil if no
// value is present.
// The ok result indicates whether value was found in the map.
Expand Down
24 changes: 24 additions & 0 deletions map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1265,6 +1265,30 @@ func TestMapStats(t *testing.T) {
}
}

func TestToPlainMap_NilPointer(t *testing.T) {
pm := ToPlainMap(nil)
if len(pm) != 0 {
t.Fatalf("got unexpected size of nil map copy: %d", len(pm))
}
}

func TestToPlainMap(t *testing.T) {
const numEntries = 1000
m := NewMap()
for i := 0; i < numEntries; i++ {
m.Store(strconv.Itoa(i), i)
}
pm := ToPlainMap(m)
if len(pm) != numEntries {
t.Fatalf("got unexpected size of nil map copy: %d", len(pm))
}
for i := 0; i < numEntries; i++ {
if v := pm[strconv.Itoa(i)]; v != i {
t.Fatalf("unexpected value for key %d: %d", i, v)
}
}
}

func BenchmarkMap_NoWarmUp(b *testing.B) {
for _, bc := range benchmarkCases {
if bc.readPercentage == 100 {
Expand Down
15 changes: 15 additions & 0 deletions mapof.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,21 @@ func newMapOfTable[K comparable, V any](minTableLen int) *mapOfTable[K, V] {
return t
}

// ToPlainMapOf returns a native map with a copy of xsync Map's
// contents. The copied xsync Map should not be modified while
// this call is made. If the copied Map is modified, the copying
// behavior is the same as in the Range method.
func ToPlainMapOf[K comparable, V any](m *MapOf[K, V]) map[K]V {
pm := make(map[K]V)
if m != nil {
m.Range(func(key K, value V) bool {
pm[key] = value
return true
})
}
return pm
}

// Load returns the value stored in the map for a key, or zero value
// of type V if no value is present.
// The ok result indicates whether value was found in the map.
Expand Down
24 changes: 24 additions & 0 deletions mapof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1205,6 +1205,30 @@ func TestMapOfStats(t *testing.T) {
}
}

func TestToPlainMapOf_NilPointer(t *testing.T) {
pm := ToPlainMapOf[int, int](nil)
if len(pm) != 0 {
t.Fatalf("got unexpected size of nil map copy: %d", len(pm))
}
}

func TestToPlainMapOf(t *testing.T) {
const numEntries = 1000
m := NewMapOf[int, int]()
for i := 0; i < numEntries; i++ {
m.Store(i, i)
}
pm := ToPlainMapOf[int, int](m)
if len(pm) != numEntries {
t.Fatalf("got unexpected size of nil map copy: %d", len(pm))
}
for i := 0; i < numEntries; i++ {
if v := pm[i]; v != i {
t.Fatalf("unexpected value for key %d: %d", i, v)
}
}
}

func BenchmarkMapOf_NoWarmUp(b *testing.B) {
for _, bc := range benchmarkCases {
if bc.readPercentage == 100 {
Expand Down

0 comments on commit 08bec51

Please sign in to comment.