windows C++ 并行编程-矩阵乘法

news2024/9/24 1:25:09

下面我们尝试分步演练演示如何使用 C++ AMP 加速矩阵乘法的执行。 提供了两种算法,一种不使用平铺,一种使用平铺,看看两者的差别。

在 Visual Studio 中创建项目
  • 在菜单栏上,选择“文件”>“新建”>“项目”,打开“创建新项目”对话框 。
  • 在对话框顶部,将“语言”设置为“C++”,将“平台”设置为“Windows”,并将“项目类型”设置为“控制台”。
  • 从筛选的项目类型列表中,选择“空项目”,然后选择“下一步”。 在下一页中的“名称”框内输入“MatrixMultiply”以指定项目的名称,并根据需要指定项目位置。
  • 选择“创建”按钮创建客户端项目。
  • 在“解决方案资源管理器”中,打开“源文件”的快捷菜单,然后选择“添加”>“新项”。
  • 在“添加新项”对话框中,选择“C++ 文件(.cpp)”,在“名称”框中输入“MatrixMultiply.cpp”,然后选择“添加”按钮。
不使用平铺的乘法

在本部分中,请考虑两个矩阵(A 和 B)的乘法,定义如下:

A 是 3x2 矩阵,B 是 2x3 矩阵。 A 和 B 的乘积是以下 3x3 矩阵。 乘积通过逐个元素将 A 的行乘以 B 的列进行计算。

 

在不使用 C++ AMP 的情况下相乘

打开 MatrixMultiply.cpp,并使用以下代码替换现有代码。

#include <iostream>

void MultiplyWithOutAMP() {
    int aMatrix[3][2] = {{1, 4}, {2, 5}, {3, 6}};
    int bMatrix[2][3] = {{7, 8, 9}, {10, 11, 12}};
    int product[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};

    for (int row = 0; row < 3; row++) {
        for (int col = 0; col < 3; col++) {
            // Multiply the row of A by the column of B to get the row, column of product.
            for (int inner = 0; inner < 2; inner++) {
                product[row][col] += aMatrix[row][inner] * bMatrix[inner][col];
            }
            std::cout << product[row][col] << "  ";
        }
        std::cout << "\n";
    }
}

int main() {
    MultiplyWithOutAMP();
    getchar();
}

 该算法是矩阵乘法定义的简单实现。 它不使用任何并行或线程算法来减少计算时间。

使用 C++ AMP 相乘

1. 在 MatrixMultiply.cpp 中,在 main 方法之前添加以下代码。

void MultiplyWithAMP() {
int aMatrix[] = { 1, 4, 2, 5, 3, 6 };
int bMatrix[] = { 7, 8, 9, 10, 11, 12 };
int productMatrix[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };

array_view<int, 2> a(3, 2, aMatrix);

array_view<int, 2> b(2, 3, bMatrix);

array_view<int, 2> product(3, 3, productMatrix);

parallel_for_each(product.extent,
   [=] (index<2> idx) restrict(amp) {
       int row = idx[0];
       int col = idx[1];
       for (int inner = 0; inner <2; inner++) {
           product[idx] += a(row, inner)* b(inner, col);
       }
   });

product.synchronize();

for (int row = 0; row <3; row++) {
   for (int col = 0; col <3; col++) {
       //std::cout << productMatrix[row*3 + col] << "  ";
       std::cout << product(row, col) << "  ";
   }
   std::cout << "\n";
  }
}

 AMP 代码类似于非 AMP 代码。 对 parallel_for_each 的调用为 product.extent 中的每个元素启动一个线程,并替换行和列的 for 循环。 idx 中提供了行和列的单元格的值。 可以使用 [] 运算符和索引变量,或者 () 运算符和行列变量来访问 array_view 对象的元素。 示例演示两种方法。 array_view::synchronize 方法将 product 变量的值复制回 productMatrix 变量。

2. 在 MatrixMultiply.cpp 的顶部添加以下 include 和 using 语句。 

#include <amp.h>
using namespace concurrency;

3. 修改 main 方法以调用 MultiplyWithAMP 方法。

int main() {
    MultiplyWithOutAMP();
    MultiplyWithAMP();
    getchar();
}
使用平铺 

平铺是一种将数据分区成大小相等的子集(称为平铺)的技术。 使用平铺时会有三个改变。

  • 可以创建 tile_static 变量。 访问 tile_static 空间中的数据可能比访问全局空间中的数据要快得多。 为每个平铺创建 tile_static 变量的实例,平铺中的所有线程可以访问该变量。 平铺的主要好处是可以从 tile_static 访问中获得性能增益;
  • 可以调用 tile_barrier::wait 方法,以在指定代码行的一个平铺中停止所有线程。 不能保证线程的运行顺序,只有一个平铺中的所有线程都在调用 tile_barrier::wait 时停止,才能继续执行;
  • 可以访问相对于整个 array_view 对象的索引以及相对于平铺的索引。 使用局部索引可使代码更易于阅读和调试;

