【问题标题】:Does size of array in C affect time to access or change a piece of data in the arrayC中数组的大小是否会影响访问或更改数组中的一条数据的时间
【发布时间】:2015-03-14 13:42:34
【问题描述】:

标题说明了一切:

我创建和排列,比如 ARRAY[10000], 我设置了只需要访问 1-100 和 900-2000 的数据的范围。

与我将数组声明为 ARRAY[2001] 相比,以这种方式访问​​和/或更改一条数据所花费的时间会更多吗

如果我的数组只包含 1-100 和 900-2000 的数据,访问和/或更改时间会改变吗?

我看过一些关于这方面的论文,但我并不清楚,希望我能在这里得到更简洁的答案。

【问题讨论】:

  • 您的访问模式如何?为什么你甚至会考虑创建一个巨大的数组,却连 80% 都不看?
  • 我不明白您将范围设置为 1-100 和 900-2000 是什么意思,但是数组的访问时间不会改变,但是您可能会浪费大量记忆
  • 除非您传递的数组大小太大而无法放入缓存中,否则会导致管道停顿或使数组小到足以放入寄存器中,我真诚地怀疑它。
  • 如果你有一个 3 维对象并且有一个环形的约束,你只需要访问那个环形的数据点。你的“甜甜圈”的整个中心永远不会被触及。如果没有 x-y-z 坐标,您将不得不在系统上的每次运行期间进行标准化。 @5gon12eder
  • @WillFox 为什么不用x-y-z 坐标创建一个struct(s) 数组呢?

标签: c performance time


【解决方案1】:

在信息理论中,正如其他人所说,数组访问是恒定的,因此不会根据数组大小或多或少地花费成本。不过,这个问题似乎与真实的现场表演有关,而且数组大小肯定很重要。 @Brendan 接受的答案很好地解释了如何做到这一点。

在实践中要考虑的事项: * 数组的元素有多大:bool[1000]、MyStruct[1000] 和 MyStruct*[1000] 在访问性能上可能会有很大差异 * 尝试两种方式编写代码,一次使用大数组,一次将所需数据保存在较小的数组中。然后在目标硬件上运行代码,并比较性能。您经常会惊讶地发现,优化尝试会使性能变差,并且您会在此过程中学到很多关于硬件及其怪癖的知识。

