优于立方复杂度的 Rust 中矩阵乘法

这篇具有很好参考价值的文章主要介绍了优于立方复杂度的 Rust 中矩阵乘法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

优于立方复杂度的 Rust 中矩阵乘法,数学建模,rust,矩阵,开发语言

中途:三次矩阵乘法

一、说明

        几年前,我在 C++ 年编写了 Strassen 矩阵乘法算法的实现,最近在 Rust 中重新实现了它,因为我继续学习该语言。这是学习 Rust 性能特征和优化技术的有用练习,因为尽管 Strassen 的算法复杂性优于朴素方法,但它在算法结构中的分配和递归开销中具有很高的常数因子

  • 通用算法
  • 换位以获得更好的性能
  • 次立方:斯特拉森算法的工作原理
  • 排比
  • 标杆
  • 分析和性能优化

二、通用算法

        一般(朴素)矩阵乘法算法是每个人在他们的第一堂线性代数课上学习的三个嵌套循环方法,大多数人会将其识别为 O(n³)

pub fn 
mult_naive (a: &Matrix, b: &Matrix) -> Matrix {
    if a.rows == b.cols {
        let m = a.rows;
        let n = a.cols;

        // preallocate
        let mut c: Vec<f64> = Vec::with_capacity(m * m);

        for i in 0..m {
            for j in 0..m {
                let mut sum: f64 = 0.0;
                for k in 0..n {
                    sum += a.at(i, k) * b.at(k, j);
                }

                c.push(sum);
            }
        }

        return Matrix::with_vector(c, m, m);
    } else {
        panic!("Matrix sizes do not match");
    }
}

        这种算法很慢,不仅因为三个嵌套循环,还因为按列通过而不是按行的内部循环遍历对于 CPU 缓存命中率来说是可怕的Bb.at(k, j)

三、换位以获得更好的性能

        转置朴素方法允许 B 上的乘法迭代在行而不是列上运行,将矩阵 B 的乘法步幅重新组织为更有利于缓存的格式。从而变成A x BA x B^t

优于立方复杂度的 Rust 中矩阵乘法,数学建模,rust,矩阵,开发语言

         它涉及一个新的矩阵分配(无论如何,在这个实现中)和一个完整的矩阵迭代(一个 O(n²) 操作,更准确地说,这种方法是 O(n³) + O(n²))——我将进一步展示它的性能有多好。它如下所示:

fn multiply_transpose (A: Matrix, B: Matrix):
  C = new Matrix(A.num_rows, B.num_cols)

  // Construct transpose; requires allocation and iteration through B
  B’ = B.transpose()

  for i in 0 to A.num_rows:
    for j in 0 to B'.num_rows:
      sum = 0;
      for k in 0 to A.num_cols:
        // Sequential access of B'[j, k] is much faster than B[k, j]
        sum += A[i, k] * B'[j, k]
      C[i, j] = sum
  return C 

四、次立方:斯特拉森算法的工作原理

        要了解 Strassen 算法的工作原理(此处为 Rust 代码),首先考虑矩阵如何用象限表示。要概念化它的外观:

优于立方复杂度的 Rust 中矩阵乘法,数学建模,rust,矩阵,开发语言

        在朴素算法中使用此象限模型,结果矩阵 C 的四个象限中的每一个都是两个子矩阵乘积的总和,总共产生 8 次乘法。

优于立方复杂度的 Rust 中矩阵乘法,数学建模,rust,矩阵,开发语言

        考虑到这八个乘法,每个乘法都在一个块矩阵上运行,其行和列跨度约为 A 和 B 大小的一半,复杂性相同:

优于立方复杂度的 Rust 中矩阵乘法,数学建模,rust,矩阵,开发语言

        斯特拉森算法定义了由这些象限组成的七个中间块矩阵

优于立方复杂度的 Rust 中矩阵乘法,数学建模,rust,矩阵,开发语言

        仅通过 7 次乘法而不是 8 次乘法计算。这些乘法可以是递归斯特拉森乘法,并可用于组成最终矩阵:

优于立方复杂度的 Rust 中矩阵乘法,数学建模,rust,矩阵,开发语言

由此产生的亚立方复杂度:

优于立方复杂度的 Rust 中矩阵乘法,数学建模,rust,矩阵,开发语言

五、排比

        中间矩阵 M1 的计算 ...M7 是一个令人尴尬的并行问题,因此也很容易检测算法的并发变体(一旦你开始理解 Rust 关于闭包的规则)。

/**
 * Execute a recursive strassen multiplication of the given vectors, 
 * from a thread contained within the provided thread pool.
 */
