首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >多线程GEMM比单线程GEMM慢?

多线程GEMM比单线程GEMM慢?
EN

Stack Overflow用户
提问于 2013-02-11 19:55:44
回答 3查看 1K关注 0票数 4

我写了一些简单的GEMM代码,我想知道为什么它比同等的单线程GEMM代码慢得多。

200x200矩阵,单线程: 7ms,多线程: 108ms,CPU: 3930k,线程池中有12个线程。

代码语言:javascript
复制
    template <unsigned M, unsigned N, unsigned P, typename T>
    static Matrix<M, P, T> multiply( const Matrix<M, N, T> &lhs, const Matrix<N, P, T> &rhs, ThreadPool & pool )
    {
        Matrix<M, P, T> result = {0};

        Task<void> task(pool);
        for (auto i=0u; i<M; ++i)
            for (auto j=0u; j<P; j++)
                task.async([&result, &lhs, &rhs, i, j](){
                    T sum = 0;
                    for (auto k=0u; k < N; ++k)
                        sum += lhs[i * N + k] * rhs[k * P + j];
                    result[i * M + j] = sum;
            });

        task.wait();

        return std::move(result);
    }
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2013-02-11 20:56:03

我没有使用GEMM的经验,但您的问题似乎与所有类型的多线程场景中出现的问题有关。

在使用多线程时,您会引入一些潜在的开销,其中最常见的通常是

启动/结束threads

  • context开关的
  1. creation/cleanup当(线程数)>( CPU核心数)锁定资源时,等待获取锁定
  2. 缓存同步问题

条目2.和3.可能在您的示例中不起作用:您在12个(超线程)核心上使用12个线程,并且您的算法不涉及锁。

然而,1.可能与您的情况相关:您总共创建了40000个线程,其中每个线程都会相乘和相加200值。我建议尝试一种不那么细粒度的线程,也许只在第一次循环后拆分。不要把问题分成不必要的小块总是一个好主意。

4.在你的情况下很可能是很重要的。虽然在将结果写入数组时不会遇到竞争条件(因为每个线程都在写入自己的索引位置),但很可能会引起大量的缓存同步开销。

“为什么?”你可能会这样想,因为你在写内存中的不同地方。这是因为典型的CPU缓存是在缓存线中组织的,在当前的Intel和AMD CPU型号上,缓存线是64字节长。当某些内容发生更改时,这是可用于传入和传出缓存的最小大小。现在所有CPU核心都在读写相邻的内存字,这导致当您只写入4个字节(或8个字节,取决于您使用的数据类型的大小)时,所有核心之间将同步64个字节。

如果内存不是问题,您可以简单地用“虚拟”数据“填充”每个输出数组元素,这样每个缓存线只有一个输出元素。如果你使用4byte数据类型,这意味着每1个实数数据元素跳过15个数组元素。当你减少线程的细粒度时,缓存问题也会得到改善,因为每个线程实际上都会访问自己在内存中的连续区域,而不会干扰其他线程的内存。

编辑: Herb Sutter (C++大师之一)的更详细的描述可以在这里找到:http://www.drdobbs.com/parallel/maximize-locality-minimize-contention/208200273

Edit2:顺便说一句,建议在返回语句中避免std::move,因为这可能会妨碍返回值优化和复制省略规则,而标准现在要求这些规则自动发生。请参阅Is returning with std::move sensible in the case of multiple return statements?

票数 4
EN

Stack Overflow用户

发布于 2013-02-11 20:17:46

多线程意味着总是同步、上下文切换、函数调用。所有这些加起来会消耗CPU周期,你可以花在主要任务本身上。

如果只有第三个嵌套循环,则可以保存所有这些步骤,并可以内联执行计算,而不是子例程,在子例程中,必须设置堆栈,调用,切换到不同的线程,返回结果,然后切换回主线程。

只有在这些开销与主任务相比较小的情况下,多线程才有用。我猜,当矩阵大于200x200的时候,你会看到多线程的效果更好。

票数 3
EN

Stack Overflow用户

发布于 2013-02-11 21:23:37

一般来说,多线程非常适用于耗费大量时间的任务,最有利的原因是复杂性,而不是设备访问。你向我们展示的循环需要很短的时间来执行,这样它才能有效地并行化。

您必须记住,创建线程有很多开销。同步也有一些(但明显更少)的开销。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/14811301

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档