在 N 体模拟中进行较少计算时程序运行速度较慢?

以撒

我正在编写一个带有粒子-粒子相互作用的简单 N 体模拟。我注意到当我计算粒子之间的相对位置时,我的代码在执行较少的计算时实际上运行得更慢。

起初我尝试了直接的实现(为简单起见,假设是一维的):

for(int i=0; i<N; i++)
{
    for(int j=0; j<N; j++)
   {
       r_rel[i][j] = r[i]-r[j];
   }
}

这就像填充 NxN 矩阵一样。这些循环对每个 r_rel 计算两次,事实上,r_rel[i][j] = -r_rel[j][i]. 因此,我尝试保留一些实现以下解决方案的计算:

for(int i=1; i<N; i++)
{
    for(int j=0; j<i; j++)
   {
       r_rel[i][j] = r[i]-r[j];
       r_rel[j][i] = -r_rel[i][j];

   }
}

这样我实际上只计算了我的 NxN 相对位置矩阵中对角线下方的项。我预计代码会更快,因为它执行的计算较少,但是当我执行时,它的运行速度明显变慢。这怎么可能?谢谢!!

埃里克·波斯皮希尔

第一个循环r_rel以连续的内存顺序遍历,在继续下一行之前遍历每一行:它r_rel[i][j]在迭代j之前的每个值的同时访问i

第二个循环遍历r_rel两个移动的访问点,一个以连续的内存顺序进行,然后通过矩阵的列进行,跨行跳转。后一种行为对缓存不利并且性能不佳。众所周知,沿列遍历行主数组对缓存性能不利。

缓存是昂贵的高性能内存,用于保存最近访问的数据或从内存中加载的数据的副本,以便在不久的将来使用。当程序以经常访问高速缓存中数据的方式使用内存时,它可能会受益于高速缓存的高性能。当程序经常访问不在缓存中的数据时,处理器必须访问普通内存中的数据,这比缓存慢得多。

缓存设计的典型特征包括:

  • 缓存被组织成行是连续内存的单位。64 字节是典型的行大小。
  • 缓存行被组织成集合每组都与存储器地址中的某些位相关联。例如,缓存可能在每组中有两行或八行。
  • 在每个集合中,一行可以是存储器的任何部分的副本,该部分具有分配给其集合的位。例如,考虑一个带有 bits 的地址aa…aaabbbcccccc这六位c告诉我们这是缓存线中的哪个字节。(2 6 = 64。) 这三位b告诉我们这个字节必须进入哪个缓存集。这些a位与缓存行一起记录,记住它在内存中的位置。

当一个进程r_rel[i][j]以连续的内存顺序工作时,每次访问 的成员时r_rel它访问的成员要么是上次迭代中刚访问过的同一高速缓存行的一部分,要么是下一个高速缓存行的一部分。在前一种情况下,数据已经在缓存中,并且可以快速提供给处理器。在后一种情况下,它必须从内存中获取。(一些处理器已经启动了这个提取,因为它们在最近访问内存之前预取数据。它们被设计来这样做,因为这种内存访问是一种常见的模式。)

从上面可以看出,第一组代码必须为 中的每个缓存行执行一次缓存行加载r_rel下面,我们将把这个数字与第二组代码的相似数字进行比较。

在第二组代码中,r_rel收益的一个用途与第一组代码相同,尽管它只遍历了数组的一半。对于r_rel[i][j],它执行大约一半的第一个代码的缓存加载。由于沿对角线的一些低效使用,它执行了一些额外的加载,但我们可以忽略这一点。

然而,其他用途的r_relr_rel[j][i]是麻烦的。它通过r_rel.

这个问题没有给我们很多细节,所以我会补一些值来说明。假设 的元素r_rel每个是四个字节,并且N行或列中的元素数是 128 的倍数。还假设缓存是 32,768 字节,组织成 64 组,每组 8 行,每行 64 字节。使用这种几何结构,地址模 512 的余数(除法时的余数)确定必须将存储器分配给哪个高速缓存集。

