【问题标题】:How to measure the time a mutex waits before being acquired?如何测量互斥锁在被获取之前等待的时间?
【发布时间】:2015-01-09 00:36:57
【问题描述】:

我不确定这个问题是否容易理解,所以我将从我想做的(错误的)代码开始。

...
{
    Time start = getCurrentTime();
    scoped_lock<MutexType> lock(mutex);
    Time lockWait = getCurrentTime() - start;

    // Protected section
    ...
}

此代码不起作用,因为无法保证线程不会在开始时间保存指令和互斥锁之间的某个位置被抢占,在这种情况下,计算的 lockWait 将是错误的(高估)。

我想不出任何解决方案来解决这个问题。我觉得我需要在多线程低级机制中动手,但我不知道如何。有什么解决办法或者指点吗?

显然,我对等待时间很感兴趣,但我也想知道锁是否有争议,我不确定是否只能通过时间测量获得。如果没有,我怎么能得到这方面的信息?

PS。我看到有一些工具可以提供一些测量,例如 mutrace 甚至是 Walgrind,但不幸的是,我的测量需要集成到应用程序源代码中。

【问题讨论】:

  • 这不是一个答案,但很可能在获取锁和计时器停止之间代码不会被抢占。当您未能获得锁时,您的线程通常会进入睡眠状态(如果没有,您将在单核系统上遇到麻烦)。这意味着当您获得锁时,您要么在第一次尝试时就获得了它,要么您的线程刚刚从睡眠中恢复并且在到达 getCurrentTime() 之前不太可能被中断。两者之间没有那么多指令。有什么特别的原因需要您一直需要确切的值吗?你可以继承互斥体。
  • 您有英特尔 VTune 的许可证吗?

标签: c++ multithreading boost synchronization


【解决方案1】:

简短回答:所有时间都是估计,因此不可能以您想要的方式精确测量。

长答案:如果您要做的是测量获取无竞争锁的速度,那么执行此操作的准确方法是仅在模拟器中计算指令周期。然而,第二好的方法是运行测试数千次(以使抢占的影响最小化)然后平均。 (真的,抢占无关紧要,因为所有用户模式的代码都受制于它。您无法阻止用户模式的抢占,并且在大量样本中时序效应将趋于零)这就是几乎所有的计时工具都可以。您可以通过在负载较轻的多 CPU 系统上运行来最小化抢占的可能性(从而提高估计值)。

如果您对实际的程序指标感兴趣(例如,在锁定可能被争用的情况下),那么同样的建议也适用。

【讨论】:

  • 好的。但是,如果只能统计计时,我无法从平均等待时间得到锁被争夺的次数。那我怎么能得到呢? [我对问题进行了编辑,以反映我不仅对等待时间感兴趣,而且对有争议/无争议状态感兴趣。]
  • 如果你使用boost,你可以使用构造函数来调用try_lock,并检查结果代码:bool locked = boost::mutex::scoped_lock lock(mutex, boost::try_to_lock); if (locked) /* lock was uncontested */; else /* lock was contested */;
【解决方案2】:

我赞成@bleater 对您的第一个问题的回答。一个小建议是,在某些情况下,您可能更感兴趣的是了解 最小值(或者更有趣的是 地板上方的最小值)而不是 平均值时间>。或者它可能是 最大值 或某个百分位数。哪个更有意义取决于您的目的。

关于您的第二个问题:查找锁被争用的次数,您可以使用原子计数器。

用法几乎与您测量时间的方式完全相同:在尝试获取锁之前增加并保存结果值,并在离开范围时减少。

只要这个值大于 1,就会发生争用。

请记住,C++ std::atomic&lt;T&gt; 不要求其库实现使用 CPU 提供的低开销原子指令。因此,您可能需要查看生成的反汇编,以确保它使用的是低开销 CPU 指令。

低级计时需要非常小心才能获得正确的结果。如果您使用 TSC (RDTSC) 进行 CPU 级时间测量,您可能还需要:

  • 禁用 SpeedStep(英特尔 EIST)
  • 禁用 TurboBoost
  • 确保您的 CPU 和平台支持不变的 TSC
  • 最后,如果发现某个线程是preempted and then migrated to another CPU core,则过滤不良时序结果,因为即使非常小心,也不能保证 CPU 内核之间的 TSC 读数一致。

(毕竟核心之间有物理距离,电信号传播速度比光慢……)

最后,需要以某种方式存储结果(无论是比赛计数、等待时间还是来自每个线程的原始时间戳跟踪),最好以某种形式的线程本地存储存储到防止缓存争用。同样,为了确保正确的结果,必须查看低级代码并测量开销以确保它做正确的事情。

【讨论】:

    猜你喜欢
    • 2012-06-13
    • 1970-01-01
    • 2010-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-30
    • 1970-01-01
    • 2015-07-12
    相关资源
    最近更新 更多