若要利用矩阵乘法中的平铺,算法必须将矩阵分区成平铺,然后将平铺数据复制到 tile_static 变量中以加快访问速度。 在此示例中,矩阵分区成大小相等的子矩阵。 乘积通过将子矩阵相乘而得出。 此示例中的两个矩阵及其乘积为:

矩阵分区成四个 2x2 矩阵,定义如下:

 

现在可以按以下方式撰写和计算 A 和 B 的乘积:

 

由于矩阵 a 到 h 是 2x2 矩阵,因此其所有乘积及和也是 2x2 矩阵。 它还遵循 A 和 B 的乘积是 4x4 矩阵,与预期一样。 若要快速检查算法,请计算乘积中第一行与第一列相交处的元素的值。 在此示例中,这将是 ae + bg 的第一行与第一列相交处的元素的值。 只需计算每个术语的 ae 和 bg 的第一列与第一行。 ae 的值为 (1 * 1) + (2 * 5) = 11。 (3 * 1) + (4 * 5) = 23 的值为 bg。 最终值是正确的 11 + 23 = 34。

若要实现此算法,代码:

  • 在 parallel_for_each 调用中使用 tiled_extent 对象,而不是 extent 对象;
  • 在 parallel_for_each 调用中使用 tiled_index 对象,而不是 index 对象;
  • 创建 tile_static 变量以保留子矩阵;
  • 使用 tile_barrier::wait 方法停止线程以计算子矩阵的乘积;
使用 AMP 和平铺相乘

1. 在 MatrixMultiply.cpp 中,在 main 方法之前添加以下代码。

