-
-
Notifications
You must be signed in to change notification settings - Fork 76
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Try seize
as a replacement of crossbeam-epoch
?
#385
Comments
Unlike
|
I am doing some experiments in a separate repository. |
Memo: Ideally, I want a concurrent memory reclamation library to provide the followings:
|
Hi! I happened to come across this issue and can share some details about seize. One of the biggest benefits of seize over crossbeam-epoch is predictable latency. Epoch based reclamation has the issue that you have to check whether it is safe to free objects every once in a while when pinning/unpinning a threads, and this check is very expensive as you have to access shared memory from other threads. This means you run into a fundamental tradeoff between latency and memory efficiency. Seize on the other hand only performs this check once when retiring a batch (by pushing the objects to other threads), and uses reference counting to free memory. This is an important consideration for a cache as you want an optimal balance between performance and memory usage.
This is something I've been thinking about. The problem is that seize requires at least as many objects as the number of active threads to be able to retire a batch, as each object acts as a node in a local thread linked list. Skipping the thread-local buffer would require allocating a large chunk of memory to perform the retirement process, which may be confusing. What you can do is try to reclaim by calling |
I also recently released papaya, a hash table built on seize. I'd be interested in porting moka to use papaya and see the results, I'd expect significant performance improvements compared to the internal cht, especially for readers. Is that something you'd be open to? |
Thanks. Yes, I am open to it. I was not aware of papaya, but at first glance, it sounds like a good idea to use it in moka! |
I briefly checked the
In some use cases, it is very important to customize the collector and/or to be able to call
|
I am adding support for the |
CC: @ibraheemdev I started experimenting with So far, so good.
Some thoughts:
I need to think about how to transition the user to a
Perhaps do 2. as it gives more flexibility to the users? |
Exciting!
Yeah, this is somewhat expected in microbenchmarks. However, it can be useful in some applications where writes are rare and latency is more important than throughput. Ideally this would be exposed as a configuration option.
We can do this dynamically based on the number of live entries in a table when resizing. Is there a heuristic that you use currently to determine when to shrink?
Ah yeah using a borrowed key here would be nice. I opened an issue here: ibraheemdev/papaya#53.
The feature approach sounds good. Ideally papaya doesn't have any downsides over the current implementation so there shouldn't be any burden on the user to decide the internal datastructure. Another thought I had is that it might be possible for moka to set I also opened an issue about force flushing a guard (ibraheemdev/seize#34), because this seems like it might be important to some users. |
Thanks for the feedback and opening the issues!
papaya expands the capacity when the number of live entries exceeds half of the capacity, and then it doubles the capacity. So the next allocation will start at 1/4 (25%) full. How about shrinking the capacity when the number of live entries is less than 1/8 (12.5%) or 1/16 (6.25%) of the capacity? Also, I think it should respect the initial capacity when shrinking the capacity; the table should not be shrunk below the initial capacity. So the new heuristic would be: let next_capacity = match cfg!(papaya_stress) {
true => ...,
// Double the table capacity if we are at least 50% (1/2) full.
false if self.len() >= (table.len() >> 1) => table.len() << 1,
// Halve the table capacity if we are at most 12.5% (1/8) full,
// but don't halve below the minimum capacity.
false if self.len() <= (table.len() >> 3) => table.min_cap().max(table.len() >> 1),
// Otherwise, keep the table capacity the same.
false => table.len(),
};
|
Thanks for the suggestion. I implemented enough stuff to moka sync cache to run some micro benchmarks. I compared the elapse time to process a dataset (cache trace) by the original moka with the cht and with papaya. I set The results are below. moka with papaya clearly outperforms the one with the original cht. Great job!
|
Shrinking at 1/8 seems reasonable. My only concern was that in a workload where you continually insert and clear the map, shrinking would lead to unnecessary extra growth. I suppose that respecting the initial capacity is a solution for that. |
Try
seize
crate as a replacement ofcrossbeam-epoch
crate.moka
cache usescrossbeam-epoch
in its lock-free concurrent hash table calledcht
.crossbeam-epoch
is an epoch-based garbage collector and helps to implement high-performance concurrent data structures likecht
. However, we have been struggling for years with the spec ofcrossbeam-epoch
, which does not guarantee to run the destructor of the pointed objects.https://docs.rs/crossbeam-epoch/0.9.18/crossbeam_epoch/struct.Guard.html#method.defer_destroy
Many of
moka
cache users expect the cached entries to be dropped as soon as they are evicted from the cache, of course. However, thiscrossbeam-epoch
spec causes these evicted entries to be dropped with some delays or never dropped.We have added mitigations for it (notes) and they mostly work except performance degradation. However, there are still some corner cases which seems difficult to solve/mitigate. It might be the time to try alternatives like
seize
.seize
is based on Hyaline reclamation scheme and used in a concurrent hash table calledflurry
.The text was updated successfully, but these errors were encountered: