【问题标题】:Efficient Multi-threading in CC中的高效多线程
【发布时间】:2017-04-02 20:58:12
【问题描述】:

我目前正处于用 C 语言编写多线程程序的入门阶段。我了解如何创建具有完全独立功能的独立线程,但我想了解该领域的程序员如何分解单个任务来完成多线程的优势。我已经完成了几个同步单独线程的练习,但是在分解单个任务并使其更快地使用多个线程时,这并没有什么好处。当谈到解决这些类型的问题并解释如何以及为什么你会以这种特定方式解决问题时,我真的很感激一些智慧。

例如,假设这是我试图用多个线程处理的任务。 [它将两个矩阵相乘,创建 n x p 维的 c 矩阵。] 还可以说我们可以输入我们想要在 1 和 t 之间运行的线程数(即使线程 >= 4 不会有显着的性能差异) 所以很明显如何攻击它。

 for (i = 0; i < n; i++){
     for (j = 0; j < p; j++){
         c[i][j] = 0;
         for (k = 0; k < m; k++){
             c[i][j] += a[i][k] * b[k][j];
         }
      }
  }

我的第一个想法是基本上根据线程数划分每个循环。所以,对于 t 个线程,

 for (i = 0; i < n / t; i++){
     for (j = 0; j < p / t; j++){
         c[i][j] = 0;
         for (k = 0; k < m / t; k++){
             c[i][j] += a[i][k] * b[k][j];
         }
      }
  }

然后将它们与信号量同步。但这必须将问题的每个部分分成不重叠或错过任何矩阵的 t 内聚线程。这似乎有点多,我觉得有更好的方法来攻击它。你们会怎么做呢?

