C++ OpenMP 无法加速矩阵乘法

2023-11-29

我用 C++ 编写了一个简单的矩阵乘法程序,并且它有效。我只是 C++ 的初学者,但我已经成功地让它工作了。

让我困惑的是它比 NumPy 慢得多。我已经对它进行了基准测试。

因此,我尝试使用 OpenMP 来加速,但我观察到性能完全没有变化:

#include <algorithm>
#include <chrono>
#include <iostream>
#include <omp.h>
#include <string>
#include <vector>


using std::vector;
using std::chrono::high_resolution_clock;
using std::chrono::duration;
using std::chrono::duration_cast;
using std::chrono::microseconds;
using std::cout;
using line = vector<double>;
using matrix = vector<line>;


void fill(line &l) {
    std::generate(l.begin(), l.end(), []() { return ((double)rand() / (RAND_MAX)); });
}

matrix random_matrx(int64_t height, int64_t width) {
    matrix mat(height, line(width));
    std::for_each(mat.begin(), mat.end(), fill);
    return mat;
}

matrix dot_product(const matrix &mat0, const matrix &mat1) {
    size_t h0, w0, h1, w1;
    h0 = mat0.size();
    w0 = mat0[0].size();
    h1 = mat1.size();
    w1 = mat1[0].size();
    if (w0 != h1) {
        throw std::invalid_argument("matrices cannot be cross multiplied");
    }

    matrix out(h0, line(w1));
    for (int y = 0; y < h0; y++) {
        for (int x = 0; x < w1; x++) {
            double s = 0;
            for (int z = 0; z < w0; z++) {
                s += mat0[y][z] * mat1[z][x];
            }
            out[y][x] = s;
        }
    }

    return out;
}

matrix dot_product_omp(const matrix& mat0, const matrix& mat1) {
    size_t h0, w0, h1, w1;
    h0 = mat0.size();
    w0 = mat0[0].size();
    h1 = mat1.size();
    w1 = mat1[0].size();
    if (w0 != h1) {
        throw std::invalid_argument("matrices cannot be cross multiplied");
    }

    matrix out(h0, line(w1));
    omp_set_num_threads(4);
    #pragma omp parallel for private(y, x, z) schedule(dynamic)
    for (int y = 0; y < h0; y++) {
        for (int x = 0; x < w1; x++) {
            double s = 0;
            for (int z = 0; z < w0; z++) {
                s += mat0[y][z] * mat1[z][x];
            }
            out[y][x] = s;
        }
    }

    return out;
}


int main()
{
    matrix a, b;
    a = random_matrx(16, 9);
    b = random_matrx(9, 24);
    auto start = high_resolution_clock::now();
    for (int64_t i = 0; i < 65536; i++) {
        dot_product(a, b);
    }
    auto end = high_resolution_clock::now();
    duration<double, std::nano> time = end - start;
    double once = time.count() / 65536000;
    cout << "mat(16, 9) * mat(9, 24): " + std::to_string(once) + " microseconds\n";
    a = random_matrx(128, 256);
    b = random_matrx(256, 512);
    start = high_resolution_clock::now();
    for (int64_t i = 0; i < 512; i++) {
        dot_product(a, b);
    }
    end = high_resolution_clock::now();
    time = end - start;
    once = time.count() / 512000;
    cout << "mat(128, 256) * mat(256, 512): " + std::to_string(once) + " microseconds\n";
    start = high_resolution_clock::now();
    for (int64_t i = 0; i < 512; i++) {
        dot_product_omp(a, b);
    }
    end = high_resolution_clock::now();
    time = end - start;
    once = time.count() / 512000;
    cout << "mat(128, 256) * mat(256, 512) omp: " + std::to_string(once) + " microseconds\n";
}
PS D:\MyScript> C:\Users\Xeni\source\repos\matmul\x64\Release\matmul.exe
mat(16, 9) * mat(9, 24): 5.200116 microseconds
mat(128, 256) * mat(256, 512): 30128.739453 microseconds
mat(128, 256) * mat(256, 512) omp: 30116.103125 microseconds

我使用 Visual Studio 2022、C++20 标准、编译器标志编译它:

