为什么这些矩阵乘法的性能如此不同?

2024-01-04

我用 Java 编写了两个矩阵类,只是为了比较它们的矩阵乘法的性能。一个类(Mat1)存储一个double[][] A成员所在行i矩阵的值为A[i]。其他类(Mat2)存储A and T where T是转置A.

假设我们有一个方阵 M,我们想要的乘积M.mult(M)。致电产品P.

当 M 是 Mat1 实例时,使用的算法是简单的:

P[i][j] += M.A[i][k] * M.A[k][j]
    for k in range(0, M.A.length)

在 M 是 Mat2 的情况下,我使用:

P[i][j] += M.A[i][k] * M.T[j][k]

这是相同的算法,因为T[j][k]==A[k][j]。在 1000x1000 矩阵上,第二个算法在我的机器上大约需要 1.2 秒,而第一个算法至少需要 25 秒。我原以为第二个会更快,但没想到这么快。问题是,为什么速度这么快?

我唯一的猜测是,第二个算法更好地利用了 CPU 缓存,因为数据以大于 1 个字的块的形式被拉入缓存,而第二个算法通过仅遍历行而受益,而第一个算法则忽略拉入的数据通过立即转到下面的行(内存中约 1000 个字,因为数组按行主要顺序存储)来进行缓存,没有缓存任何数据。

我问过某人,他认为这是因为更友好的内存访问模式(即第二个版本会导致更少的 TLB 软故障)。我根本没有想到这一点,但我可以看出它是如何减少 TLB 错误的。

那么,是哪一个呢?或者还有其他原因导致性能差异?


这是因为您的数据的位置。

在 RAM 中,矩阵虽然从您的角度来看是二维的,但它当然存储为连续的字节数组。与一维数组的唯一区别是偏移量是通过对您使用的两个索引进行插值来计算的。

这意味着如果您访问位置处的元素x,y它会计算x*row_length + y这将是用于引用指定位置处的元素的偏移量。

发生的情况是,一个大矩阵不仅仅存储在内存页面中(这就是操作系统管理 RAM 的方式,通过将其分割成块),因此如果您尝试访问某个内存页面,它必须在 CPU 缓存内加载正确的页面。尚不存在的元素。

只要您连续进行乘法,就不会产生任何问题,因为您主要使用一页的所有系数,然后切换到下一页,但如果您反转索引,会发生的情况是每个元素都可能包含在不同的内存页面,因此每次它都需要向 RAM 请求不同的页面,这几乎对于您执行的每一次乘法都是如此,这就是差异如此巧妙的原因。

(我相当简化了整个解释,只是为了给您围绕这个问题的基本想法)

无论如何,我不认为这是 JVM 本身造成的。它可能与您的操作系统如何管理 Java 进程的内存有关。

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