【讨论】:

    【解决方案2】:

    如果不经常访问数组,那么数组的大小可能不会有太大的不同,因为无论如何您都会遇到缓存未命中。在这种情况下,时间将取决于 CPU 能够以多快的速度进行任何“虚拟地址到物理地址”的转换并从 RAM 中获取数据。

    您访问数组中的某些内容的频率越高,缓存效果就越重要。这些缓存效果在很大程度上取决于不同缓存的数量、大小和速度。

    不过,这也取决于您的访问模式。例如,如果您有一个 1 GiB 数组并经常访问其中的 5 个字节,那么大小不会有太大区别,因为您经常访问的 5 个字节将在缓存中。再举一个例子,如果您使用顺序访问模式(例如“对于数组 { ... } 中的每个元素”),那么 CPU 可能会进行硬件预取,并且您不会支付缓存未命中的全部成本。

    对于典型的 80x86 系统,具有随机访问模式和频繁访问;大约有 5 种不同的尺寸很重要。第一个(最小的)是 L1 数据缓存大小 - 如果数组适合 L1 数据缓存,那么无论数组是 20 字节还是 20 KiB,它都会相对较快。下一个大小是 L2 数据缓存大小 - 随着阵列变大,“L1 命中与 L2 命中”的比率会降低并且性能变得更差,直到(可能“比 L1 大两倍”)L1 命中变得可以忽略不计。然后(对于某些具有 L3 缓存的 CPU)同样的情况发生在 L3 缓存大小上,其中随着数组变大,“L2 命中与 L3 命中”的比率会降低。

    一旦超过最大缓存,“缓存命中与缓存未命中”的比率就会降低。

    下一个重要的大小是 TLB 大小。大多数现代操作系统使用分页,并且大多数现代 CPU 将“虚拟地址到物理地址的转换”缓存在通常称为转换后备缓冲区的东西中。如果数组很大,那么您会开始出现 TLB 未命中(除了缓存未命中),这会使性能变差(因为 CPU 在将虚拟地址转换为物理地址时无法避免额外的工作)。

    最后,最重要的大小是您实际拥有多少 RAM。如果你有一个 10 TiB 的阵列、4 GiB 的 RAM 和 20 TiB 的交换空间;那么操作系统将在磁盘之间交换数据,磁盘 IO 的开销将主导性能。

    当然,您通常会为许多不同的计算机创建可执行文件(例如“64 位 80x86,从古代 Athlon 到现代 Haswell”)。在这种情况下,您无法知道大多数重要的细节(例如缓存大小),并且它成为猜测之间的折衷(由于“数组太大”而导致的缓存未命中的估计开销与由“数组”引起的其他事情的估计开销太小了”)。

    【讨论】:

    • 这是一种非常彻底的方式来说明它取决于,但它很有趣。感谢您花时间以非常简洁和连贯的方式阐明硬件组件!
    【解决方案3】:

    可能是的,但这取决于大小。

    缓存大小

    访问范围的大小会改变延迟。 int ARRAY[10000] 适合一级缓存(32KB) 非常小的访问,适合 L1 缓存(32KB),它需要 4 个时钟的 haswell。 但是 L2 缓存访问需要 12 个时钟。

    在此处查看详细信息 http://www.7-cpu.com/cpu/Haswell.html

    缓存冲突

    如果其他 CPU 核心修改数组中的一些数据,本地缓存行状态将被修改为Invalid 状态。 Invalid 声明缓存线的延迟要高得多。

    NUMA

    一些主板上有多个CPU插槽的环境,称为non-uniform memory access环境。

    那可能有很大的内存容量,但是有些内存地址可能驻留在CPU1中,另外一些内存地址可能驻留在CPU2中。

    int huge_array[SOME_FUGE_SIZE];   // it strides multiple CPU's DIMM
    // suppose that entire huge_array is out of cache.
    huge_array[ADDRESS_OF_CPU1] = 1;  // maybe fast for CPU1
    huge_array[ADDRESS_OF_CPU2] = 1;  // maybe slow for CPU2
    

    但我想知道巨大的数组跨越多个 CPU 的内存。 巨大数组的分配可能会失败。 这取决于操作系统。

    【讨论】:

    • 我真的很想选择这个和 Brendan 的回答,因为你的回答有助于让他更容易理解为介绍。感谢您的回答,它确实有帮助!
    【解决方案4】:

    我认为不应该。

    当您访问一个元素时,您将前往内存位置 0 + (element),因此无论大小如何,它都会在同一时间到达相同的内存位置。

    【讨论】:

      【解决方案5】:

      不,至少通常访问数组中任何项目的时间是恒定的,无论数组的大小如何。

      如果(例如)您定义一个大于内存的数组,这可能会改变,因此操作系统(假设您使用的是一个)最终会执行一些额外的分页以支持更大的数组。在大多数情况下,即使这样也不太可能产生太大影响。

      【讨论】:

      • 这不仅仅是分页。这就是为什么我要求访问模式。如果他一遍又一遍地迭代,如果数组中有“洞”,缓存未命中的频率会更高。
      • @5gon12eder 我将对其进行迭代。
      • @WillFox 在这种情况下,您应该知道,如果您正在访问连续的内存块,您的硬件将表现最佳,因为这样可以最大化缓存中填充有用数据的百分比并允许预取启发式方法来正确猜测您接下来需要什么内存。当然,如果你只把数组分成两部分,而且比较大,惩罚也不会太高。
      • @5gon12eder:抱歉,没有。未使用的“洞”不会在大多数类型的缓存中创建缓存未命中(除非您实际上在“洞”中读取了您不关心的内存)。它可能会影响预取,但影响很小(而且大多数都有预测器,因此如果您重复以特定模式访问,他们会检测并遵循该模式)。
      • 如果“空洞”没有以缓存行大小的倍数对齐,您至少会在缓存中加载一些垃圾。每次你“跳过一个洞”时,预取启发式算法都很难正确猜测。但正如我在之前的评论中所说,如果连续的块很少且相对较大,那应该不会太糟糕。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-20
      • 2017-06-30
      • 2014-02-20
      • 2014-12-16
      • 1970-01-01
      • 2012-11-17
      相关资源
      最近更新 更多