Skip to content

Commit a103d4a

Browse files
mzientstiepan
authored andcommitted
Optimize spinlock hot path. (#5961)
* Optimize spinlock hot path. Signed-off-by: Michal Zientkiewicz <michalz@nvidia.com>
1 parent 4101873 commit a103d4a

1 file changed

Lines changed: 29 additions & 12 deletions

File tree

include/dali/core/spinlock.h

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2018-2019, NVIDIA CORPORATION. All rights reserved.
1+
// Copyright (c) 2018-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -17,6 +17,9 @@
1717

1818
#include <atomic>
1919
#include <thread>
20+
#ifdef __x86_64
21+
#include <emmintrin.h>
22+
#endif
2023

2124
namespace dali {
2225

@@ -26,21 +29,28 @@ namespace dali {
2629
class busy_spinlock {
2730
public:
2831
void lock() noexcept {
29-
while (flag_.test_and_set(std::memory_order_acquire)) {
30-
// busy-wait
32+
for (;;) {
33+
bool was_locked = flag_.load(std::memory_order_relaxed);
34+
if (!was_locked && !flag_.exchange(true, std::memory_order_acquire))
35+
break;
36+
#ifdef __x86_64
37+
_mm_pause(); // Let other hyperthreads run
38+
#endif
3139
}
3240
}
3341

3442
bool try_lock() noexcept {
35-
return !flag_.test_and_set(std::memory_order_acquire);
43+
// First load the flag - if it's locked, just fail. This helps in high contention scenarios.
44+
bool was_locked = flag_.load(std::memory_order_relaxed);
45+
return !was_locked && !flag_.exchange(true, std::memory_order_acquire);
3646
}
3747

3848
void unlock() noexcept {
39-
flag_.clear(std::memory_order_release);
49+
flag_.store(false, std::memory_order_release);
4050
}
4151

4252
private:
43-
std::atomic_flag flag_ = ATOMIC_FLAG_INIT;
53+
std::atomic<bool> flag_{false};
4454
};
4555

4656
/**
@@ -51,25 +61,32 @@ class yielding_spinlock {
5161
public:
5262
void lock() noexcept {
5363
int spin = yield_after_n_spins;
54-
while (flag_.test_and_set(std::memory_order_acquire)) {
55-
if (spin > 0) {
56-
spin--;
64+
for (;;) {
65+
bool was_locked = flag_.load(std::memory_order_relaxed);
66+
if (!was_locked && !flag_.exchange(true, std::memory_order_acquire))
67+
break;
68+
if (spin-- > 0) {
69+
#ifdef __x86_64
70+
_mm_pause(); // Let other hyperthreads run
71+
#endif
5772
} else {
5873
std::this_thread::yield();
74+
spin = yield_after_n_spins;
5975
}
6076
}
6177
}
6278

6379
bool try_lock() noexcept {
64-
return !flag_.test_and_set(std::memory_order_acquire);
80+
bool was_locked = flag_.load(std::memory_order_relaxed);
81+
return !was_locked && !flag_.exchange(true, std::memory_order_acquire);
6582
}
6683

6784
void unlock() noexcept {
68-
flag_.clear(std::memory_order_release);
85+
flag_.store(false, std::memory_order_release);
6986
}
7087

7188
private:
72-
std::atomic_flag flag_ = ATOMIC_FLAG_INIT;
89+
std::atomic<bool> flag_{false};
7390
};
7491

7592
using spinlock = yielding_spinlock<100>;

0 commit comments

Comments
 (0)