Skip to content

Commit 185dd43

Browse files
authored
Improve lock-free bucket: use relaxed memory order, set to min/max in CAS (#9379)
1 parent 2148020 commit 185dd43

File tree

1 file changed

+41
-25
lines changed

1 file changed

+41
-25
lines changed

ydb/library/lockfree_bucket/lockfree_bucket.h

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
#include <util/datetime/base.h>
77

88
template<class TTimer>
9-
class TLockFreeBucket {
9+
class alignas(128) TLockFreeBucket { // align to cache line size
1010
public:
1111
TLockFreeBucket(std::atomic<i64>& maxTokens, std::atomic<i64>& minTokens, std::atomic<ui64>& inflowPerSecond)
1212
: MaxTokens(maxTokens)
@@ -19,36 +19,47 @@ class TLockFreeBucket {
1919
}
2020

2121
bool IsEmpty() {
22-
FillBucket();
23-
return Tokens.load() <= 0;
22+
FillAndTake(0);
23+
return Tokens.load(std::memory_order_relaxed) <= 0;
2424
}
2525

26-
void FillAndTake(i64 tokens) {
27-
FillBucket();
28-
TakeTokens(tokens);
29-
}
26+
void FillAndTake(ui64 tokens) {
27+
TTime prev = LastUpdate.load(std::memory_order_acquire);
28+
TTime now = TTimer::Now();
29+
i64 duration = TTimer::Duration(prev, now);
3030

31-
private:
32-
void FillBucket() {
33-
TTime prev;
34-
TTime now;
35-
for (prev = LastUpdate.load(), now = TTimer::Now(); !LastUpdate.compare_exchange_strong(prev, now); ) {}
36-
37-
ui64 rawInflow = InflowPerSecond.load() * TTimer::Duration(prev, now);
38-
if (rawInflow >= TTimer::Resolution) {
39-
Tokens.fetch_add(rawInflow / TTimer::Resolution);
40-
for (i64 tokens = Tokens.load(), maxTokens = MaxTokens.load(); tokens > maxTokens; ) {
41-
if (Tokens.compare_exchange_strong(tokens, maxTokens)) {
42-
break;
43-
}
31+
while (true) {
32+
if (prev >= now) {
33+
duration = 0;
34+
break;
35+
}
36+
37+
if (LastUpdate.compare_exchange_weak(prev, now,
38+
std::memory_order_release,
39+
std::memory_order_acquire)) {
40+
break;
4441
}
4542
}
46-
}
4743

48-
void TakeTokens(i64 tokens) {
49-
Tokens.fetch_sub(tokens);
50-
for (i64 tokens = Tokens.load(), minTokens = MinTokens.load(); tokens < minTokens; ) {
51-
if (Tokens.compare_exchange_strong(tokens, minTokens)) {
44+
i64 currentTokens = Tokens.load(std::memory_order_acquire);
45+
46+
ui64 rawInflow = InflowPerSecond.load(std::memory_order_relaxed) * duration;
47+
i64 minTokens = MinTokens.load(std::memory_order_relaxed);
48+
i64 maxTokens = MaxTokens.load(std::memory_order_relaxed);
49+
Y_DEBUG_ABORT_UNLESS(minTokens <= maxTokens);
50+
51+
while (true) {
52+
i64 newTokens = currentTokens + rawInflow / TTimer::Resolution;
53+
newTokens = std::min(newTokens, maxTokens);
54+
newTokens = newTokens - tokens;
55+
newTokens = std::max(newTokens, minTokens);
56+
57+
if (newTokens == currentTokens) {
58+
break;
59+
}
60+
61+
if (Tokens.compare_exchange_weak(currentTokens, newTokens, std::memory_order_release,
62+
std::memory_order_acquire)) {
5263
break;
5364
}
5465
}
@@ -63,4 +74,9 @@ class TLockFreeBucket {
6374

6475
std::atomic<i64> Tokens;
6576
std::atomic<TTime> LastUpdate;
77+
78+
constexpr static ui32 CacheLineFillerSize = 128 - sizeof(std::atomic<i64>&) * 2 - sizeof(std::atomic<ui64>&)
79+
- sizeof(std::atomic<i64>) - sizeof(std::atomic<TTime>);
80+
81+
std::array<char, CacheLineFillerSize> CacheLineFiller;
6682
};

0 commit comments

Comments
 (0)