为什么自旋锁中的第二次自旋可以提高性能?
Why second spin in Spinlock gives performance boost?
这是使用 std::atomic_flag
实现的基本自旋锁。
book 的作者声称 lock()
中的第二个 while 提高了性能。
class Spinlock
{
std::atomic_flag flag{};
public:
void lock() {
while (flag.test_and_set(std::memory_order_acquire)) {
while (flag.test(std::memory_order_acquire)); //Spin here
}
}
void unlock() {
flag.clear(std::memory_order_release);
}
};
The reason we use test()
in an extra inner loop is performance:
test()
doesn't invalidate cache line, whereas test_and_set()
does.
有人可以详细说明一下这句话吗? test还是读操作,需要从内存中读取对吗?
读取内存地址不会清除缓存行。
写作可以。
所以在现代计算机中,有 RAM,CPU“周围”有多层缓存(它们被称为 L1、L2 和 L3 缓存,但重要的是它们是层,CPU 在中间)。在 multi-core 系统中,通常外层是共享的;最内层通常不是,并且特定于给定的 CPU.
清除缓存行意味着通知持有此内存的所有其他缓存“您拥有的数据可能已过时,将其丢弃”。
测试并设置为 true 并原子地写入 returns 旧值。它清除缓存行,因为它写入。
测试不写。如果你有另一个线程与这个线程不同步,它读取这个内存的缓存就不用戳了。
外层循环写入true,替换为false退出。内循环一直等到有假可见,才落入外循环。内部循环不需要清除每个其他 cpu 的原子标志值的缓存状态,但外部必须这样做(因为它可以将 false 更改为 true)。由于旋转可能会持续一段时间,因此避免连续清除缓存似乎是个好主意。
(原子)存储需要独占访问缓存行,但普通原子(释放)存储仍然相对有效,因为涉及存储缓冲区 'caching'; IE。不需要立即访问缓存行。
在自旋锁示例中,存储 (test_and_set
) 是一个原子操作 read-modify-write (RMW),由于其性质,它的速度明显较慢;它在一次操作中读取和写入。
这需要立即访问缓存行,而存储缓冲区中仍未决的任何内容都必须在继续之前被刷新。
此外,必须锁定缓存行以确保读写之间的独占访问。
这很昂贵,但自旋锁需要它来保证独占锁访问。
当多个线程执行同一个 test_and_set
操作时,缓存行会在 CPU 个内核之间不断跳动。
内部循环(使用原子读取)是一种优化,因为多个 threads/cores 可以 read-only 访问同一缓存行(MESI 缓存一致性协议中的 'SHARED' )。
一旦 inner-loop test
操作读取到 false
,outer-loop 中的 test_and_set
就获得了锁,只有在那个时候,才需要 std::memory_order_acquire
。
因此,在内部循环中的 test
操作上使用 'acquire' 是毫无意义的,并且对较弱的体系结构感到悲观,其中 memory_order_acquire
需要额外的 CPU 指令(例如 PowerPC)。
由于内循环都是关于优化的,所以它应该使用 std::memory_order_relaxed
.
void lock() {
while (flag.test_and_set(std::memory_order_acquire)) {
while (flag.test(std::memory_order_relaxed)); //Spin here
}
}
为了进一步优化,可以使用独立的栅栏,以便 CPU 栅栏指令 仅 在线程获得锁定标志后调用。
class Spinlock
{
std::atomic_flag flag{};
public:
void lock() {
while (flag.test_and_set(std::memory_order_relaxed)) {
while (flag.test(std::memory_order_relaxed)); //Spin here
}
std::atomic_thread_fence(std::memory_order_acquire);
}
void unlock() {
flag.clear(std::memory_order_release);
}
};
有一个内部循环是一种有用的优化吗? - 也许..它不会造成伤害,但如果经常到达内循环,就会存在一些争用,自旋锁可能不是正确的工具。
这是使用 std::atomic_flag
实现的基本自旋锁。
book 的作者声称 lock()
中的第二个 while 提高了性能。
class Spinlock
{
std::atomic_flag flag{};
public:
void lock() {
while (flag.test_and_set(std::memory_order_acquire)) {
while (flag.test(std::memory_order_acquire)); //Spin here
}
}
void unlock() {
flag.clear(std::memory_order_release);
}
};
The reason we use
test()
in an extra inner loop is performance:test()
doesn't invalidate cache line, whereastest_and_set()
does.
有人可以详细说明一下这句话吗? test还是读操作,需要从内存中读取对吗?
读取内存地址不会清除缓存行。
写作可以。
所以在现代计算机中,有 RAM,CPU“周围”有多层缓存(它们被称为 L1、L2 和 L3 缓存,但重要的是它们是层,CPU 在中间)。在 multi-core 系统中,通常外层是共享的;最内层通常不是,并且特定于给定的 CPU.
清除缓存行意味着通知持有此内存的所有其他缓存“您拥有的数据可能已过时,将其丢弃”。
测试并设置为 true 并原子地写入 returns 旧值。它清除缓存行,因为它写入。
测试不写。如果你有另一个线程与这个线程不同步,它读取这个内存的缓存就不用戳了。
外层循环写入true,替换为false退出。内循环一直等到有假可见,才落入外循环。内部循环不需要清除每个其他 cpu 的原子标志值的缓存状态,但外部必须这样做(因为它可以将 false 更改为 true)。由于旋转可能会持续一段时间,因此避免连续清除缓存似乎是个好主意。
(原子)存储需要独占访问缓存行,但普通原子(释放)存储仍然相对有效,因为涉及存储缓冲区 'caching'; IE。不需要立即访问缓存行。
在自旋锁示例中,存储 (test_and_set
) 是一个原子操作 read-modify-write (RMW),由于其性质,它的速度明显较慢;它在一次操作中读取和写入。
这需要立即访问缓存行,而存储缓冲区中仍未决的任何内容都必须在继续之前被刷新。
此外,必须锁定缓存行以确保读写之间的独占访问。
这很昂贵,但自旋锁需要它来保证独占锁访问。
当多个线程执行同一个 test_and_set
操作时,缓存行会在 CPU 个内核之间不断跳动。
内部循环(使用原子读取)是一种优化,因为多个 threads/cores 可以 read-only 访问同一缓存行(MESI 缓存一致性协议中的 'SHARED' )。
一旦 inner-loop test
操作读取到 false
,outer-loop 中的 test_and_set
就获得了锁,只有在那个时候,才需要 std::memory_order_acquire
。
因此,在内部循环中的 test
操作上使用 'acquire' 是毫无意义的,并且对较弱的体系结构感到悲观,其中 memory_order_acquire
需要额外的 CPU 指令(例如 PowerPC)。
由于内循环都是关于优化的,所以它应该使用 std::memory_order_relaxed
.
void lock() {
while (flag.test_and_set(std::memory_order_acquire)) {
while (flag.test(std::memory_order_relaxed)); //Spin here
}
}
为了进一步优化,可以使用独立的栅栏,以便 CPU 栅栏指令 仅 在线程获得锁定标志后调用。
class Spinlock
{
std::atomic_flag flag{};
public:
void lock() {
while (flag.test_and_set(std::memory_order_relaxed)) {
while (flag.test(std::memory_order_relaxed)); //Spin here
}
std::atomic_thread_fence(std::memory_order_acquire);
}
void unlock() {
flag.clear(std::memory_order_release);
}
};
有一个内部循环是一种有用的优化吗? - 也许..它不会造成伤害,但如果经常到达内循环,就会存在一些争用,自旋锁可能不是正确的工具。