使用 SSE 的矩阵向量和矩阵矩阵乘法

2023-12-04

我需要编写矩阵-向量和矩阵-矩阵乘法函数,但我无法理解 SSE 命令。

矩阵和向量的维数始终是 4 的倍数。

我设法编写了向量-向量乘法函数,如下所示:

void vector_multiplication_SSE(float* m, float* n, float* result, unsigned const int size)
{
    int i;

    __declspec(align(16))__m128 *p_m = (__m128*)m;
    __declspec(align(16))__m128 *p_n = (__m128*)n;
    __declspec(align(16))__m128 *p_result = (__m128*)result;

    for (i = 0; i < size / 4; ++i)
        p_result[i] = _mm_mul_ps(p_m[i], p_n[i]);

    // print the result
    for (int i = 0; i < size; ++i)
    {
        if (i % 4 == 0) cout << endl;
        cout << result[i] << '\t';
    }
}

现在我正在尝试实现矩阵向量乘法。

这是我到目前为止所拥有的:

void multiply_matrix_by_vector_SSE(float* m, float* v, float* result, unsigned const int vector_dims)
{
    int i, j;

    __declspec(align(16))__m128 *p_m = (__m128*)m;
    __declspec(align(16))__m128 *p_v = (__m128*)v;
    __declspec(align(16))__m128 *p_result = (__m128*)result;

    for (i = 0; i < vector_dims; i += 4)
    {
        __m128 tmp = _mm_load_ps(&result[i]);
        __m128 p_m_tmp = _mm_load_ps(&m[i]);

        tmp = _mm_add_ps(tmp, _mm_mul_ps(tmp, p_m_tmp));
        _mm_store_ps(&result[i], tmp);

        // another for loop here? 
    }

    // print the result
    for (int i = 0; i < vector_dims; ++i)
    {
        if (i % 4 == 0) cout << endl;
        cout << result[i] << '\t';
    }
}

这个函数看起来完全错误。我的意思是,它不仅无法正常工作,而且似乎我正朝着错误的方向前进。


谁能帮助我实现向量矩阵和矩阵矩阵乘法?我真的很感激一些示例代码和非常详细的解释

Update

这是我的第二次尝试:

它失败了Access reading violation例外但仍然感觉更接近

void multiply_matrix_by_vector_SSE(float* m, float* v, float* result, unsigned const int vector_dims)
{
    int i, j;

    __declspec(align(16))__m128 *p_m = (__m128*)m;
    __declspec(align(16))__m128 *p_v = (__m128*)v;
    __declspec(align(16))__m128 *p_result = (__m128*)result;

    for (i = 0; i < vector_dims; ++i)
    {
        p_result[i] = _mm_mul_ps(_mm_load_ps(&m[i]), _mm_load_ps1(&v[i]));
    }

    // print the result
    for (int i = 0; i < vector_dims; ++i)
    {
        if (i % 4 == 0) cout << endl;
        cout << result[i] << '\t';
    }
}

Update 2

void multiply_matrix_by_vector_SSE(float* m, float* v, float* result, unsigned const int vector_dims)
{
    int i, j;
    __declspec(align(16))__m128 *p_m = (__m128*)m;
    __declspec(align(16))__m128 *p_v = (__m128*)v;
    __declspec(align(16))__m128 *p_result = (__m128*)result;

    for (i = 0; i < vector_dims; ++i)
    {
        for (j = 0; j < vector_dims * vector_dims / 4; ++j)
        {
            p_result[i] = _mm_mul_ps(p_v[i], p_m[j]);
        }
    }

    for (int i = 0; i < vector_dims; ++i)
    {
        if (i % 4 == 0) cout << endl;
        cout << result[i] << '\t';
    }
    cout << endl;
}

没有任何技巧或任何东西,矩阵向量乘法只是向量和矩阵行之间的一堆点积。您的代码实际上并不具有该结构。实际上将其写为点积(未测试):