fn 
_par_run_strassen (a: Vec<f64>, b: Vec<f64>, 
                   m: usize, pool: &ThreadPool) 
                     -> Arc<Mutex<Option<Matrix>>> {
    let m1: Arc<Mutex<Option<Matrix>>> = Arc::new(Mutex::new(None));
    let m1_clone = Arc::clone(&m1);
     
    pool.execute(move|| { 
        // Recurse with non-parallel algorithm once we're 
        // in a working thread
        let result = mult_strassen(
            &mut Matrix::with_vector(a, m, m),
            &mut Matrix::with_vector(b, m, m)
        );
        
        *m1_clone.lock().unwrap() = Some(result);
    });

    return m1;
}

六、标杆

        我编写了一些快速的基准测试代码,该代码在不断增加的矩阵维度范围内运行四种算法中的每一种进行几次试验,并报告每种算法的平均时间。

~/code/strassen ~>> ./strassen --lower 75 --upper 100 --factor 50 --trials 2

running 50 groups of 2 trials with bounds between [75->3750, 100->5000]

x    y    nxn      naive       transpose  strassen   par_strassen
75   100  7500     0.00ms      0.00ms     1.00ms     0.00ms
150  200  30000    6.50ms      4.00ms     4.00ms     1.00ms
225  300  67500    12.50ms     9.00ms     8.50ms     2.50ms
300  400  120000   26.50ms     22.00ms    18.00ms    5.50ms
[...]
3600 4800 17280000 131445.00ms 53683.50ms 21210.50ms 5660.00ms
3675 4900 18007500 141419.00ms 58530.00ms 28291.50ms 6811.00ms
3750 5000 18750000 154941.00ms 60990.00ms 26132.00ms 6613.00ms

        然后,我通过以下方式可视化结果:pyplot

优于立方复杂度的 Rust 中矩阵乘法,数学建模,rust,矩阵,开发语言

        此图显示了矩阵从 7.5k 元素 () 到大约 19 万 () 的乘法时间。你可以看到朴素算法在计算上变得不切实际的速度有多快,在高端需要两分半钟。N x M = 75 x 100N x M = 3750 x 5000

        相比之下,Strassen 算法的扩展更平滑,并行算法计算两个 19M 个元素的矩阵的结果,而朴素算法只处理 3.6M 个元素所花费的时间。

        对我来说最有趣的是算法的性能。如前所述,缓存性能的改进(以牺牲完整矩阵副本为代价)在这些结果中得到了清楚地证明 - 即使使用与该方法渐近等效的算法也是如此。transposenaive

七、分析和性能优化

        这个文档是理解 Rust 性能基础知识的绝佳资源。在 Mac OS 上启动并运行仪器进行分析是微不足道的,这要归功于货运仪器的 Rust 指南。这是调查分配行为、CPU 热点和其他事情的绝佳工具。

优于立方复杂度的 Rust 中矩阵乘法,数学建模,rust,矩阵,开发语言

在此过程中发生了一些变化:

  • Strassen 代码通过分而治之策略递归调用自己,但是一旦矩阵达到足够小的大小,其高常数因子使其比一般矩阵算法慢。我发现这个点是大约 64 的行宽或列宽;通过提高吞吐量提高几个因素来增加此阈值2
  • 斯特拉森算法要求矩阵填充到最接近的指数 2;减少这种情况以懒惰地确保矩阵只有偶数行和列 通过减少昂贵的大分配,将吞吐量提高了大约两倍
  • 将小矩阵回退算法从 更改为 导致大约 20% 的改进naivetranspose
  • 添加和添加到 Cargo.toml 发布构建标志大约提高了 5%。有趣的是,性能持续恶化codegen-units = 1lto = "thin"lto = “true”
  • 一丝不苟地删除所有可能的副本大约提高了~10%Vec
  • 提供一些提示并删除随机访问查找中的向量边界检查,又提高了大约 20%#[inline]
    /**
     * Returns the element at (i, j). Unsafe.
     */
    #[inline]
    pub fn at (&self, i: usize, j: usize) -> f64 {
         unsafe {
            return *self.elements.get_unchecked(i * self.cols + j);
        }
    }

参考资料:文章来源地址https://www.toymoban.com/news/detail-660027.html

迈克·克维特
线性代数
算法