void MultiplyWithTiling() {
    // The tile size is 2.
    static const int TS = 2;

    // The raw data.
    int aMatrix[] = { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 };
    int bMatrix[] = { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 };
    int productMatrix[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

    // Create the array_view objects.
    array_view<int, 2> a(4, 4, aMatrix);
    array_view<int, 2> b(4, 4, bMatrix);
    array_view<int, 2> product(4, 4, productMatrix);

    // Call parallel_for_each by using 2x2 tiles.
    parallel_for_each(product.extent.tile<TS, TS>(),
        [=] (tiled_index<TS, TS> t_idx) restrict(amp)
        {
            // Get the location of the thread relative to the tile (row, col)
            // and the entire array_view (rowGlobal, colGlobal).
            int row = t_idx.local[0];
            int col = t_idx.local[1];
            int rowGlobal = t_idx.global[0];
            int colGlobal = t_idx.global[1];
            int sum = 0;

            // Given a 4x4 matrix and a 2x2 tile size, this loop executes twice for each thread.
            // For the first tile and the first loop, it copies a into locA and e into locB.
            // For the first tile and the second loop, it copies b into locA and g into locB.
            for (int i = 0; i < 4; i += TS) {
                tile_static int locA[TS][TS];
                tile_static int locB[TS][TS];
                locA[row][col] = a(rowGlobal, col + i);
                locB[row][col] = b(row + i, colGlobal);
                // The threads in the tile all wait here until locA and locB are filled.
                t_idx.barrier.wait();

                // Return the product for the thread. The sum is retained across
                // both iterations of the loop, in effect adding the two products
                // together, for example, a*e.
                for (int k = 0; k < TS; k++) {
                    sum += locA[row][k] * locB[k][col];
                }

                // All threads must wait until the sums are calculated. If any threads
                // moved ahead, the values in locA and locB would change.
                t_idx.barrier.wait();
                // Now go on to the next iteration of the loop.
            }

            // After both iterations of the loop, copy the sum to the product variable by using the global location.
            product[t_idx.global] = sum;
        });

    // Copy the contents of product back to the productMatrix variable.
    product.synchronize();

    for (int row = 0; row <4; row++) {
        for (int col = 0; col <4; col++) {
            // The results are available from both the product and productMatrix variables.
            //std::cout << productMatrix[row*3 + col] << "  ";
            std::cout << product(row, col) << "  ";
        }
        std::cout << "\n";
    }
}

此示例明显不同于不使用平铺的示例。 代码使用以下概念步骤:

  • 将 a 的平铺[0,0] 的元素复制到 locA 中。 将 b 的平铺[0,0] 的元素复制到 locB 中。 请注意,product 是平铺的,而不是 a 和 b。 因此,使用全局索引访问 a, b 和 product。 调用 tile_barrier::wait 至关重要。 它会停止平铺中的所有线程,直到填充完 locA 和 locB;
  • 将 locA 与 locB 相乘并将结果放入 product 中;
  • 将 a 的 tile[0,1] 的元素复制到 locA 中。 将 b 的 tile[1,0] 的元素复制到 locB 中;
  • 将 locA 与 locB 相乘并将它们添加到已在 product 中的结果;
  • tile[0,0] 的乘法已完成;
  • 对其他四个平铺重复上述步骤。 没有专门为平铺编制索引,线程可以按任意顺序执行。 执行每个线程时,会相应地为每个平铺创建 tile_static 变量,并且对 tile_barrier::wait 的调用控制程序流;
  • 仔细检查算法时,请注意,每个子矩阵都加载到 tile_static 内存中两次。 数据传输确实需要一段时间。 但是,一旦数据位于 tile_static 内存中,对数据的访问速度要快得多。 由于计算乘积需要重复访问子矩阵中的值,因此总体性能提升。 对于每种算法,需要试验才能找到最佳算法和平铺大小;

在非 AMP 和非平铺示例中,从全局内存访问 A 和 B 的每个元素四次,以计算乘积。 在平铺示例中,每个元素从全局内存访问两次,并从 tile_static 内存访问四次。 这不是显著的性能提升。 但是,如果 A 和 B 是 1024x1024 矩阵,平铺大小为 16,则性能将显著提升。 在这种情况下,每个元素将仅复制到 tile_static 内存中 16 次,并从 tile_static 内存访问 1024 次。

2. 如下所示修改 main 方法以调用 MultiplyWithTiling 方法。

int main() {
    MultiplyWithOutAMP();
    MultiplyWithAMP();
    MultiplyWithTiling();
    getchar();
}

编译并运行;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2093795.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[Algorithm][综合训练][过桥][最大差值][兑换零钱]详细讲解

目录 1.过桥1.题目链接2.算法原理详解 && 代码实现 2.最大差值1.题目链接2.算法原理详解 && 代码实现 3.兑换零钱1.题目链接2.算法原理详解 && 代码实现 1.过桥 1.题目链接 过桥 2.算法原理详解 && 代码实现 解法&#xff1a;贪心 BFS #in…

【Slurm集群在centos7上的搭建】

Slurm集群在centos7上的部署 集群基本情况1. 前期准备工作2.网络配置3.NTP时间同步配置4.NFS共享目录配置5.NIS用户管理配置6.Munge通信部署7.安装Mariadb数据库以及Slurm安装配置7.1安装配置Mariadb及SlurmID配置7.2Slurm安装配置 附录配置文件slurm.conf&#xff1a;slurmdbd…

Redis从入门再再到入门(下)

文章目录 1.Redis远程连接1.1 Redis远程连接配置1.2 通过桌面版图形化界面连接Redis1.3 通过IDEA中的插件连接Redis 2.Jedis的基本使用2.1 jedis概述2.2 jedis的基本操作2.3 jedis连接池 3.Spring整合Redis3.1 新建maven工程,引入相关依赖3.2 redis.properties3.3 spring-redis…

Python | Leetcode Python题解之第387题字符串中的第一个唯一字符

题目&#xff1a; 题解&#xff1a; class Solution:def firstUniqChar(self, s: str) -> int:position dict()q collections.deque()n len(s)for i, ch in enumerate(s):if ch not in position:position[ch] iq.append((s[i], i))else:position[ch] -1while q and po…

如何开发针对不平衡分类的成本敏感神经网络 python

如何开发针对不平衡分类的成本敏感神经网络 深度学习神经网络是一类灵活的机器学习算法&#xff0c;可以在各种问题上表现良好。 神经网络使用误差反向传播算法进行训练&#xff0c;该算法涉及计算模型在训练数据集上产生的误差&#xff0c;并根据这些误差的比例更新模型权重…

鸿蒙开发入门day16-拖拽事件和手势事件

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;还请三连支持一波哇ヾ(&#xff20;^∇^&#xff20;)ノ&#xff09; 目录 拖拽事件 概述 拖拽流程 ​手势拖拽 ​鼠标拖拽 拖拽背板图 …

如何有效防止表单重复提交

如何有效防止表单重复提交 1. 使用重定向&#xff08;Redirect&#xff09;2. 点击后按钮失效3. Loading 遮罩4. 自定义重复提交过滤器 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在Web开发中&#xff0c;表单重复提交是一个常见问题&…

计算物理精解【3】

文章目录 力学单位矢量基础定义 矢量加法矢量加法的几何方法矢量加法的代数方法示例注意事项 矢量间的关系矢量&#xff08;或向量&#xff09;的标量积&#xff08;也称为点积、内积或数量积&#xff09;性质计算两矢量之间的夹角例子步骤数值结果 计算两三维矢量之间夹角的例…

厨房老鼠检测算法解决方案老鼠检测算法源码样本详细介绍

厨房老鼠检测算法是一种创新的解决方案&#xff0c;它结合了机器学习和图像识别技术。通过使用高精度的传感器和智能摄像头&#xff0c;这些算法可以实时监控厨房环境&#xff0c;并检测到老鼠的活动痕迹。与传统的检测方法相比&#xff0c;这种算法具有更高的灵敏度和准确性&a…

Java对象的访问定位技术

Java虚拟机规范中规定reference类型是一个指向对象的引用&#xff0c;但规定并没有定义这个引用应该通过什么方式去定位、访问堆中的对象的具体位置&#xff0c;所以对象访问方式取决于具体的虚拟机实现。 目前主流的访问方式有两种&#xff1a;使用句柄和直接指针。 使用句柄…

Altium designer设计经验谈——常用规则的使用(二)

文章目录 前言三、规则设置介绍——走线规则1、Routing——>Width 线宽2、Routing——>Topology 拓扑 四、规则设置介绍——平面层规则1、Plane——>电源层连接样式 Power Plane Connect Style2、Plane——>电源层间距距离 Power Plane Clearance3、Plane——>多…

单片机编程魔法师-并行多任务程序

程序架构 程序代码 小结 数码分离&#xff0c;本质上就是将数据和代码逻辑进行分离&#xff0c;跟第一章使用数据驱动程序一样的道理。 不过这里不同之处在于。这里使用通过任务线程&#xff0c;但是却有2个任务在运行&#xff0c;两个任务都通过先初始化任务数据参数&#x…

C++ | Leetcode C++题解之第387题字符串中的第一个唯一字符

题目&#xff1a; 题解&#xff1a; class Solution { public:int firstUniqChar(string s) {unordered_map<char, int> position;queue<pair<char, int>> q;int n s.size();for (int i 0; i < n; i) {if (!position.count(s[i])) {position[s[i]] i;…

设备管理与文件系统

1、设备管理框架 对于不同类型的设备的操作&#xff0c;全部由一下函数指针来完成。即操作系统对设备进行操作&#xff0c;只需要调用统一的API接口&#xff0c;无需了解相关的细节。 比如如下的接口设计&#xff1a; int (*open) (device_t * dev) ; int (*read) (device_t …

直播行业的未来:南昌络喆科技有限公司的创新无人直播项目!

随着数字化时代的推进&#xff0c;直播行业迎来了前所未有的增长机遇。南昌络喆科技有限公司凭借其创新的无人直播技术&#xff0c;正引领着行业的新潮流&#xff0c;展现出直播领域的新面貌。 无人直播技术突破了传统直播的局限&#xff0c;实现了自动化的高效运营模式。它摒弃…

用Python解决预测问题_对数线性模型模板

对数线性模型&#xff08;Log-linear model&#xff09;是统计学中用于分析计数数据或频率数据的一类模型&#xff0c;特别是在多维列联表&#xff08;contingency tables&#xff09;分析中非常常见。这种模型通过取对数将乘法关系转换为加法关系&#xff0c;从而简化了数据分…

关于自己部署AI大模型踩的坑(三)—— 部署

最近一直在研究如何打算属于我自己的J.A.R.V.I.S.&#xff08;钢铁侠中的机器人管家&#xff09;。 上一篇写了我最近在部署自己的大模型&#xff0c;使用llama3.1&#xff0c; 和通义千问2。虽然最终结果也是成功了&#xff0c;过程却十分地坎坷。所以这一篇文章一是总结其中遇…

Nginx快速入门:编译及常用配置

Nginx 是一个高性能的 HTTP 服务器和反向代理服务器&#xff0c;也是一个 IMAP/POP3 邮件代理服务器。它以其高并发处理能力和低资源消耗而闻名&#xff0c;能够同时处理数千个连接。 Nginx 的主要功能包括&#xff1a; 静态资源服务器&#xff1a;Nginx 可以担任静态资源服务…

【Python零基础】Python测试

文章目录 前言一、使用pip安装pytest1.1 更新pip1.2 安装pytest 二、测试函数2.1 编写测试文件2.2 运行测试2.3 测试不通过2.4 测试不通过2.4 增加新测试 三、测试类3.1 断言3.2 夹具 总结 前言 代码测试是程序开发中极其重要的一环&#xff0c;任何代码都应该经过测试才能上生…

sqli-labs靶场通关攻略(五十一到五十六关)

sqli-labs-master靶场第五十一关 步骤一&#xff0c;尝试输入?sort1 我们发现这关可以报错注入 步骤二&#xff0c;爆库名 ?sort1 and updatexml(1,concat(0x7e,database(),0x7e),1)-- 步骤三&#xff0c;爆表名 ?sort1 and updatexml(1,concat(0x7e,(select group_conc…