为什么这些矩阵乘法的性能如此不同? 的相关文章

  • 将 jar 作为 Linux 服务运行 - init.d 脚本在启动应用程序时卡住

    我目前正在致力于在 Linux VM 上实现一个可运行的 jar 作为后台服务 我已经使用了找到的例子here https gist github com shirish4you 5089019作为工作的基础 并将 start 方法修改为
  • 禁用 Eclipse Java 调试器的热代码替换 [重复]

    这个问题在这里已经有答案了 可能的重复 如何在 Eclipse 中禁用热代码替换 https stackoverflow com questions 2594408 how do i disable hot code replace in
  • Grails 2.3.0 自动重新加载不起作用

    我最近将我们的项目升级到 grails 2 3 0 一切工作正常 除了每当我更改代码时自动重新加载都无法工作的问题 这包括所有项目工件 控制器 域 服务 gsps css 和 javascript 文件 我的旧版本 grails 可以正常工
  • 无法使用maven编译java项目

    我正在尝试在 java 16 0 1 上使用 maven 构建 IntelliJ 项目 但它无法编译我的项目 尽管 IntelliJ 能够成功完成 在此之前 我使用maven编译了一个java 15项目 但我决定将所有内容更新到16 0 1
  • 为什么 java 编译器不报告 Intellij 中多播表达式的未经检查的强制转换警告?

    为什么下面的代码没有报告 Intellij IDEA 的未经检查的警告jdk 1 8 0 121自从Supplier
  • C++ Exp 与 Log:哪个更快?

    我有一个 C 应用程序 需要比较两个值并决定哪个值更大 唯一的复杂之处是一个数字在对数空间中表示 而另一个则不是 例如 double log num 1 log 1 23 double num 2 1 24 如果我想比较num 1 and
  • Java套接字:在连接被拒绝异常时重试的最佳方法?

    现在我正在这样做 while true try SocketAddress sockaddr new InetSocketAddress ivDestIP ivDestPort downloadSock new Socket downloa
  • 无法在 Spring Boot 测试中模拟 persistenceContext

    我正在使用带有 Mockito 框架的 spring boot 测试来测试我的应用程序 存储库类 EntityManager 之一作为参考 我的班级如下所示 Repository Transactional Slf4j public cla
  • Firestore - RecycleView - 图像持有者

    我不知道如何编写图像的支架 我已经设置了 2 个文本 但我不知道图像的支架应该是什么样子 你能帮我告诉我图像的文字应该是什么样子才能正确显示吗 holder artistImage setImageResource model getArt
  • 主线程如何在该线程之前运行?

    我有以下代码 public class Derived implements Runnable private int num public synchronized void setA int num try Thread sleep 1
  • 在java中实现你自己的阻塞队列

    我知道这个问题之前已经被问过并回答过很多次了 但我只是无法根据互联网上找到的示例找出窍门 例如this http tutorials jenkov com java concurrency blocking queues html or t
  • Java 服务器-客户端 readLine() 方法

    我有一个客户端类和一个服务器类 如果客户端向服务器发送消息 服务器会将响应发送回客户端 然后客户端将打印它收到的所有消息 例如 如果客户端向服务器发送 A 则服务器将向客户端发送响应 1111 所以我在客户端类中使用 readLine 从服
  • 将表值参数与 SQL Server JDBC 结合使用

    任何人都可以提供一些有关如何将表值参数 TVP 与 SQL Server JDBC 一起使用的指导吗 我使用的是微软提供的6 0版本的SQL Server驱动程序 我已经查看了官方文档 https msdn microsoft com en
  • React Native:加载图像后应用程序性能不佳

    加载图像似乎没有问题 但是加载完毕后就出现问题了 在我的应用程序中 我在整个游戏中一张一张地加载卡片图像 一旦我加载了 40 张卡片图像 整个应用程序就会变得很慢 它总是发生在第 40 个图像处 当我在第 40 个图像之后继续加载更多卡片图
  • 列表应该如何转换为具体的实现?

    假设我正在使用一个我不知道源代码的库 它有一个返回列表的方法 如下所示 public List
  • Java 数组的最大维数

    出于好奇 在 Java 中数组可以有多少维 爪哇language不限制维数 但是JavaVM规范将维度数限制为 255 例如 以下代码将无法编译 class Main public static void main String args
  • 如何在 Quartz 调度程序中每 25 秒运行一次?

    我正在使用 Java 的 Quartz Scheduling API 你能帮我使用 cron 表达式每 25 秒运行一次吗 这只是一个延迟 它不必总是从第 0 秒开始 例如 序列如下 0 00 0 25 0 50 1 15 1 40 2 0
  • 如何在Java中正确删除数组[重复]

    这个问题在这里已经有答案了 我刚接触 Java 4 天 从我搜索过的教程来看 讲师们花费了大量精力来解释如何分配二维数组 例如 如下所示 Foo fooArray new Foo 2 3 但我还没有找到任何解释如何删除它们的信息 从内存的情
  • 嵌入式 Jetty - 以编程方式添加基于表单的身份验证

    有没有一种方法可以按如下方式以编程方式添加基于表单的身份验证 我用的是我自己的LdapLoginModule 最初我使用基本身份验证并且工作正常 但现在我想在登录页面上进行更多控制 例如显示徽标等 有没有好的样品 我正在使用嵌入式 jett
  • Android 和 Java 中绘制椭圆的区别

    在Java中由于某种原因Ellipse2D Double使用参数 height width x y 当我创建一个RectF在Android中参数是 left top right bottom 所以我对适应差异有点困惑 如果在 Java 中创

随机推荐