操作顺序如何严重改变代码速度?

How Order of Operations Can Seriously Change The Code Speed?

我看了a video Tarodev 的代码优化。在视频的最后,我很震惊,因为我看到操作顺序可以严重改变代码速度。虽然自己试过了,但是不明白怎么可能呢? Float x Float x Vector 如何比 Float x Vector x Float 或 Vector x Float x Float 快 2 倍?

我从 GitHub 安装了 Tarodev 的项目并自己尝试了。结果和视频一样。

编辑:

感谢所有这些很棒的回答,伙计们。在你回答之后,我明白了原因并感到愚蠢 :) 另外,我在 Orhtej2 的 建议

中添加了 video Repository and example code

答案很简单,按照我的例子:

向量是由超过 1 个值组成的对象:例如Vector3 v = new Vector3(1,2,3)

如果您将 Vector3 的数字相乘,您将进行 3 次运算。

float n = 3.f
Vector3 v = new Vector3(1,2,3)

n*v 结果是 Vector3(1*n,2*n,3*n),所以一次乘法需要 3 次计算。 在第一种情况下按照您的示例进行 n*n*v,因此 n*n + 3 计算 n^2 * v 的 1 次计算,总共 4 次计算。

在第二种情况下,你正在做 n * v * n,这意味着 n*v 的 3 次计算 + v*n 的另外 3 次计算,总共 6 次计算。

Vector x Scalar1 x Scalar2  => (Vector x Scalar1) x Scalar2 => 
VectorTemp x Scalar2 = EndResult

总而言之,我们必须通过向量运算对标量进行两次运算。意义 向量长度乘法的 2 倍。

对比

Scalar1 x Scalar2 x Vector = (Scalar1 x Scalar2) x Vector => 
Scalar3 x Vector = EndResult.

这里需要一个Vector x Scalar运算。 Scalar2 的 Scalar1 只是 multipication

所以我想这里的关键思想是如果操作是可交换的,从最便宜的开始。

首先

someVector3 * someFloat

someFloat * someVector3

基本上都是return

new Vector3(someVector3.x * someFloat, someVector3.y * someFloat, someVector3.z * someFloat)

所以基本上是 3 float * float 操作 + Vector3

的构造函数调用

然后顺序很重要,因为您的操作将按这样的顺序解决

  • 第一种情况你做

    (float * float) * Vector3
    

    所以步骤:

    1. (float * float) => single float * operation => result: float
    2. (float * Vector3) => 3 float * operations => result: Vector3
    
  • 在你做的那一秒

    (float * Vector3) * float 
    

    所以步骤:

    1. (float * Vector3) => 3 float * operations => result: Vector3
    2. (Vector3 * float) => 3 float * operations => result: Vector3
    
  • 在第三个等价物中你做

    (Vector2 * float) * float
    

    所以步骤

    1. (Vector3 * float) => 3 float * operations => result: Vector3
    2. (Vector3 * float) => 3 float * operations => result: Vector3
    

在第一种情况下,您只有 4 次 * 操作,而在其他两种情况下,您有 6 次操作。

Plus 在第二种和第三种情况下,您还实例化了一个额外的 Vector3(第一个操作的结果),这也会消耗性能和内存。

要将 float 乘以 Vector3,需要进行三次乘法运算。要将 float 乘以 float,显然只需要一次乘法。

当你这样做时:

result = a * b * c;

这意味着:

result = (a * b) * c;

或更明确地说:

var tmp = a * b;
result = tmp * c;

假设 ab 是浮点数,c 是向量。然后 a * b 执行一次乘法(float 乘以 float)并得到 float,然后第二个运算执行三次乘法(float 乘以 Vector3).总共是四次乘法。

现在支持 a 是向量,bc 是浮点数。然后 a * b 执行三次乘法(Vector3 乘以 float)并得到 Vector3,然后第二个运算再次执行三次乘法(Vector3 乘以 float) 并产生 Vector3。总共是六次乘法。

现在,可能 编译器可能能够弄清楚这些是等价的,并改为执行更便宜的四次乘法。但通常它并不容易看到这一点。通常它 完全等价。例如,舍入和溢出在很大程度上取决于您执行操作的方式,并且编译器不会弄乱它们,因为它可能会引入不准确,因此它信任您并简单地按照您告诉它的去做。