【问题标题】:Multithreaded mex code slower than single threaded多线程 mex 代码比单线程慢
【发布时间】:2012-09-28 12:20:49
【问题描述】:

我正在 MATLAB 中编写 mex 代码来执行和操作(因为该操作使用 c++ 中的库)。 mex 代码有一个部分,其中有一个函数在循环中以不同的参数值重复调用,并且每个函数调用都是独立的(即,1 个调用的计算不依赖于先前的调用)。因此,为了加快速度,我编写了创建多个线程的多线程代码 - 线程的确切数量等于循环迭代的数量,在我的示例中,此值为 10。每个线程计算循环中的函数以获得单独的值参数,线程返回并加入,完成更多计算并返回结果。 所有这一切理论上应该给我很好的加速,但我看到多线程代码比普通的单线程代码慢很多!我可以使用非常强大的 24 核机器,所以这完全令人困惑,因为我希望每个线程都被安排在一个单独的核心上。 对导致这种情况的任何想法?导致此问题的代码中是否存在任何常见问题/错误?

任何帮助将不胜感激。

编辑: 为了回答这里人们提出的解决方案中提出的许多疑问,我想分享一些关于我的代码的信息: 1. 每个函数调用都需要几分钟,因此线程的同步和生成不应该是开销(尽管如果在这种情况下有任何缓解情况,任何有关这方面的信息都会非常有帮助!)

  1. 每个线程都会访问公共数据结构、数组、矩阵,但这些值根本不会被覆盖。所有对变量的写入都是对线程本地的变量、指针、数组等进行的。所以,我猜这里应该不会有很多缓存未命中?

  2. 我的代码中也没有互斥体部分,因为没有线程写入任何公共内存位置。所有写入都写入线程本地的内存位置。

我仍在尝试找出我的多线程实现无法正常工作的原因:(因此,任何指针/信息都会非常有帮助!

谢谢!!

【问题讨论】:

  • 每个函数调用需要多长时间?如果是 uS 而不是几十毫秒,那么您可能是在浪费时间。一般来说,由于持续的开销和阻塞,create/join/destroy 几乎是最糟糕的线程化方式。不要问我为什么它在每个线程教程书/网站的第一页,因为我的回答可能会冒犯作者。请改用池或一些专用的应用程序生命周期线程。
  • 你试过调试它吗?尝试在代码中打印一些调试行 - 例如 - “线程 1 启动任务 3”、“线程 2 启动任务 6”、...等
  • 可能你使用的 C++ 库已经是多线程的了……
  • @angainor - 这就是为什么我首先问'每个函数调用需要多长时间?'。
  • @MartinJames 当然,我忽略了它。这只是在您评论的开头;)

标签: c++ multithreading matlab mex


【解决方案1】:

鉴于您的问题很笼统,一般的答案是可能有两种影响:

  • 启动和停止线程(以及同步它们)会产生大量开销,并且计算扩展不足以克服开销。每个函数调用的总次数将有助于解决这个问题。
  • 线程可能会相互竞争并降低总体性能。一种常见的机制是“缓存抖动”。由于多个核心共享相同的内存控制器和部分缓存层次结构,一个线程可以用它需要的信息填充缓存,只是让一些数据被另一个线程的需要驱逐,从而导致更多的主内存访问。由于主存访问如此昂贵,最终结果是速度变慢。

我会使用不同数量的线程来测试作业。例如,可能会证明使用两个线程是有利的,但四个或更多则不是。如需更详细的答案,请在问题中添加更多详细信息,例如计算类型、数据集大小等。

【讨论】:

    【解决方案2】:

    你没有描述你的代码做什么,所以这只是猜测。

    多线程不是灵丹妙药。有很多方法可以使单线程代码块的多线程处理比原始代码慢。产生、同步、加入和销毁线程会产生大量开销。

    假设手头的任务是添加十对数字。如果您通过为每个添加生成一个线程然后在计算完成时加入和销毁来实现这个多线程,那么您的多线程版本将比原来的慢得多。线程不适用于持续时间很短的计算。生成、加入和销毁的成本将超过并行执行这些简单任务所获得的任何加速。

    另一种让事情变慢的方法是设置障碍以防止并行操作。例如,互斥锁可防止多个写入者同时访问同一个对象。受保护的代码需要很小。让你的整个线程体在互斥体的幌子下运行,你就相当于一个单线程应用程序,其中添加了一大堆线程开销。

    即使您没有设置那些阻碍并行执行的障碍,也可能存在这些障碍。其中一些障碍位于 C 标准库中。 POSIX 要求大多数库函数是线程安全的。该标准仅列出了不必是线程安全的函数。如果您在这些计算中使用库函数,则最好保持单线程,因为您的代码本质上是单线程的。

    【讨论】:

      【解决方案3】:

      我认为您的问题根本不是 mex 特有的 - 这听起来像是为 SMP 编写多线程代码时常见的性能问题。

      补充一点已经提到的潜在问题:

      • False cache line sharing: 你可能认为你的线程是独立工作的,而实际上它们在同一个缓存行中访问不同的数据。简单的例子:

        /* global variable accessible by all threads */
        int thread_data[nthreads];
        
        /* inside thread function */
        thread_data[thrid] = some_value;
        
      • 内存带宽利用率低。在 NUMA 系统上,您希望 CPU 访问它们自己的数据库。如果您没有正确分配数据,CPU 会向其他 CPU 请求内存。这意味着通信,你不怀疑存在。

      • 线程亲和性。有点与上面的点有关。您希望您的线程在整个计算期间都绑定到它们自己的 CPU。否则它们可能会被操作系统迁移,从而导致开销,并且它们可能会被移离它们将要访问的内存库更远的地方。

      【讨论】:

        猜你喜欢
        • 2012-09-05
        • 1970-01-01
        • 1970-01-01
        • 2020-08-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多