Skip to content

Releases: puzpuzpuz/xsync

v4.5.0

18 Apr 07:05
1d45188

Choose a tag to compare

  • Apply integer hash inlining in Map to more types #195
  • Fix trivial hash flooding in integer hash function in Map #199
  • Use power-of-two capacity to replace modulo with bitwise ops in MPMCQueue #200
  • Avoid false sharing in MPMCQueue #203

Kudos to @llxisdsh, @jeremiah-masters, and @huynhanx03 for making this release happen.

Breaking changes

MPMCQueue's capacity specified in NewMPMCQueue is now implicitly rounded up to the next power of 2. For example, xsync.NewMPMCQueue[string](1000) means that the queue's capacity will be set to 1024.

v4.4.0

24 Jan 21:32
5c4fab0

Choose a tag to compare

  • Micro-optimize Map for integer keys #185
  • Add Map.RangeRelaxed method for faster map iteration #187
  • Add Map.DeleteMatching method for batch entry deletion #186

Read-heavy operations on Map with integer keys are now 24-29% faster due to a more efficient hash function, as well as a number of micro-optimizations.

RangeRelaxed is a much faster (~11x), lock-free alternative to Range. The downside is that the same key may be visited by RangeRelaxed more than once if it is concurrently deleted and re-inserted during the iteration. RangeRelaxed should be preferred over Range in all cases when weaker consistency is acceptable.

m := xsync.NewMap[string, int]()
m.Store("alice", 10)
m.Store("bob", 20)
m.Store("carol", 30)
m.Store("dave", 40)

// Iterate map entries and calculate sum of all values.
sum := 0
m.RangeRelaxed(func(key string, value int) bool {
	sum += value
	return true // continue iteration
})

DeleteMatching deletes all entries for which the delete return value of the input function is true. If the cancel return value is true, the iteration stops immediately. The function returns the number of deleted entries. The call locks a hash table bucket for the duration of evaluating the function for all entries in the bucket and performing deletions. It performs up to 20% faster than Range + Delete, yet if the percentage of the entries to-be-deleted is low, RangeRelaxed + Delete combination should be more efficient.

// Delete entries with value greater than 25.
deleted := m.DeleteMatching(func(key string, value int) (delete, cancel bool) {
	return value > 25, false
})

v4.3.0

12 Jan 19:19
5133b05

Choose a tag to compare

  • Add iterator function Map.All #181
  • Make shrink resize lock-free on target buckets #180

All is similar to Range, but returns an iter.Seq2, so is compatible with Go 1.23+ iterators. All of the same caveats and behavior from Range apply to All.

m := xsync.NewMap[string, int]()
for i := range 100 {
  m.Store(strconv.Itoa(i), i)
}

// Will print all of the map entries
for key, val := range m.All() {
  fmt.Printf("m[%q] = %q\n")
}

Kudos to @llxisdsh and @moskyb for making this release happen.

v4.2.0

14 Sep 16:22
fa8af1d

Choose a tag to compare

  • Cooperative parallel rehashing in Map #178
  • Use runtime.cheaprand instead of fastrand #177

Introduces cooperative rehashing for xsync.Map this means that goroutines that execute write operations, such as Compute or Store, may participate in table rehashing when the hash table grows or shrinks. From now on, table rehashing never spawns additional goroutines.

This behavior is always enabled, so the WithSerialResize function is now marked as deprecated and acts as a no-op.

v4.1.0

03 May 05:21
f6258c7

Choose a tag to compare

  • New data structure: UMPSCQueue #168
  • Speed up LoadAndDelete and Delete in case of non-existing Map key #167
  • Parallel Map resize #170

UMPSCQueue is meant to serve as a replacement for a channel. However, crucially, it has infinite capacity. This is a very bad idea in many cases as it means that it never exhibits backpressure. In other words, if nothing is consuming elements from the queue, it will eventually consume all available memory and crash the process. However, there are also cases where this is desired behavior as it means the queue will dynamically allocate more memory to store temporary bursts, allowing producers to never block while the consumer catches up.

From now on, Map spawns additional goroutines to speed up resizing the hash table. This can be disabled when creating a Map with the new WithSerialResize setting:

m := xsync.NewMap[int, int](xsync.WithSerialResize())
// resize will take place on the current goroutine only
for i := 0; i < 10000; i++ {
	m.Store(i, i)
}

Thanks @PapaCharlie and @llxisdsh for the contributions!

v4.0.0

01 Apr 20:04
5cf46af

Choose a tag to compare

  • Minimal Golang version is now 1.24.
  • All non-generic data structures are now removed. Generic versions should be used instead - they use the old names, but type aliases are present to simplify v3-to-v4 code migration.
  • MapOf's hasher API is gone. The default and only hash function is now based on maphash.Comparable.
  • Map's Compute API now supports no-op (cancel) compute operation.

Thanks @PapaCharlie for making this release happen

Migration notes

  • The old *Of types are kept as type aliases for the renamed data structures to simplify the migration, e.g. MapOf is an alias for Map.
  • NewMapOfPresized function is gone. NewMap combined with WithPresize should be used instead.
  • Map.Compute method now expects valueFn to return a ComputeOp value instead of a boolean flag. That's to support compute operation cancellation, so that the call does nothing.
  • Map.LoadOrTryCompute method is renamed to LoadOrCompute. The old LoadOrCompute method is removed as it was redundant.

v3.5.1

15 Feb 11:37
800e3a0

Choose a tag to compare

  • Clarify docs for LoadOrCompute and LoadOrTryCompute #154

v3.5.0

25 Jan 16:15
6947fae

Choose a tag to compare

  • Add SPSCQueue/SPSCQueueOf #152
  • Add LoadOrTryCompute method to Map/MapOf #153

Thanks @mattjohnsonpint for suggesting the map enhancement.

v3.4.1

18 Jan 20:25
ac72527

Choose a tag to compare

  • Add ToPlainMap/ToPlainMapOf utility functions #151

Thanks @KiddoV for suggesting this enhancement.

v3.4.0

14 Jul 11:13
5f67a12

Choose a tag to compare

  • Add optimistic locking methods to RBMutex #138 and #140
  • Fix Map/MapOf capacity calculation for WithPresize #139

RBMutex now has methods for optimistic locking:

mu := xsync.NewRBMutex()
if locked, t := mu.TryRLock(); locked {
	// critical reader section...
	mu.RUnlock(t)
}
if mu.TryLock() {
	// critical writer section...
	mu.Unlock()
}

Thanks @kkroo for the contribution.