/permissive- /ifcOutput "x64\Release\" /GS /GL /W3 /Gy /Zc:wchar_t /Zi /Gm- /O2 /Ob2 /sdl /Fd"x64\Release\vc143.pdb" /Zc:inline /fp:precise /D "NDEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /std:c17 /Gd /Oi /MD /std:c++20 /FC /Fa"x64\Release\" /EHsc /nologo /Fo"x64\Release\" /Ot /Fp"x64\Release\matmul.pch" /diagnostics:column

附加标志:

/arch:AVX2 /fp:fast 

只是为什么没有任何改善呢?我怎样才能真正改善它?


我已将 OMP 版本更改为:

matrix dot_product_omp(const matrix& mat0, const matrix& mat1) {
    size_t h0, w0, h1, w1;
    h0 = mat0.size();
    w0 = mat0[0].size();
    h1 = mat1.size();
    w1 = mat1[0].size();
    if (w0 != h1) {
        throw std::invalid_argument("matrices cannot be cross multiplied");
    }

    matrix out(h0, line(w1));
    omp_set_num_threads(4);
    #pragma omp parallel for schedule(dynamic)
    for (int y = 0; y < h0; y++) {
        for (int x = 0; x < w1; x++) {
            double s = 0;
            for (int z = 0; z < w0; z++) {
                s += mat0[y][z] * mat1[z][x];
            }
            out[y][x] = s;
        }
    }

    return out;
}

我已经编译了/openmpflag,我已经进行了多次基准测试,它只使代码运行时间约为原始时间的四分之一:

PS D:\MyScript> C:\Users\Xeni\source\repos\matmul\x64\Release\matmul.exe
mat(16, 9) * mat(9, 24): 5.126476 microseconds
mat(128, 256) * mat(256, 512): 30999.137109 microseconds
mat(128, 256) * mat(256, 512) omp: 8574.475195 microseconds

NumPy 更快:

In [374]: a = np.random.random((128, 256))

In [375]: b = np.random.random((256, 512))

In [376]: %timeit a @ b
382 µs ± 19.6 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)

我的代码慢了一个数量级。那么如何才能缩小性能差距呢?


代码中存在很多问题,但关键是内存访问模式效率低下 and it 防止(几乎)任何矢量化.

访问mat1[z][x]效率低下,因为当z增加,需要获取一个新的向量,然后x第一项是从内存中获取的。这会导致两次类似随机的内存读取。这种内存访问比顺序内存访问慢得多。更不用说大多数编译器不会对具有此类内存访问的循环进行矢量化,因为这几乎是不可能的(理论上这对于 SIMD 集合来说是可能的,但实际上效率很低)。最重要的是,缓存线的使用很差:只有 8/64 字节的缓存线与mat1被使用,其余的被浪费,因为高速缓存行将很快从高速缓存中逐出(导致高速缓存垃圾)。这样的问题会导致应用程序在大多数平台上无法很好地扩展,因为它变得内存限制(使用更多内核并不会使 RAM 运行速度更快)。您需要连续读取数据以获得更好的性能。这是一个更快的实现:

    #pragma omp parallel for schedule(dynamic)
    for (int y = 0; y < h0; y++) {
        for (int x = 0; x < w1; x++) {
            out[y][x] += 0.0;
        }
        for (int z = 0; z < w0; z++) {
            for (int x = 0; x < w1; x++) {
                out[y][x] += mat0[y][z] * mat1[z][x];
            }
        }
    }
Before:
mat(16, 9) * mat(9, 24): 2.931490 microseconds
mat(128, 256) * mat(256, 512): 14704.138781 microseconds
mat(128, 256) * mat(256, 512) omp: 4013.665295 microseconds

After:
mat(16, 9) * mat(9, 24): 0.931926 microseconds
mat(128, 256) * mat(256, 512): 3296.070098 microseconds
mat(128, 256) * mat(256, 512) omp: 1230.341350 microseconds

顺序代码比以前快 4.3 倍,并行代码现在快 3.3 倍。现在应该对代码进行矢量化。

由于其他因素,该代码仍然相当低效,例如:

  • 缓存未命中/损坏:矩阵重新加载多次;
  • FMA/负载比小:CPU 花时间加载数据,同时可以花时间执行 FMA 指令;
  • 内存间接寻址:vector<vector<double>>是一种存储矩阵效率非常低的数据结构,请使用展平数组(或本征);
  • schedule(dynamic)在大多数机器上效率低下,实际上不应该有用:如果代码被优化,工作应该在核心之间平衡。考虑使用schedule(static);
  • NUMA影响(尤其是对服务器和 AMD 处理器);
  • etc.

