【问题标题】:Performances issues when launching an application on a parallel machine在并行机器上启动应用程序时的性能问题
【发布时间】:2009-07-13 16:41:38
【问题描述】:

我有一个非常奇怪的问题:

我有一个应用程序可以并行启动一些工作人员:

for (it = jobList.begin(); it != jobList.end(); it++) {
    DWORD threadId;
    Job job = *it;
    Worker *worker = new Worker(job);
    workers[i] = worker;
    threads[i++] = CreateThread((LPSECURITY_ATTRIBUTES)NULL, (DWORD)0, &launchThread, worker, (DWORD)0, &threadId);
}
WaitForMultipleObjects((DWORD)jobList.size(), threads, (BOOL)true, (DWORD)INFINITE);

他们分配了一堆东西,所以我假设他们在新的地方同步,但这是他们最终相互同步的唯一地方。

当我在单核机器上运行应用程序时,一切都很好;当我在多核机器上启动应用程序时,性能变得差很多,比这更差:

for (it = jobList.begin(); it != jobList.end(); it++) {
    DWORD threadId;
    Job job = *it;
    Worker *worker = new Worker(job);
    workers[i] = worker;
    threads[i++] = CreateThread((LPSECURITY_ATTRIBUTES)NULL, (DWORD)0, &launchThread, worker, (DWORD)0, &threadId);
    WaitForSingleObject(threads[i-1], (DWORD)INFINITE);
}

有人给我一个合理的猜测吗?

编辑

我进行了一些测试,发现:

  1. 使用最先进的并行分配器更改分配器无济于事
  2. 多线程应用的结果在有Core的机器上效果更好 2 双核(两个内核共享二级缓存)而不是双至强(两个处理器具有不同的缓存)。

我认为我手中的应用程序存在内存访问瓶颈,但是......我如何检查这是否真的是问题,或者我应该看看其他地方?

【问题讨论】:

  • 不知道函数launchThread的作用就没有好办法知道,很可能是缓存线乒乓。
  • -1 没有提供足够的上下文。
  • “上下文”太大了,无法放入...
  • 如果您在多核机器上运行此程序,但将进程限制为单个处理器,会发生什么情况? (Process Explorer 会让你这样做)
  • eran,好建议。我会尝试一下,然后更新问题:)

标签: c++ windows multithreading


【解决方案1】:

在寻找同步时,Interlocked* 功能有时会被忽视。它们非常快,但它们确实会强制进行一些同步和 CPU 缓存更新,这会减慢您的速度。话虽如此,人们可能必须大量使用这些功能才能获得您所描述的影响。

没有更多细节,我建议分析工作线程,深入研究冗长的代码部分并最终确定瓶颈。鉴于效果显着,瓶颈应该是显而易见的。如果您找到了位置但没有找到原因,请使用导致问题的代码的实际部分更新您的问题。

【讨论】:

    【解决方案2】:

    您的个人Jobs 是否也致电newnew 几乎总是线程安全的,但通常是this safety comes at a huge performance penalty(是的,那篇论文谈到了malloc,但是(1)同样的问题困扰着new,以及(2)new 通常使用@987654331 实现@)。

    如果对new Worker(job) 的调用失败并且没有清理代码,则可能存在内存泄漏。当然,您可能为了发布示例而删除了该代码,或者这可能是整个程序并且您依赖操作系统进行清理。您可以考虑使用类似Scope Guard 的解决方案。

    总的来说,我建议您查看英特尔的Threading Building Blocks 或Windows 的thread pool。这些应该处理许多其他棘手的细节(要创建多少线程、如何公平调度、为它们提供哪些数据以避免缓存未命中等)。

    【讨论】:

    • 不幸的是,我使用的是 metrowerks,所以我不能使用 Intel TBB,因为它不受支持。感谢您建议使用 Windows 的线程池,我不知道(我尝试了一些线程池库,但它们与 MW 不兼容或以荒谬的方式实现)
    【解决方案3】:

    您可以尝试在创建任何工作线程之前放置所有动态内存分配。动态内存分配访问需要临界区访问的堆。目前新的分配将在做什么。由于您正在运行多个线程,因此这些线程将获得大量时间来执行,并且如果这些工作线程正在执行一些动态内存分配,那么您的主线程可能会获得一点时间来分配动态内存,因为它必须等待让其他线程产生新的。

    【讨论】:

      【解决方案4】:

      您正在创建多少个线程以及您有多少个内核?每个内核超过 2 个线程都会显着降低性能。

      通常在处理此类问题时,您会设置一个线程池(即,对于双核机器,最多 4 个线程),然后为每个线程分配一个“作业”。当该作业完成时,您将下一个作业从作业列表中弹出,线程继续处理新作业。您这样做直到处理完所有作业。请记住,池中的线程可以直接从作业列表中获取下一个作业,而不是让工作线程将下一个作业分配给它。

      工作流程如下。

      1) 主线程创建多个工作线程。
      2) 主线程填充作业列表。
      3) 工作线程检查是否还有更多工作并从列表中获取下一个。
      4) 如果还有工作,请转到 3。
      5) 完成(或根据您的要求返回到 2)。

      还有其他方法可以解决该问题,但上述方法可能是最有效且易于实施的方法。

      【讨论】:

      • 这并不能解释为什么我只有在具有超过 1 个处理器/内核的机器上运行它才会降级。
      • 无论如何,使用上述线程池系统非常有可能解决您的问题。顺便说一句,你一开始没有回答我的问题......
      • 我有一个古老的编译器,它不允许我利用我发现的大部分线程池和并行化技术。顺便说一句,使用单核机器,线程/内核比率更高,并且我没有双处理器的 2 倍减速,所以原因应该在其他地方
      • 它可能确实在其他地方,我不会否认。但这很奇怪。我想知道问题是否直接在于您的古老编译器进行优化,假设两件事不能同时发生?你用的是什么编译器?
      • 另外,您是否尝试过禁用编译器中的优化并查看会发生什么?它不一定表明它不是编译器的问题,但如果它确实解决了问题,那么你肯定知道它是一个优化问题......
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-06
      • 2012-12-02
      • 2020-01-28
      • 2021-07-16
      • 1970-01-01
      相关资源
      最近更新 更多