for (int row = 0; row < nrows; ++row) {
    __m128 acc = _mm_setzero_ps();
    // I'm just going to assume the number of columns is a multiple of 4
    for (int col = 0; col < ncols; col += 4) {
        __m128 vec = _mm_load_ps(&v[col]);
        // don't forget it's a matrix, do 2d addressing
        __m128 mat = _mm_load_ps(&m[col + ncols * row]);
        acc = _mm_add_ps(acc, _mm_mul_ps(mat, vec));
    }
    // now we have 4 floats in acc and they have to be summed
    // can use two horizontal adds for this, they kind of suck but this
    // isn't the inner loop anyway.
    acc = _mm_hadd_ps(acc, acc);
    acc = _mm_hadd_ps(acc, acc);
    // store result, which is a single float
    _mm_store_ss(&result[row], acc);
}

有一些明显的技巧,例如一次处理多行、重用向量中的负载以及创建多个独立的依赖链,以便您可以更好地利用吞吐量(见下文)。另外一个非常简单的技巧是使用 FMA 进行 mul/add 组合,但支持还没有那么广泛(2015 年还没有,但现在到 2020 年已经相当广泛了)。

您可以由此构建矩阵-矩阵乘法(如果您更改结果所在的位置),但这不是最佳的(请参阅下文)。


一次取四行(未测试):

for (int row = 0; row < nrows; row += 4) {
    __m128 acc0 = _mm_setzero_ps();
    __m128 acc1 = _mm_setzero_ps();
    __m128 acc2 = _mm_setzero_ps();
    __m128 acc3 = _mm_setzero_ps();
    for (int col = 0; col < ncols; col += 4) {
        __m128 vec = _mm_load_ps(&v[col]);
        __m128 mat0 = _mm_load_ps(&m[col + ncols * row]);
        __m128 mat1 = _mm_load_ps(&m[col + ncols * (row + 1)]);
        __m128 mat2 = _mm_load_ps(&m[col + ncols * (row + 2)]);
        __m128 mat3 = _mm_load_ps(&m[col + ncols * (row + 3)]);
        acc0 = _mm_add_ps(acc0, _mm_mul_ps(mat0, vec));
        acc1 = _mm_add_ps(acc1, _mm_mul_ps(mat1, vec));
        acc2 = _mm_add_ps(acc2, _mm_mul_ps(mat2, vec));
        acc3 = _mm_add_ps(acc3, _mm_mul_ps(mat3, vec));
    }
    acc0 = _mm_hadd_ps(acc0, acc1);
    acc2 = _mm_hadd_ps(acc2, acc3);
    acc0 = _mm_hadd_ps(acc0, acc2);
    _mm_store_ps(&result[row], acc0);
}

现在,每 4 个 FMA 只有 5 个负载,而在未行展开的版本中,每 1 个 FMA 有 2 个负载。此外,还有 4 个独立的 FMA,或无 FMA 收缩的加法/乘法对,无论哪种方式,它都增加了流水线/同时执行的潜力。实际上,您可能想要展开更多,例如 Skylake 每个周期可以启动 2 个独立的 FMA,它们需要 4 个周期才能完成,因此要完全占用两个 FMA 单元,您需要 8 个独立的 FMA。作为奖励,这 3 个水平相加最终对于水平求和来说效果相对较好。


不同的数据布局最初似乎是一个缺点,不再可能简单地从矩阵和向量进行向量加载并将它们相乘(这会将第一个矩阵的微小行向量乘以一个微小的行向量)row再次是第二个矩阵的向量,这是错误的)。但是,完整的矩阵-矩阵乘法可以利用这样一个事实:它本质上是将一个矩阵乘以许多独立的向量,它充满了需要完成的独立工作。水平总和也可以轻松避免。所以实际上它比矩阵向量乘法更方便。

关键是从矩阵 A 中取出一点列向量,从矩阵 B 中取出一点行向量,然后将它们相乘成一个小矩阵。与您习惯的方式相比,这听起来可能是相反的,但使用 SIMD 这样做效果更好,因为计算始终保持独立且无需水平操作。

例如(未经测试,假设矩阵的维度可被展开因子整除,需要 x64,否则会耗尽寄存器)