正如评论中提到的,人们不应该期望达到 Numpy 的速度,因为它调用dgemmBLAS 原语在大多数机器上接近最佳(至少对于 OpenBLAS、BLIS 和 Intel MKL)。如果没有低级 SIMD 内在函数或汇编代码,则很难获得类似的性能(大多数编译器会生成次优代码,由于寄存器分配不当而无法实现最佳性能)。

很多高性能计算书籍 and 教程解释此类问题以及如何解决它们。有些具体解释了如何获得相对快速的矩阵乘法。我强烈鼓励您阅读它们。

注意private(y, x, z)没有用,因为变量是在循环中声明的。事实上,这甚至是不正确的(像 GCC 这样的编译器会打印错误)。另请注意,使用omp_set_num_threads(4);通常被认为是一种不好的做法。您应该修改环境变量OMP_NUM_THREADS。另外请注意,MSVC 支持的默认 OpenMP 版本非常旧且完全过时。应改用 Clang OpenMP 版本。事实上,据我所知 MSVC 不是一个好的优化编译器,因此应该使用 Clang/GCC(或任何 HPC 编译器,如 ICC、PGI 等)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++ OpenMP 无法加速矩阵乘法 的相关文章

  • Qt - QProcess 不工作

    我尝试启动 Internet Explorer 所以我使用下面的代码 QProcess process new QProcess this QString temp C Program Files Internet Explorer iex
  • 尝试了解使用服务打开对话框

    我已经阅读了有关使用 mvvm 模式打开对话框的讨论 我看过几个使用服务的示例 但我不明白所有部分如何组合在一起 我发布这个问题寻求指导 以了解我应该阅读哪些内容 以更好地理解我所缺少的内容 我将在下面发布我所拥有的内容 它确实有效 但从我
  • 转换 const void*

    我有一个函数返回一个const void 我想用它的信息作为char 我可以将它投射为 C 风格的罚款 char variable但是当我尝试使用reinterpret cast like reinterpret cast
  • 如何在类文件中使用 Url.Action() ?

    如何在 MVC 项目的类文件中使用 Url Action Like namespace 3harf public class myFunction public static void CheckUserAdminPanelPermissi
  • 循环遍历 C 结构中的元素以提取单个元素的值和数据类型

    我有一个要求 我有一个 C 语言的大结构 由大约 30 多个不同数据类型的不同元素组成 typedef struct type1 element1 type2 element2 type3 element3 type2 element4 1
  • 从复选框列表中选择循环生成的复选框中的一个复选框

    抱歉我的英语不好 在我的 ASP NET 网站上 我从 SQL 表导入软件列表 看起来像这样 但实际上要长得多 Microsoft Application Error Reporting br br Microsoft Applicatio
  • extern 声明和函数定义都在同一文件中

    我只是浏览了一下gcc源文件 在gcc c 我发现了类似的东西 extern int main int char int main int argc char argv 现在我的疑问是extern是告诉编译器特定的函数不在这个文件中 但可以
  • 如何将 .txt 文件中的数据转换为 xml? C#

    我在一个文本文件中有数千行数据 我想通过将其转换为更容易搜索的内容来轻松搜索 我希望 XML 或其他类型的大型数据结构 尽管我不确定它是否是最好的对于我的想法 每行的数据如下所示 第 31 册 托马斯 乔治 32 34 154 每本书都不是
  • 强制初始化模板类的静态数据成员

    关于模板类的静态数据成员未初始化存在一些问题 不幸的是 这些都没有能够帮助我解决我的具体问题的答案 我有一个模板类 它有一个静态数据成员 必须为特定类型显式实例化 即必须专门化 如果不是这种情况 使用不同的模板函数应该会导致链接器错误 这是
  • Eigen 和 OpenMP:由于错误共享和线程开销而没有并行化

    系统规格 Intel Xeon E7 v3 处理器 4 插槽 16 核 插槽 2 线程 核心 Eigen 系列和 C 的使用 以下是代码片段的串行实现 Eigen VectorXd get Row const int j const int
  • 什么是空终止字符串?

    它与什么不同标准 字符串 http www cplusplus com reference string string 字符串 实际上只是一个数组chars 空终止字符串是指其中包含空字符的字符串 0 标记字符串的结尾 不一定是数组的结尾
  • 如何使用 ASP.NET Core 获取其他用户的声明

    我仍在学习 ASP NET Core 的身份 我正在进行基于声明的令牌授权 大多数示例都是关于 当前 登录用户的 就我而言 我的 RPC 服务正在接收身份数据库中某个用户的用户名和密码 我需要 验证是否存在具有此类凭据的用户 获取该用户的所
  • 在 C# 中检查 PowerShell 执行策略的最佳方法是什么?

    当你跑步时Get ExecutionPolicy在 PowerShell 中 它得到有效的执行政策 https learn microsoft com en us powershell module microsoft powershell
  • 是否使用 C# 数据集? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我对 C 中的数据集概念有点困惑 编码 ASP NET 站点 但这并不重要 在我的阅读中 我了解到它们 本质上 用作我的应用程序和我的
  • 将二变量 std::function 转换为单变量 std::function

    我有一个函数 它获取两个值 x 和 y 并返回结果 std function lt double double double gt mult double x double y return x y 现在我想得到一个常量 y 的单变量函数
  • 如何最好地以编程方式将 `__attribute__ ((unused))` 应用于这些自动生成的对象?

    In my makefile我有以下目标 它将文本 HTML 资源 编译 为unsigned char数组使用xxd i http linuxcommand org man pages xxd1 html 我将结果包装在匿名命名空间和标头保
  • 如何解压 msgpack 文件?

    我正在将 msgpack 编码的数据写入文件 在编写时 我只是使用 C API 的 fbuffer 如 我为示例删除了所有错误处理 FILE fp fopen filename ab msgpack packer pk msgpack pa
  • C++:为什么 numeric_limits 对它不知道的类型起作用?

    我创建了自己的类型 没有任何比较器 也没有专门化std numeric limits 尽管如此 由于某种原因 std numeric limits
  • 将 Lambda 表达式树与 IEnumerable 结合使用

    我一直在尝试了解有关使用 Lamba 表达式树的更多信息 因此我创建了一个简单的示例 这是代码 如果作为 C 程序粘贴到 LINQPad 中 它可以工作 void Main IEnumerable
  • 为什么空循环使用如此多的处理器时间?

    如果我的代码中有一个空的 while 循环 例如 while true 它将把处理器的使用率提高到大约 25 但是 如果我执行以下操作 while true Sleep 1 它只会使用大约1 那么这是为什么呢 更新 感谢所有精彩的回复 但我

