【问题标题】:Cost of changing a value vs. accessing an array in C [closed]更改值与访问 C 中的数组的成本 [关闭]
【发布时间】:2021-09-11 15:01:49
【问题描述】:

这个问题是基于意见的,所以这是一个编辑来澄清我的意思。

有什么方法可以对更改double 的值是否比从数组中检索double 花费更多或更少的时间做出有根据的猜测?我知道更快的方法可能是情境性的,问题是是否有任何方法可以预测在给定情况下更快的方法是什么。或者,如果有任何“良好做法”,则应遵循这样的编译器可以进行尽可能多的优化。

此问题基于以下知识:访问给定数据所需的时间取决于它位于 L1、L2、L3 (...) 还是 RAM。由于 L1、L2、...中的空间有限,我相信重复修改单个变量比修改许多不同的变量一次要快一些。但是,我不知道差异有多大,或者是否可以预测/操作哪些数据/指令将位于哪个缓存/RAM 中。

下面是最初所说的问题:

操作所花费的时间(据我所知)与存储您正在使用的信息的内存缓存有关。所以我想知道改变双精度 2N 次的值是否比将 N 双精度存储在数组中然后遍历数组更有效。想法是频繁更改的变量将存储在较低级别的缓存中,因此访问它的速度将比存储在数组中的值快一点。数组足够小,整个数组都可以放入 RAM,重点是不要释放内存。

这两种选择的示例代码如下所示。请注意,这里的计算被简化以更好地描述问题的本质。实际上数组是二维的,tmp1tmp2 的计算量稍大,但仍然只是对索引的简单依赖:

#define DIM 1000
double states[DIM];
double time_derivatives[DIM];
double ambient_state = 3.0;
    
// Initialize states
for (int pos = 0; pos < DIM; pos++) {
    states[pos] = pos;
}

// Alternative 1
double tmp1;
double tmp2;

// Ends
tmp1 = 1;
tmp2 = 2;
time_derivatives[0] = (ambient_state - states[0]) * tmp1 + (states[1] - states[0]) * tmp2;
tmp1 = DIM;
tmp2 = DIM + 1;
time_derivatives[DIM - 1] = (ambient_state - states[DIM - 1]) * tmp2 + (states[DIM - 2] - states[DIM - 1]) * tmp1;

// Bulk
for (int pos = 1; pos < DIM - 1; pos++) {
    tmp1 = pos + 1;
    tmp2 = pos + 2;
    time_derivatives[pos] = (states[pos - 1] - states[pos]) * tmp1 + (states[pos + 1] - states[pos]) * tmp2;
}
    
// Alternative 2
double flows[DIM + 1];
double tmp1; //Some intermediate, neccesary calculation variable

// Flows at ends
tmp1 = 1;
flows[0] = (states[0] - ambient_state) * tmp1;
tmp1 = DIM;
flows[DIM] = (ambient_state - states[DIM - 1]) * tmp1;

// Flows in bulk
for (int pos = 1; pos < DIM; pos++) {
    tmp1 = pos + 1;
    flows[pos] = (states[pos] - states[pos - 1]) * tmp1;
}
// Compute time derivatives
for (int pos = 0; pos < DIM; pos++) {
    time_derivatives[pos] = flows[pos + 1] - flows[pos];
}
    

在备选方案 1 中,大量计算在最终 for 循环中“重复”,因为一次迭代中的 (states[pos + 1] - states[pos]) * tmp1 将等于下一次迭代中的 - (states[pos - 1] - states[pos]) * tmp2。在备选方案 2 中,计算所有差异并将其存储在数组 flows 中,从而减少了计算的总数。

问题本质上是,与在数组中存储和访问变量的成本相比,计算操作的成本是多少?是否存在限制案例何时一个比另一个更有效?

【问题讨论】:

  • 优化编译器可能会相当显着地重新排序代码。如果您想确定,请测量。
  • 没有任何保证。分析这两种方法,看看哪种方法更快。
  • 与往常一样,唯一确定的方法就是测量。现代硬件很复杂,即使我们认为我们知道发生了什么,也很容易感到惊讶。我的目标是编写干净、易于理解、自我记录的代码。这通常使编译器更容易进行优化并使维护变得更加容易。只有在分析并确定存在问题后,我才会尝试对一段代码进行微优化。
  • 实际上,即使单独进行基准测试也可能会产生误导。绝对确定的唯一方法是同时实现它们的实际应用和衡量
  • 我唯一要说的是现代英特尔处理器可以检测和预取串行数据(SSE 中的“流”部分),因此顺序访问内存应该是可取的,因为会有更少的停顿。这两个选项似乎都无法以这种方式访问​​内存。