for (size_t i = 0; i < mat1rows; i += 4) {
    for (size_t j = 0; j < mat2cols; j += 8) {
        float* mat1ptr = &mat1[i * mat1cols];
        __m256 sumA_1, sumB_1, sumA_2, sumB_2, sumA_3, sumB_3, sumA_4, sumB_4;
        sumA_1 = _mm_setzero_ps();
        sumB_1 = _mm_setzero_ps();
        sumA_2 = _mm_setzero_ps();
        sumB_2 = _mm_setzero_ps();
        sumA_3 = _mm_setzero_ps();
        sumB_3 = _mm_setzero_ps();
        sumA_4 = _mm_setzero_ps();
        sumB_4 = _mm_setzero_ps();

        for (size_t k = 0; k < mat2rows; ++k) {
            auto bc_mat1_1 = _mm_set1_ps(mat1ptr[0]);
            auto vecA_mat2 = _mm_load_ps(mat2 + m2idx);
            auto vecB_mat2 = _mm_load_ps(mat2 + m2idx + 4);
            sumA_1 = _mm_add_ps(_mm_mul_ps(bc_mat1_1, vecA_mat2), sumA_1);
            sumB_1 = _mm_add_ps(_mm_mul_ps(bc_mat1_1, vecB_mat2), sumB_1);
            auto bc_mat1_2 = _mm_set1_ps(mat1ptr[N]);
            sumA_2 = _mm_add_ps(_mm_mul_ps(bc_mat1_2, vecA_mat2), sumA_2);
            sumB_2 = _mm_add_ps(_mm_mul_ps(bc_mat1_2, vecB_mat2), sumB_2);
            auto bc_mat1_3 = _mm_set1_ps(mat1ptr[N * 2]);
            sumA_3 = _mm_add_ps(_mm_mul_ps(bc_mat1_3, vecA_mat2), sumA_3);
            sumB_3 = _mm_add_ps(_mm_mul_ps(bc_mat1_3, vecB_mat2), sumB_3);
            auto bc_mat1_4 = _mm_set1_ps(mat1ptr[N * 3]);
            sumA_4 = _mm_add_ps(_mm_mul_ps(bc_mat1_4, vecA_mat2), sumA_4);
            sumB_4 = _mm_add_ps(_mm_mul_ps(bc_mat1_4, vecB_mat2), sumB_4);
            m2idx += 8;
            mat1ptr++;
        }
        _mm_store_ps(&result[i * mat2cols + j], sumA_1);
        _mm_store_ps(&result[i * mat2cols + j + 4], sumB_1);
        _mm_store_ps(&result[(i + 1) * mat2cols + j], sumA_2);
        _mm_store_ps(&result[(i + 1) * mat2cols + j + 4], sumB_2);
        _mm_store_ps(&result[(i + 2) * mat2cols + j], sumA_3);
        _mm_store_ps(&result[(i + 2) * mat2cols + j + 4], sumB_3);
        _mm_store_ps(&result[(i + 3) * mat2cols + j], sumA_4);
        _mm_store_ps(&result[(i + 3) * mat2cols + j + 4], sumB_4);
    }
}

该代码的要点在于,很容易将计算安排得非常 SIMD 友好,使用大量独立算术来使浮点单元饱和,同时使用相对较少的负载(否则可能会成为瓶颈) ,即使不考虑它们可能会错过 L1 缓存,只是因为它们太多了)。

您甚至可以使用此代码,但它与英特尔 MKL 没有竞争力。特别是对于中型或大型矩阵,平铺极其重要。升级到 AVX 很容易。它根本不适合微小矩阵,例如将两个 4x4 矩阵相乘,请参见高效的 4x4 矩阵乘法.

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