到了这里,关于优于立方复杂度的 Rust 中矩阵乘法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 感染法和广度优先搜索及时间复杂度分析 —— NC269999 小红走矩阵

    感染法和广度优先搜索及时间复杂度分析 —— NC269999 小红走矩阵

    题目来源: 牛客周赛 Round 36 题目如下: 题目 小红走矩阵 小红来到了一个n∗m的矩阵,她初始站在左上角,每次行走可以按“上下左右”中的一个方向走一步,但必须走到和当前格子不同的字符,也不能走到矩阵外。 小红想知道,从左上角走到右下角最少需要走多少步?

    2024年04月17日
    浏览(11)
  • 数据结构 --- 复杂度概念及计算讲解(时间复杂度,空间复杂度)

    数据结构 --- 复杂度概念及计算讲解(时间复杂度,空间复杂度)

    今天没有sao话,今天认真学习 前言: 经常刷题的人都知道,我们在解决一道题时可能有多个解法,那么如何判断那个解法才是最优解呢? 我们通常从代码的两个方面进行判断:1.时间 2.空间。 –❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀

    2024年03月22日
    浏览(17)
  • 时间复杂度和空间复杂度

    时间复杂度和空间复杂度是用来评估算法性能的两个重要指标。 时间复杂度(Time Complexity)是衡量算法执行时间随输入规模增长而增长的度量。它表示了算法解决问题所需的时间量级。常见的时间复杂度有: 常数时间复杂度 O(1):无论输入规模的大小,算法的执行时间都是固

    2024年01月17日
    浏览(10)
  • 数据结构-初识复杂度以及如何计算时间复杂度和空间复杂度(详细)

    数据结构-初识复杂度以及如何计算时间复杂度和空间复杂度(详细)

    🌸🌸从今天开始将持续更新数据结构的相关知识点~ 🌸首先,从复杂度开始~ 什么是复杂度呢? 从字面来看就是说复杂的程度,我们需要具备一种工具可以评估某种算法(程序)的好坏,比如运行时间、占用空间等等。 复杂度具体体现在三个方面: 1.算法 2.数据规模 3.输入

    2024年01月16日
    浏览(12)
  • 算法的时间复杂度与空间复杂度

    算法的时间复杂度与空间复杂度

    1.算法效率 2.时间复杂度 3.空间复杂度 4.复杂度oj题目 1.算法效率 1.1 如何衡量一个算法的好坏 一辆车的好坏我们可以从价格,油耗...... 方面来衡量,但衡量一个算法的好坏我们该从哪一个方面入手呢?比如斐波那契数列: 斐波那契数列的递归实现方式非常简洁,但简洁一定

    2024年02月15日
    浏览(13)
  • 【算法基础】时间复杂度和空间复杂度

    【算法基础】时间复杂度和空间复杂度

    1 算法的评价 2 算法复杂度 2.1 时间复杂度(Time Complexity) 2.1.1 如何计算时间复杂度: 2.1.2 常见的时间复杂度类别与示例 2.2 空间复杂度 2.2.1 如何计算空间复杂度 2.2.2 常见的空间复杂度与示例 3 时间复杂度和空间复杂度计算示例 例子1:计算数组中所有元素的和。 例子2:快

    2024年02月08日
    浏览(16)
  • 算法之【时间复杂度】与【空间复杂度】

    算法之【时间复杂度】与【空间复杂度】

    目录 一、算法 1、算法定义 2、两种算法的比较 3、算法的特性 4、算法设计的要求 二、算法的复杂度 1、时间复杂度 1.1定义 1.2大O的渐近表示法 1.3推导大O阶方法 1.4最坏情况与平均情况 1.5常见的时间复杂度计算示例 🍂常数阶: 🍂线性阶:  🍂对数阶: 🍂平方阶: 2、空间

    2024年02月05日
    浏览(17)
  • 算法的时间复杂度和空间复杂度

    算法的时间复杂度和空间复杂度

    目录 本章重点 一 时间复杂度 2.1 时间复杂度的概念 2.2 大O的渐进表示法 2.3 常见的时间复杂度的计算 二 空间复杂度 三 常见复杂度对比 四 复杂度的oj练习 4.1 消失的数字 4.2 旋转数字 每一天都是人生限定,每一天都值得100%努力 (1)算法效率(2)时间复杂度(3)空间复

    2024年02月01日
    浏览(9)
  • 什么是时间复杂度和空间复杂度

    什么是时间复杂度和空间复杂度

    🍕博客主页:️自信不孤单 🍬文章专栏:数据结构与算法 🍚代码仓库:破浪晓梦 🍭欢迎关注:欢迎大家点赞收藏+关注 数据结构(Data Structure)是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合。 算法(Algorithm):就是定义良好的计算过程

    2023年04月15日
    浏览(11)
  • 数据结构(时间复杂度,空间复杂度)

    数据结构(时间复杂度,空间复杂度)

    算法的时间复杂度是一个数学函数,算法中的基本操作的执行次数,为算法的时间复杂度。 1.大O的表示法 2.推导大O表示法 1、用常数1取代运行时间中的所有加法常数。 2、在修改后的运行次数函数中,只保留最高阶项。 3、如果最高阶项存在且不是1,则去除与这个项目相乘的

    2024年02月07日
    浏览(12)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包