因此,当r_rel[j][i]被访问时会发生什么,该地址周围的 64 字节内存被带入缓存并分配给特定的缓存集。当,当j递增时,该地址周围的内存被带入缓存并分配给特定的缓存集。这些是相同的缓存集。因为行是128个元素,每个元素是4个字节,所以正好相隔一行的两个元素之间的距离是128•4 = 512个字节,这与用于确定一行进入哪个缓存集的数字相同. 所以这两个元素被分配到同一个缓存集。

一开始没问题。缓存集有八行。不幸的是,代码继续迭代j一旦j增加了八次,它就会访问 的第九个元素r_rel由于高速缓存集只有八行,因此处理器必须从集合中删除前面的行之一。随着代码继续迭代j,更多的行被删除。最终,所有先前的行都被删除。当代码完成对j和 的迭代后i,它会返回到数组的开头附近。

回想一下,在第一组代码中,当r_rel[0][2]被访问时,它仍然在缓存中r_rel[0][1]然而,在第二组代码中,r_rel[0][2]缓存早已不复存在。处理器必须再次加载它。

对于对 的访问r_rel[j][i],第二组代码实际上没有从缓存中受益。它必须为每次访问从内存加载。由于在此示例中,每个缓存行中有 16 个元素(四字节元素,64 字节行),因此对于一半矩阵,它的内存访问次数大约是其 16 倍。

总共,如果整个数组中x 条缓存线,第一组代码加载x 条缓存线,第二组代码加载大约x / 2 条缓存线用于r_rel[i][j]访问,大约x /2•16 = 8• x缓存线用于r_rel[i][j]访问,总共 8.5• x缓存线加载。

按列顺序遍历数组对于缓存性能来说糟糕

上面使用的示例数字。最灵活的一种是数组大小,N我假设它是 64 的倍数。我们可以考虑一些其他值。如果它是 32 的倍数,则r_rel[j][i]r_rel[j+1][i]将映射到不同的缓存集。但是,r_rel[j][i]r_rel[j+2][i]映射到同一个集合。这意味着,经过 8 次迭代后j,每组中将只使用 4 行,因此还不需要驱逐旧行。不幸的是,这帮助很小,因为一旦i超过 16,代码就会迭代j足够多的值,以至于缓存集再次清空之前的行,因此每次循环都j必须加载它遇到的每个缓存行。

另一方面,设置N为 73 之类的值可能会减轻这种影响。当然,您不想仅仅为了适应计算机硬件而改变阵列的大小。然而,有一两件事可以做的就是在内存阵列的尺寸NNP唯一的,即使N通过N使用元素。NP(代表“N Padded”)被选择以使行相对于缓存几何具有奇数大小。额外的元素只是浪费了。

这提供了一种快速更改程序的方法,以证明缓存效果使其变慢,但它通常不是首选解决方案。另一种方法是平铺对数组的访问。不是迭代i遍历j整个数组,而是将数组分割成一定大小的瓦片,A逐行B两个外循环遍历所有图块,两个内循环遍历每个图块内的数组元素。

AB被选择,以便在内部循环进行时,一个图块的所有元素都将保留在缓存中。对于上面的样本数,A并且B必须为 8 或更少,因为在一个缓存集中只能保存阵列的 8 行。(可能还有其他考虑因素会使最佳平铺大小稍微小一些。或者,对于不同的元素大小或 的值N,最佳平铺大小可能更大。)

请注意,平铺会在编写代码时引发一些问题。当处理对角线上的瓷砖时,代码将处理来自同一瓷砖内两个点的元素。在处理对角线上的图块时,代码将从一个图块内的一个点和另一个图块内的转置点处理元素。这可能会影响操作数组索引的代码和内部循环的边界。对于对角线瓷砖,内部循环看起来与您的j < i条件相似,处理三角形。对于非对角瓷砖,内循环将处理一个完整的正方形(或矩形,如果AB不同)。

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

基本的openmp程序运行速度较慢

来自分类Dev

CURAND运行速度较慢

来自分类Dev

在NOT IN中进行强制转换会使查询运行速度变慢

来自分类Dev

Windows 7应用程序在不集中时运行速度较慢

来自分类Dev

Visual Studio-进行概要分析时,程序运行速度更快

来自分类Dev