随机推荐

  • 这个字符串的范围是多少?

    如果我有以下代码 UnicodeString sFish L FISH char szFish AnsiString sFish c str CallFunc szFish 那么临时的范围是什么呢 AnsiString已创建 持续时间是多久
  • 将java应用程序作为后台进程运行

    我已经使用java制作了一个应用程序 当我将其安装在我的计算机上时 我希望它运行为后台进程而不是应用程序 如果任何用户尝试任务管理器 那么他无法在应用程序中找到它 它不应该在应用程序列表中列出 它在进程列表中 所以请告诉我怎样才能做到这一点
  • 适合编写编译器的好语言

    我正在考虑用 haskell 编写一个编译器 为了获得一些知识和经验 我将尝试为现有语言实现编译器 有人可以给我一份适合于此的语言列表吗 提前致谢 Pascal 可能是一个好的开始 您可以一次性编译它 Lisp 的一个子集可能有助于理解 l
  • 使用纹理区域和引擎渲染精灵

    请注意 这是我第一次尝试 Andengine 我一直在尝试使用 libGdx 作为 Android 游戏开发的潜力 但它不适合我的需求 因为我只想为 Android 进行开发 而使用 3D 引擎来完成 2D 工作似乎有点矫枉过正 我现在想尝
  • 解释 SciPy 层次聚类树状图的输出? (也许发现了一个错误......)

    我想弄清楚如何输出scipy cluster hierarchy dendrogram有效 我以为我知道它是如何工作的 并且我能够使用输出来重建树状图 但似乎我不再理解它了 或者有一个错误Python 3该模块的版本 这个答案 如何获取 s
  • AES128 CTR 加密与 iv

    我想用 iv 和 key 实现 AES128 CTR 我正在寻找如何以最好的方式做到这一点而不是重新发明轮子的建议 我为此找到了很好的库RNC加密器 但看起来那里不支持这个 aes 我也测试this方法 但看起来这不是点击率 EDIT 我使
  • 如何使用 Discord 机器人嵌入消息?

    我想编写一个机器人 将用户发送的消息嵌入到特定频道中 如果您对 GTA RP 服务器有所了解 它就像 Twitter 或 Instagram 机器人 这是一个例子 我认为这与console log和作者的名字 但我不确定 所以这就是我来这里
  • PERL Email::Send::Gmail 无法连接到 Windows 7 上的 Gmail 帐户

    我正在尝试使用Email Send Gmail发送电子邮件 但由于某种原因 我收到一条错误消息 不允许我连接 代码是标准示例 usr bin perl use strict use warnings use Email Send use E
  • EmberJS registerHelper 在 Ember 1.8 中动态渲染模板

    我想动态渲染一个模板 如下所示 each layout in layouts render layout get type layout each 问题是 render 不需要变量作为它的第一个参数 所以在旧的 EmberJS 版本中 可以
  • 如何将 d3.js 图表转换/保存为 pdf/jpeg

    我正在开发一个客户端 javascript 函数来将现有的 D3 SVG 图形保存或转换为文件 我搜索了很多并找到了一些建议 主要使用canvas toDataURL 我没有
  • 如何将文件嵌入到 exe 中?

    我需要一个包含我的数据的文件嵌入到一个exe中 这样当用户调用它时 它将打开该文件 读取它 采取行动 并且用户看不到该文件或无法访问它或知道它存在 我怎样才能做到这一点 像 Molebox 这样的文件加壳器可以做到这一点 但会花费相当多的费
  • iPhone:如何允许在自定义单元格的表格视图中进行多项选择?

    我该如何调整它才能进行多项选择 并获取选定的 id initWithCellIdentifier NSString cellID if self super initWithStyle UITableViewCellStyleDefault
  • EF4.1:可能有零或一到零或一(0..1 到 0..1)的关系吗?

    NET 4 0 与 SQL Server 2008 R2 我试图表示 0 1 到 0 1 的关系 但不断收到以下错误 错误 113 多重性与关系 1 中角色 0 中的引用约束冲突 由于从属角色中的所有属性均不可为空 因此主体角色的重数必须为
  • 在多项目解决方案中共享变量

    我正在使用 C 在 VS2010 中为 Outlook 2010 创建一个解决方案 该解决方案由 3 个项目组成 项目 A B 和 C 都依赖于此 它定义了需要从 B 和 C 访问的某些变量 函数 项目 B 需要从 A 读取变量 项目 C
  • 如何在不访问 http://getfirebug.com 的情况下包含 firebug light?

    过去我可以包含萤火虫灯 如下所述 JavaFX WebView 中的 Html Javascript 调试 不幸的是 链接 http getfirebug com releases lite 1 2 firebug lite compres
  • Android 中的全局 TTS

    您好 我正在为盲人用户开发一个应用程序 因此我经常使用文本转语音 作为响应用户操作的唯一一种方法 我决定让一个全局 TTS 实例与应用程序的运行时间一样长 我是这样实现的 package com simekadam blindguardia
  • 我可以在 Google App Engine 上使用 SQLite 库吗?

    我目前正在使用 Google Datastore 来存储数据 我想以sql数据库的形式保留一个离线版本 是否可以在谷歌应用引擎上使用sqlite将数据存储转换为sql数据库 从 Python Development Server v1 7
  • 新的 Rails 应用程序默认使用 Postgres 而不是 SQLite3

    我刚刚在这台机器上第一次安装了 rbenv ruby 2 2 3 和rails 4 2 4 我已经启动了我的 Rails 应用程序 没有更改任何代码 只是使用默认生成的文档rails new 然后我启动了服务器rails server 击球
  • 为什么带有内部类的 Java 代码会生成第三个 SomeClass$1.class 文件? [复制]

    这个问题在这里已经有答案了 如果我有一个内部类 如下所示 public class Test public class Inner code public static void main String args code 当我编译它时 我
  • C++ OpenMP 无法加速矩阵乘法

    我用 C 编写了一个简单的矩阵乘法程序 并且它有效 我只是 C 的初学者 但我已经成功地让它工作了 让我困惑的是它比 NumPy 慢得多 我已经对它进行了基准测试 因此 我尝试使用 OpenMP 来加速 但我观察到性能完全没有变化 incl