【问题讨论】:

    标签: c multithreading pthreads


    【解决方案1】:

    有 m 个操作,涉及将左矩阵的一行乘以右矩阵的一列。对于 t 个线程,每个线程可以执行 m/t 操作。如果 m 不是 t 的倍数,则决定如何拆分工作。使用 m/t 表示最后一个线程做更多的工作,使用 (m+t-1)/t 表示最后一个线程做更少的工作,或者在某些线程上使用 (m/t)+1 操作和 (m/t) 操作在剩余的线程上。

    这对于多线程来说可能不是一个好案例,但至少您对这个概念有所了解。

    这是一个用于合并排序的基于 Windows 的多线程示例的链接,使用 4 个线程可将性能提高约 3 倍。之前以为merge函数中的key loop太小了,进程会受内存限制,结果发现是cpu限制。

    https://codereview.stackexchange.com/questions/148025/multithreaded-bottom-up-merge-sort

    【讨论】:

    • 超级有帮助 - 经过一段时间的思考,这是我一直在考虑的路线。我不熟悉如何查看它是否会受到内存或 CPU 的限制-您能否详细说明或指出一篇好文章?我对 C 和多线程这一方面还是很陌生。
    • @n_kritz - 在多线程归并排序中,关键循环在归并,比较两个整数,将较小的整数移动到输出数组,递增索引,检查运行结束,以及循环回比较。这是一个相当小的循环,但它受 CPU 限制,而不是单个线程的内存限制。如果您再次查看该示例,则会显示单线程与多线程实现的有效内存吞吐量(带宽)。
    【解决方案2】:

    不要尝试将其拆分为多个部分并为每个线程分配一个。首先,这是很多工作。其次,它在各种现实条件下的表现都很差。

    例如,假设您有一台具有四个物理内核和八个虚拟内核的机器,您创建了四个线程并为每个线程分配四分之一的工作。如果您的代码运行时占用了一个物理内核,那么您的两个线程将共享一个物理内核。当两个拥有自己物理核心的“快速”线程完成时,您将拥有三个物理核心,但只有两个线程在运行。呸。

    为什么要安排事情,所以你必须找出最佳的划分?这是额外的工作,如果您发现错误,它会使您的代码变慢。不要那样做。

    相反,将工作分成合理的块,并让每个线程使用以下算法:

    1. 是否有大量工作尚未开始?
    2. 如果否,请等待并转到步骤 1。
    3. 完成这项工作。
    4. 转到步骤 1。

    因此,您可以将矩阵工作分成大量方便的部分,并创建一个具有合理数量线程的池。然后线程可以在任何并发状态下运行,只要有足够的工作要做,所有的核心都会保持忙碌。

    换句话说,您以错误的方式思考问题并试图完成调度程序的工作。不要试图将特定的工作分配给特定的线程——这太难了。

    【讨论】:

    • 在某些情况下,不需要动态处理块。核心是否“被占用”取决于操作系统。例如,Windows 倾向于在每个时间片上重新分配进程和线程以合理平衡内核之间的负载,因此简单地将工作分成 4 个块用于 4 个内核(如果超线程有帮助,则为 8 个)通常就足够了。
    • @rcgldr 在某些情况下可能已经足够好了,但为了希望“足够好”足够好,还需要做很多额外的工作。为什么要做额外的工作让事情变得更糟?
    • @rcgldr 但是现在你已经解决了一个问题,即你有四个速度完全相等的核心。因此,在一般情况下,您已经付出了尽可能多的努力来解决它,而您只在一种情况下解决了它。而且,更糟糕的是,在现实条件下,即使您认为自己处于那种特殊情况下,通常也不会完全如此。 (例如,一个内核可能会获得绝大多数中断,当只有一个内核工作时会留下大量时间。)
    • @rcgldr 您可以将工作划分为足够独立的工作单元,或者不能。如果你不能,这两种技术都行不通。如果可以的话,任何一种技术都可以。因此,这不是偏爱一种方法而不是另一种方法的理由。
    • 按照@rcgldr 提到的方式划分它对我来说似乎更直接,但我也想了解你在说什么。有什么方法可以将该算法应用于我提供的示例?它可能更容易消化。
    【解决方案3】:

    并行计算(OpenCL 和矢量化)是在同一个对象上执行多个任务的最佳方式。也许多线程的最佳使用示例之一是典型的“客户端-服务器聊天通信&I/O”。 MT 服务器比多进程服务器效率高得多,因为每个通信操作的成本不足以由单个进程处理。

    如果我考虑到您的矩阵问题,我会使用 GPU 能力和 OpenCL 的并行计算来解决它,因为使用它可以同时处理矩阵中的每个元素而不是其他元素。

    Here 是使用 OpenCL 进行矩阵加法的示例

    如果我必须使用线程,我将通过创建 n 个线程(其中 n 是 CPU 的物理(甚至逻辑)核心的数量,并将矩阵的计算分成 n 个部分,就像您所做的那样来解决这个问题。显然,信号量需要处理互斥。请注意,通过使用指针算法,矩阵将同时受到线程的攻击。

    编辑:请注意,多线程解决方案只有在 CPU 是多核的情况下才能有效工作,在单核 CPU 的情况下,算术多线程操作比单线程解决方案更有效。

    如果这不能回答您的问题,请更准确地说明您要查找的内容。

    【讨论】:

    • 我目前还不熟悉 OpenCL 或矢量化。在了解如何分解手头的任务之前,是否值得深入研究?
    • 如果使用指针运算,矩阵将被线程同时攻击是什么意思?我为我的无知道歉 - 我是 C 这边的新手。
    • 可以不用为矩阵声明一个静态双精度数组,而是使用动态内存分配,这样就可以看到一个像指针一样的矩阵。通过将此点作为每个线程的参数传递,您的矩阵将由更多线程同时处理。 Here你可以找到关于指针算法的信息。
    【解决方案4】:

    对于矩阵运算,尤其是小型矩阵,最好使用 SIMD。线程确实为大型长时间运行的任务而生。对于短任务,tge 开销变得令人望而却步。

    【讨论】:

    • 我不熟悉 SIMD。
    猜你喜欢
    • 2016-01-13
    • 1970-01-01
    • 2011-11-05
    • 1970-01-01
    • 2015-11-08
    • 2023-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多