是非题:对于足够大的n,O(n ^ 3)算法的运行速度比O(n ^ 4)算法快

来自分类Dev

通过值传递对象与通过引用传递对象时,运行速度较慢

来自分类Dev

网站首次运行时运行速度较慢?

来自分类Dev

网站首次运行时运行速度较慢?

来自分类Dev

应用在Nexus 6 Android棉花糖“ N”帧上的运行速度非常慢

来自分类Dev

应用在Nexus 6 Android棉花糖“ N”帧上的运行速度非常慢

来自分类Dev

计算的模^ N的速度比O(N)

来自分类Dev

使用Intellij在模拟器上运行我的应用程序的运行速度非常慢

来自分类Dev

Haskell的并行速度较慢

来自分类Dev

Hazelcast 执行速度较慢

来自分类Dev

C#中的N体模拟

来自分类Dev

为什么这个为O(n ^ 2)代码执行速度比为O(n)?

来自分类Dev

在R中进行模拟-如何使速度更快?

来自分类Dev

Runnig在anylogic中进行N倍的仿真

来自分类Dev

为什么使用NaN时,数字运算程序的运行速度会慢得多?

来自分类Dev

按下按钮时,C# 控制台程序运行速度更快

来自分类常见问题

为什么此Haskell代码在-O下运行速度较慢?

来自分类Dev

使用实体框架的SQL查询运行速度较慢,使用错误的查询计划

来自分类Dev

LWJGL在较高的FPS上运行速度较慢?(三角洲)

来自分类Dev

隐式 JOIN 在 EXPLAIN 中的行数较少,但运行速度比显式 JOIN 慢

来自分类Dev

在R中进行排名时,如何保留连续的(1,2,3,... n)排名符号?

来自分类Dev

N除以N的速度比N除以2的速度快?

来自分类Dev

当计算机速度加倍时计算O(n)

来自分类Dev

Java应用程序运行速度慢于预期

Related 相关文章

  1. 1

    基本的openmp程序运行速度较慢

  2. 2

    CURAND运行速度较慢

  3. 3

    在NOT IN中进行强制转换会使查询运行速度变慢

  4. 4

    Windows 7应用程序在不集中时运行速度较慢

  5. 5

    Visual Studio-进行概要分析时,程序运行速度更快

  6. 6

    是非题:对于足够大的n,O(n ^ 3)算法的运行速度比O(n ^ 4)算法快

  7. 7

    通过值传递对象与通过引用传递对象时,运行速度较慢

  8. 8

    网站首次运行时运行速度较慢?

  9. 9

    网站首次运行时运行速度较慢?

  10. 10

    应用在Nexus 6 Android棉花糖“ N”帧上的运行速度非常慢

  11. 11

    应用在Nexus 6 Android棉花糖“ N”帧上的运行速度非常慢

  12. 12

    计算的模^ N的速度比O(N)

  13. 13

    使用Intellij在模拟器上运行我的应用程序的运行速度非常慢

  14. 14

    Haskell的并行速度较慢

  15. 15

    Hazelcast 执行速度较慢

  16. 16

    C#中的N体模拟

  17. 17

    为什么这个为O(n ^ 2)代码执行速度比为O(n)?

  18. 18

    在R中进行模拟-如何使速度更快?

  19. 19

    Runnig在anylogic中进行N倍的仿真

  20. 20

    为什么使用NaN时,数字运算程序的运行速度会慢得多?

  21. 21

    按下按钮时,C# 控制台程序运行速度更快

  22. 22

    为什么此Haskell代码在-O下运行速度较慢?

  23. 23

    使用实体框架的SQL查询运行速度较慢,使用错误的查询计划

  24. 24

    LWJGL在较高的FPS上运行速度较慢?(三角洲)

  25. 25

    隐式 JOIN 在 EXPLAIN 中的行数较少,但运行速度比显式 JOIN 慢

  26. 26

    在R中进行排名时,如何保留连续的(1,2,3,... n)排名符号?

  27. 27

    N除以N的速度比N除以2的速度快?

  28. 28

    当计算机速度加倍时计算O(n)

  29. 29

    Java应用程序运行速度慢于预期

热门标签

归档