在一个代码块中多次互斥锁定一个变量,还是只锁定整个代码块,效率更高?
Is it more efficient to mutex lock a variable multiple times in a code-block, or just lock the whole code-block?
如果我们有如下代码示例:
if (letsDoThis) //bool
{
sharedVar++; // This is shared across other threads.
:
sharedVar++;
:
sharedVar++;
:
sharedVar++;
:
sharedVar++;
}
其中每个 :
就像 ~10 行代码(没有缓慢的函数调用或任何东西)。编写代码以便在整个 "if" 块(好吧,if 块的内容)周围锁定互斥量,或者锁定和解锁每个单独的 sharedVar 使用会更快吗?
如果是 "it depends" 类型的问题,那么哪种方法(也许作为经验法则)更好?
最后,您如何确定两者中哪一个在您的系统上运行得更快? - 跟踪工具真的会以有意义的方式向您显示有用的数据吗?
如果从程序逻辑的角度来看,允许后续增量之间的代码运行并发,那么最好的办法是使用原子计数器。如果在计数器增量之前所做的任何事情需要对其他线程立即可见,则使用 release
语义(原子增量加屏障)进行增量,否则 - 使用 relaxed
(仅原子增量)。
如果出于某种原因原子增量不是一个选项,那么请在一个简单的测试应用程序中对您的互斥体想法进行基准测试 运行在一个循环中同时使用主题代码。 google benchmark
是一个不错的小库,可以节省您的输入时间。如果您只有 Posix 个原始线程,您可以借用 my old code.
这取决于(在所有其他影响性能的因素中)
- 你有多少个线程和核心(两者的最小值是相关的)
- 线程在该部分代码(以及锁定互斥锁的其他部分)中花费了多少时间
从单个线程的角度来看,多个锁定和解锁意味着额外的工作,并且 - 特别是在拥塞情况下 - 而不是 expensive/timeconsuming 并且它可能会导致内核之间的缓存乒乓更多。所以会降低单线程的性能。但是,如果多次锁定和解锁减少了线程在每次迭代中持有互斥量的总时间,那么这意味着您的程序中可以有更多的并行性,并且整体性能随着线程数和 CPU 核心。
这两种影响都可以忽略不计,两种影响都可能很重要,如果代码不在您程序的热路径中,那么它一开始可能并不重要。我认为,您唯一可以确定什么对您的情况更好,就是 运行 两种变体并测量整体吞吐量。如果您看不出有什么区别,为了简单起见,我可能会使用单锁和解锁(即使用 std::lock_guard
)。
然而,您应该问的一般问题是,如果您真的需要在线程之间进行如此多的同步并且您是否必须同步多次:如果其他线程看不到共享状态的中间值是可以的(如果他们不互相等待,你无论如何也不能保证),那你为什么不把共享状态上的所有操作结合起来,并在块的末尾使它成为一个单一的操作?
当然,如果您的共享状态确实只是一个整数,那么您应该只使用原子并完全摆脱互斥体。
如果我们有如下代码示例:
if (letsDoThis) //bool
{
sharedVar++; // This is shared across other threads.
:
sharedVar++;
:
sharedVar++;
:
sharedVar++;
:
sharedVar++;
}
其中每个 :
就像 ~10 行代码(没有缓慢的函数调用或任何东西)。编写代码以便在整个 "if" 块(好吧,if 块的内容)周围锁定互斥量,或者锁定和解锁每个单独的 sharedVar 使用会更快吗?
如果是 "it depends" 类型的问题,那么哪种方法(也许作为经验法则)更好?
最后,您如何确定两者中哪一个在您的系统上运行得更快? - 跟踪工具真的会以有意义的方式向您显示有用的数据吗?
如果从程序逻辑的角度来看,允许后续增量之间的代码运行并发,那么最好的办法是使用原子计数器。如果在计数器增量之前所做的任何事情需要对其他线程立即可见,则使用 release
语义(原子增量加屏障)进行增量,否则 - 使用 relaxed
(仅原子增量)。
如果出于某种原因原子增量不是一个选项,那么请在一个简单的测试应用程序中对您的互斥体想法进行基准测试 运行在一个循环中同时使用主题代码。 google benchmark
是一个不错的小库,可以节省您的输入时间。如果您只有 Posix 个原始线程,您可以借用 my old code.
这取决于(在所有其他影响性能的因素中)
- 你有多少个线程和核心(两者的最小值是相关的)
- 线程在该部分代码(以及锁定互斥锁的其他部分)中花费了多少时间
从单个线程的角度来看,多个锁定和解锁意味着额外的工作,并且 - 特别是在拥塞情况下 - 而不是 expensive/timeconsuming 并且它可能会导致内核之间的缓存乒乓更多。所以会降低单线程的性能。但是,如果多次锁定和解锁减少了线程在每次迭代中持有互斥量的总时间,那么这意味着您的程序中可以有更多的并行性,并且整体性能随着线程数和 CPU 核心。
这两种影响都可以忽略不计,两种影响都可能很重要,如果代码不在您程序的热路径中,那么它一开始可能并不重要。我认为,您唯一可以确定什么对您的情况更好,就是 运行 两种变体并测量整体吞吐量。如果您看不出有什么区别,为了简单起见,我可能会使用单锁和解锁(即使用 std::lock_guard
)。
然而,您应该问的一般问题是,如果您真的需要在线程之间进行如此多的同步并且您是否必须同步多次:如果其他线程看不到共享状态的中间值是可以的(如果他们不互相等待,你无论如何也不能保证),那你为什么不把共享状态上的所有操作结合起来,并在块的末尾使它成为一个单一的操作?
当然,如果您的共享状态确实只是一个整数,那么您应该只使用原子并完全摆脱互斥体。