使用 SSE 的矩阵向量和矩阵矩阵乘法 的相关文章

  • 计算 XML 中特定 XML 节点的数量

    请参阅此 XML
  • 适合初学者的良好调试器教程[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 有谁知道一个好的初学者教程 在 C 中使用调试器 我感觉自己好像错过了很多 我知道怎么做 单步执行代码并查看局部变量 虽然这常常给我带来问
  • 查找进程的完整路径

    我已经编写了 C 控制台应用程序 当我启动应用程序时 不使用cmd 我可以看到它列在任务管理器的进程列表中 现在我需要编写另一个应用程序 在其中我需要查找以前的应用程序是否正在运行 我知道应用程序名称和路径 所以我已将管理对象搜索器查询写入
  • 如何使用 Castle Windsor 将对象注入到 WCF IErrorHandler 实现中?

    我正在使用 WCF 开发一组服务 该应用程序正在使用 Castle Windsor 进行依赖注入 我添加了一个IErrorHandler通过属性添加到服务的实现 到目前为止一切正常 这IErrorHandler对象 一个名为FaultHan
  • 当一组凭据下的计划任务启动的进程在另一组凭据下运行另一个程序时,Windows 是否有限制

    所以我有一个简单的例子 其中我有应用程序 A 它对用户 X 本地管理员 有一些硬编码的凭据 然后它使用硬编码的绝对路径启动带有这些凭据的应用程序 B A 和 B 以及 dotnet 控制台应用程序 但是它们不与控制台交互 只是将信息写入文件
  • 从同一个类中的另一个构造函数调用构造函数

    我有一个带有两个构造函数的类 C 这是代码片段 public class FooBar public FooBar string s constructor 1 some functionality public FooBar int i
  • 使用可变参数包类型扩展的 C++ 函数调用者包装器

    我绑定了一些 API 并且绑定了一些函数签名 如下所示 static bool WrapperFunction JSContext cx unsigned argc JS Value vp 我尝试将对象和函数包装在 SpiderMonkey
  • unordered_map 中字符串的 C++ 哈希函数

    看起来 C 标准库中没有字符串的哈希函数 这是真的 在任何 c 编译器上使用字符串作为 unordered map 中的键的工作示例是什么 C STL提供模板专业化 http en cppreference com w cpp string
  • File.AppendText 尝试写入错误的位置

    我有一个 C 控制台应用程序 它作为 Windows 任务计划程序中的计划任务运行 此控制台应用程序写入日志文件 该日志文件在调试模式下运行时会创建并写入应用程序文件夹本身内的文件 但是 当它在任务计划程序中运行时 它会抛出一个错误 指出访
  • 在Linux中,找不到框架“.NETFramework,Version=v4.5”的参考程序集

    我已经设置了 Visual studio 来在我的 Ubuntu 机器上编译 C 代码 我将工作区 我的代码加载到 VS 我可以看到以下错误 The reference assemblies for framework NETFramewo
  • 在视口中查找 WPF 控件

    Updated 这可能是一个简单或复杂的问题 但在 wpf 中 我有一个列表框 我用一个填充数据模板从列表中 有没有办法找出特定的数据模板项位于视口中 即我已滚动到其位置并且可以查看 目前我连接到了 listbox ScrollChange
  • 为什么这个二维指针表示法有效,而另一个则无效[重复]

    这个问题在这里已经有答案了 这里我编写了一段代码来打印 3x3 矩阵的对角线值之和 这里我必须将矩阵传递给函数 矩阵被传递给指针数组 代码可以工作 但问题是我必须编写参数的方式如下 int mat 3 以下导致程序崩溃 int mat 3
  • C++ int 前面加 0 会改变整个值

    我有一个非常奇怪的问题 如果我像这样声明一个 int int time 0110 然后将其显示到控制台返回的值为72 但是当我删除前面的 0 时int time 110 然后控制台显示110正如预期的那样 我想知道两件事 首先 为什么它在
  • 检测到严重错误 c0000374 - C++ dll 将已分配内存的指针返回到 C#

    我有一个 c dll 它为我的主 c 应用程序提供一些功能 在这里 我尝试读取一个文件 将其加载到内存 然后返回一些信息 例如加载数据的指针和内存块的计数到 c Dll 成功将文件读取到内存 但在返回主应用程序时 程序由于堆损坏而崩溃 检测
  • 在屏幕上获取字符

    我浏览了 NCurses 函数列表 似乎找不到返回已打印在屏幕上的字符的函数 每个字符单元格中存储的字符是否有可访问的值 如果没有的话Windows终端有类似的功能吗 我想用它来替换屏幕上某个值的所有字符 例如 所有a s 具有不同的特征
  • WebBrowser.Print() 等待完成。 。网

    我在 VB NET 中使用 WebBrowser 控件并调用 Print 方法 我正在使用 PDF 打印机进行打印 当调用 Print 时 它不会立即启动 它会等到完成整个子或块的运行代码 我需要确保我正在打印的文件也完整并继续处理该文件
  • String.Empty 与 "" [重复]

    这个问题在这里已经有答案了 可能的重复 String Empty 和 有什么区别 https stackoverflow com questions 151472 what is the difference between string
  • OpenGL:仅获取模板缓冲区而没有深度缓冲区?

    我想获取一个模板缓冲区 但如果可能的话 不要承受附加深度缓冲区的开销 因为我不会使用它 我发现的大多数资源表明 虽然模板缓冲区是可选的 例如 排除它以利于获得更高的深度缓冲区精度 但我还没有看到任何请求并成功获取仅 8 位模板缓冲区的代码
  • 是否可以在不连接数据库的情况下检索 MetadataWorkspace?

    我正在编写一个需要遍历实体框架的测试库MetadataWorkspace对于给定的DbContext类型 但是 由于这是一个测试库 我宁愿不连接到数据库 它引入了测试环境中可能无法使用的依赖项 当我尝试获取参考时MetadataWorksp
  • 如何使用 C++11 using 语法键入定义函数指针?

    我想写这个 typedef void FunctionPtr using using 我该怎么做呢 它具有类似的语法 只不过您从指针中删除了标识符 using FunctionPtr void 这是一个Example http ideone

随机推荐

  • Go调度器什么时候会创建新的M和P?

    刚刚学习了golang GMP模型 现在我了解了goroutines 操作系统线程和golang上下文 处理器如何相互协作 但我还是不明白什么时候会产生M和P 例如 我有一个测试代码来在数据库上运行一些操作 并且有两个测试用例 两批 gor
  • bash $* 的 Powershell 等效项是什么?

    换句话说 我怎样才能获得脚本本身的命令行 所以 我知道 PSBoundParameters 但并不相同 我只想按原样获取包含传入参数的字符串 我该怎么做 See get help about Automatic Variables Args
  • 错误:系列的真值不明确。蟒蛇和熊猫

    我正在尝试识别当天交易量超过 10 000 份的 MSFT 和 GOOG 的所有期权合约 并打印出交易品种的名称 我收到错误 一系列的真值不明确 使用 empty a bool a item a any 或 a all 错误出现在第 13
  • 在 R 中重新缩放变量

    我有一个名为 Esteem 的变量 其比例为 1 7 我想将其重新调整为 1 100 我知道 R 程序可以做到这一点 但是我在语法上遇到了问题 有人可以提供一个如何重新调整该变量的示例吗 另外 我可以在 R Commander 中使用一个工
  • Python-如何验证字符串是否以特定字符串结尾?

    例如我有以下字符串 24499 00 02 05 sys yg ys 如何验证字符串是否以从函数结果中获得的字符串结尾 例如sys yg ys 我在上面的字符串上尝试了以下操作 只是为了检查简单的情况 结果 if line endswith
  • 如何更改 ASP.NET MVC 中的默认视图位置方案?

    我想根据当前的 UI 文化在运行时更改视图位置 如何使用默认的 Web 表单视图引擎实现此目的 基本上我想知道如何实施WebFormViewEngine某事是什么自定义 IDescriptorFilter in Spark 是否有其他视图引
  • 将二进制数转换为 Base 64

    我知道这是一个很愚蠢的问题 但我不知道该怎么办 我有一个任意的二进制数 比如说 10010000001100100000001001000000100000110000000100010000010110001100001100000111
  • Google Drive api:范围“drive.file”和“drive.readonly”的复制错误

    我的问题是 如果您只有范围 drive file 和 drive readonly 是否无法使用 google Drive api 将文件从驱动器中的一个文件夹复制到驱动器中的另一个文件夹 使用 API 浏览器进行测试 https deve
  • PhoneGap:如何获取 appView 的 id 并将其传递?

    对于PhoneGap应用程序 正如说明所述 我已经替换了setContentView 与super loadUrl file android asset www index html 下一行是appView addJavascriptInt
  • 如何将数组的前一个字符串与下一个字符串连接起来?

    我很难理解这个问题 但假设有一个包含这些元素的数组 apple banana pear kiwi orange 我想将此数组转移到 apple apple banana apple banana pear apple banana pear
  • 如何在java上进行mysqldump?

    我在 mySQL 中创建了数据库 并使用 mysqldump 将其导出到文件中 有没有办法让我的 JAVA 程序连接到 mysql 并使用我保存在文件中的结构创建一个空数据库 前提是上述数据库尚未存在于服务器中 谢谢你 尝试类似的方法 Ru
  • 如何嵌套 PHP 代码块

    这段代码被破坏了 因为我正在嵌套 php 代码块 执行此操作的正确方法是什么 gt
  • 在 Meteor 中,我如何在客户端知道服务器端操作何时完成?

    我知道 Meteor 对数据库进行客户端缓存 以获得更好的有效性能 在客户端Meteor方法调用中 有没有办法知道什么时候服务器端数据库操作actually完成 或者如果它实际上失败了 当完整的远程过程调用完成时 我是否可以挂钩一些事件来获
  • 使用 AWS ECS Fargate 进行水平和垂直自动扩展

    我这里有一个具体的用例 我需要自动扩展在 ECS Fargate 上运行的分布式 Web 应用程序 问题是所有节点都需要在内存中保存相同的数据 因此增加节点数量无助于缓解内存压力 因此 只有水平扩展 添加节点 和垂直扩展 增加节点内存 才能
  • LinkedIn inShare 插件共享计数器返回零

    我有一个 WordPress 博客 http bloculus com 我使用 Super Socializer 插件来分享我的帖子 最近 我发现我失去了所有 LinkedIn 分享计数 在每个帖子中 它都回到了 0 我联系了插件作者 我们
  • 使用 CSS 制作脉动环动画

    我想要一个从中心开始的扩展半径div而不是从左上角开始div 想象一下按钮有一个向外的脉动轮廓 那脉动的轮廓应该从中间开始div然后出去 请参阅此处的示例 https jsbin com dinehoqaro edit html css 输
  • IcmpSendEcho2 失败并显示 WSA_QOS_ADMISSION_FAILURE 和 ERROR_NOACCESS

    我有一个应用程序可以 ping 一堆服务器 它运行了好几天 但突然会出现以下两种类型之一的许多故障 WSA QOS ADMISSION FAILURE 11010 由于缺乏资源而发生 QoS 错误 or ERROR NOACCESS 998
  • 在matlab中绘制一个包含许多子图的大图

    我必须打印一张大海报 其中包含数字矩阵 让 MATLAB 排列它们对我来说非常实用 不幸的是 子图是为了适应特定的图形尺寸而显示的 因此很小且扭曲 我不想适应人物尺寸 而是想适应海报的纸张尺寸 我尝试过set gcf Position 并且
  • Delphi 皮肤库

    我想知道最适合您的 Delphi 应用程序皮肤库是什么 我正在寻找 WinXP Windows Vista Windows 7 兼容性 这样应用程序就不会因为皮肤而崩溃或工作异常 我尝试过主题引擎 但它在 Windows Vista 中运行
  • 使用 SSE 的矩阵向量和矩阵矩阵乘法

    我需要编写矩阵 向量和矩阵 矩阵乘法函数 但我无法理解 SSE 命令 矩阵和向量的维数始终是 4 的倍数 我设法编写了向量 向量乘法函数 如下所示 void vector multiplication SSE float m float n