【问题标题】:Porting threads to windows. Critical sections are very slow将线程移植到 Windows。关键部分非常慢
【发布时间】:2013-08-26 11:02:37
【问题描述】:

我正在将一些代码移植到 Windows,发现线程非常慢。该任务在 Windows 上需要 300 秒(两个至强 E5-2670 8 核 2.6ghz = 16 核),在 linux 上需要 3.5 秒(至强 E5-1607 4 核 3ghz)。使用 vs2012 express。

我有 32 个线程都在调用 EnterCriticalSection(),弹出一个 80 字节的 std::stack 作业、LeaveCriticalSection 并做一些工作(总共 250k 作业)。

在每次临界区调用之前和之后,我都会打印线程 ID 和当前时间。

  • 单线程锁的等待时间约为160ms
  • 将作业从堆栈中弹出大约需要 3 毫秒
  • 打电话请假大约需要 3 毫秒
  • 这项工作大约需要 1 毫秒

(调试/发布大致相同,调试需要更长的时间。我希望能够正确分析代码:P)

注释掉作业调用使整个过程需要 2 秒(仍然比 linux 多)。

我已经尝试过 queryperformancecounter 和 timeGetTime,两者的结果大致相同。

AFAIK 这项工作从不进行任何同步调用,但除非确实如此,否则我无法解释减速的原因。

我不知道为什么从堆栈中复制并调用 pop 需要这么长时间。 另一个非常令人困惑的事情是为什么调用 leave() 需要这么长时间。

谁能推测它为什么运行如此缓慢?

我没想到处理器的差异会带来 100 倍的性能差异,但这可能与双 CPU 有关吗? (必须在单独的 CPU 之间而不是内部内核之间进行同步)。

顺便说一句,我知道 std::thread 但我希望我的库代码可以在 C++11 之前使用。

编辑

//in a while(hasJobs) loop...

EVENT qwe1 = {"lock", timeGetTime(), id};
events.push_back(qwe1);

scene->jobMutex.lock();

EVENT qwe2 = {"getjob", timeGetTime(), id};
events.push_back(qwe2);

hasJobs = !scene->jobs.empty();
if (hasJobs)
{
    job = scene->jobs.front();
    scene->jobs.pop();
}

EVENT qwe3 = {"gotjob", timeGetTime(), id};
events.push_back(qwe3);

scene->jobMutex.unlock();

EVENT qwe4 = {"unlock", timeGetTime(), id};
events.push_back(qwe4);

if (hasJobs)
    scene->performJob(job);

还有互斥类,去掉了 linux #ifdef 的东西...

CRITICAL_SECTION mutex;

...

Mutex::Mutex()
{
    InitializeCriticalSection(&mutex);
}
Mutex::~Mutex()
{
    DeleteCriticalSection(&mutex);
}
void Mutex::lock()
{
    EnterCriticalSection(&mutex);
}
void Mutex::unlock()
{
    LeaveCriticalSection(&mutex);
}

【问题讨论】:

  • 你在 Linux 上使用了什么?您是否仅使用关键部分保护对 std::stack 的访问?
  • 你在哪里打印线程ID和当前时间?
  • 您能出示您的计时码吗?出于好奇,您是否尝试过使用互斥锁(本来预计会更慢)?
  • 您是否尝试过使用std::mutexstd::lock?你得到了哪些结果?
  • 猜测:您的问题可能是进入临界区时完成的旋转。如果许多线程试图进入临界区,它们都会旋转并尝试获取锁。由于您的线程数超过了并发运行的数量,因此生产者线程可能会被阻塞。您可以尝试使用SetCriticalSectionSpinCountInitializeCriticalSectionAndSpinCount 调整旋转计数。

标签: c++ c windows multithreading critical-section


【解决方案1】:

当您第一次进入窗口时,窗口的 CRITICAL_SECTION 会在一个紧密的循环中旋转。它不会挂起调用 EnterCriticalSection 的线程,除非在自旋循环中经过了相当长的一段时间。因此,有 32 个线程竞争同一个临界区会烧毁并浪费大量 CPU 周期。请尝试使用互斥锁(请参阅 CreateMutex)。

【讨论】:

  • I suspect the same,但调整自旋计数不是比使用互斥锁更合适/更快吗?
【解决方案2】:

您的 Windows 线程似乎正面临超级争用。它们似乎完全连载。您的关键部分有大约 7 毫秒的总处理时间和 32 个线程。如果所有线程都在锁上排队,队列中的最后一个线程要等到休眠大约 217 毫秒后才会运行。这与观察到的 160 毫秒等待时间相差不远。

所以,如果线程除了进入临界区之外别无他法,做工作,然后离开临界区,这就是我所期望的行为。

尝试刻画linux的profiling行为,看看程序的行为是否真的和apple to apple的比较。

【讨论】:

  • 我同意,它们似乎按顺序运行松散(有时执行顺序略有不同)。没有代码可以序列化它们,80 字节的复制和弹出应该比我的工作快得多。真正没有意义的是调用 LeaveCriticalSection 所花费的时间。我相信序列化是关键部分潜在问题的症状。不过,我会仔细检查 linux 上的争用情况。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-24
  • 1970-01-01
  • 1970-01-01
  • 2011-02-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多