标签: c++ c performance optimization processing-efficiency


【解决方案1】:

正如几个 cmets 所提到的,通常无法仅通过查看 C 代码来比较两种替代实现(执行相同操作)的性能。首先,现代编译器会使用各种“魔法”来生成性能良好的代码,当代码执行时,处理器会使用很多魔法来尽可能快地执行代码。因此,您需要成为编译器和处理器方面的专家才能仅通过查看 C 代码来判断性能。

如果您不是极端专家(很少有专家),唯一的选择是衡量两者在您的实际应用中的表现。

也就是说……在我看来,您的备选方案 2 正在做一些奇怪且不必要的事情。例如:

// Flows in bulk
for (int pos = 1; pos < DIM; pos++) {
    tmp1 = pos + 1;
    flows[pos] = (states[pos] - states[pos - 1]) * tmp1;
}
// Compute time derivatives
for (int pos = 0; pos < DIM; pos++) {
    time_derivatives[pos] = flows[pos + 1] - flows[pos];
}

为什么有两个循环?

据我所知,您可以使用一个循环,例如:

for (int pos = 1; pos < DIM; pos++) {
    tmp1 = pos + 1;
    flows[pos] = (states[pos] - states[pos - 1]) * tmp1;
    time_derivatives[pos-1] = flows[pos] - flows[pos-1];
}

为什么要有一个流数组?

据我所知,flows 数组没有任何理由。只需这样做:

tmp1 = 1;
flows_prev_loop = (states[0] - ambient_state) * tmp1;
for (int pos = 1; pos < DIM; pos++) {
    tmp1 = pos + 1;
    flows_this_loop = (states[pos] - states[pos - 1]) * tmp1;
    time_derivatives[pos-1] = flows_this_loop - flows_prev_loop;
    flows_prev_loop = flows_this_loop;
}

这样你就有了一个alternative 3,它可以避免在使用数组的情况下多次计算相同的结果。

我觉得这个替代方案会击败你的两个......但可以肯定的是,你需要衡量

【讨论】:

  • OP 确实声明“请注意,此处的计算已简化”,因此算法不是问题所在,主要是内存访问时间。正如其他人所说,分析代码以查看哪个效果最好。
【解决方案2】:

确实,如果不进行测量,您就无法知道,但您可能会面临测量错误或无法测量未来计算机的风险。

请记住,您很容易测量错误的东西。程序员时间通常比机器时间昂贵得多。猜测——甚至猜测错误——可能是最好的策略,因为它很快。

所以这里有一个快速猜测的基础。

大约 20 年前,我从事蒙特卡罗模拟系统工作,该系统需要大量随机数。我们花了数周时间评估随机数生成器,以选择一个在我们的模型中引入最小偏差的生成器。然后我们将这些数字存储在一个数组中,并在整个过程中使用该数组。

大约 10 年后,我们有理由重新审视这个过程,IIRC,因为我们需要更多的数字。在此过程中,我们注意到数组没有帮助:每次我们需要一个数字时调用 RNG 函数比使用预先生成的数组要快。很多。

随机数生成是一项非常复杂的业务,需要大量计算。但它是一个小算法,几乎没有一页代码。

我学到的教训是计算很便宜,而高速缓存则不然。我一直以此作为我猜测的基础。随意做同样的事情。

【讨论】:

  • 谢谢!我意识到我的问题含糊不清,但这确实是我想知道的。重复修改内存(许多计算)或访问许多不同的内存地址是否更便宜?我知道你必须测量才能确定。但正如我从你那里得到的,一个有根据的猜测是,许多计算(尤其是相对简单的计算)比重复访问数组中的不同值要便宜。
猜你喜欢
  • 2012-02-16
  • 1970-01-01
  • 1970-01-01
  • 2022-01-19
  • 2014-05-31
  • 1970-01-01
  • 2012-09-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多