最近的c#编译器是否消除了不必要的数组查找



假设:

  • 正在使用最近的mono或其他c#编译器
  • 代码对性能敏感

动机:

  • 删除变量vert
  • 以提高可读性
  • 依靠编译器缓存数组查找

示例:

public static List<Vector2> Wavefronts(Vertex[] vertices, float s) {
var result = new List<Vector2>(vertices.Length);
for (int i = 0; i < vertices.Length; i++) {
var vert = vertices[i];
result[i] = vert.o + vert.v * s;
}
return result;
}

我们可能应该在Release配置中检查由.NET Standard 2.0编译器创建的IL。我自己不是IL专家,但让我们比较一下在C#代码中查找两次和一次,并使用dnSpy的自动注释。

首先,您的示例已经只进行了一次数组查找,并将该顶点存储在变量vert中。这将创建以下IL输出;注意,只有一条ldelem指令,这意味着只有一个数组查找,正如预期的那样:

// loop start (head: IL_0037)
IL_000D: ldarg.0    // Loads the argument at index 0 onto the evaluation stack.
IL_000E: ldloc.1    // Loads the local variable at index 1 onto the evaluation stack.
IL_000F: ldelem    ClassLibrary1.Vertex // Loads the element at a specified array index onto the top of the evaluation stack as the type specified in the instruction. 
IL_0014: stloc.2    // Pops the current value from the top of the evaluation stack and stores it in a the local variable list at index 2.
IL_0015: ldloc.0    // Loads the local variable at index 0 onto the evaluation stack.
IL_0016: ldloc.1    // Loads the local variable at index 1 onto the evaluation stack.
IL_0017: ldloc.2    // Loads the local variable at index 2 onto the evaluation stack.
IL_0018: ldfld     class [Syroot.Maths]Syroot.Maths.Vector2 ClassLibrary1.Vertex::o // Finds the value of a field in the object whose reference is currently on the evaluation stack.
IL_001D: ldloc.2    // Loads the local variable at index 2 onto the evaluation stack.
IL_001E: ldfld     class [Syroot.Maths]Syroot.Maths.Vector2 ClassLibrary1.Vertex::v // Finds the value of a field in the object whose reference is currently on the evaluation stack.
IL_0023: ldarg.1    // Loads the argument at index 1 onto the evaluation stack.
IL_0024: call      class [Syroot.Maths]Syroot.Maths.Vector2 [Syroot.Maths]Syroot.Maths.Vector2::op_Multiply(class [Syroot.Maths]Syroot.Maths.Vector2, float32)  // Calls the method indicated by the passed method descriptor.
IL_0029: call      class [Syroot.Maths]Syroot.Maths.Vector2 [Syroot.Maths]Syroot.Maths.Vector2::op_Addition(class [Syroot.Maths]Syroot.Maths.Vector2, class [Syroot.Maths]Syroot.Maths.Vector2) // Calls the method indicated by the passed method descriptor.
IL_002E: callvirt  instance void class [netstandard]System.Collections.Generic.List`1<class [Syroot.Maths]Syroot.Maths.Vector2>::set_Item(int32, !0)    // Calls a late-bound method on an object, pushing the return value onto the evaluation stack.
IL_0033: ldloc.1    // Loads the local variable at index 1 onto the evaluation stack.
IL_0034: ldc.i4.1   // Pushes the integer value of 1 onto the evaluation stack as an int32.
IL_0035: add    // Adds two values and pushes the result onto the evaluation stack.
IL_0036: stloc.1    // Pops the current value from the top of the evaluation stack and stores it in a the local variable list at index 1.
IL_0037: ldloc.1    // Loads the local variable at index 1 onto the evaluation stack.
IL_0038: ldarg.0    // Loads the argument at index 0 onto the evaluation stack.
IL_0039: ldlen  // Pushes the number of elements of a zero-based, one-dimensional array onto the evaluation stack.
IL_003A: conv.i4    // Converts the value on top of the evaluation stack to int32.
IL_003B: blt.s     IL_000D  // Transfers control to a target instruction (short form) if the first value is less than the second value.
// end loop

(我也一直在使用我的Syroot.Maths库,因为我不知道你在使用什么,你的代码示例不完整。(


现在,如果您的样本将执行多个数组查找,也就是

public static List<Vector2> Wavefronts(Vertex[] vertices, float s)
{
var result = new List<Vector2>(vertices.Length);
for (int i = 0; i < vertices.Length; i++)
{
result[i] = vertices[i].o + vertices[i].v * s;
}
return result;
}

测试编译不会对我进行优化,它将执行两个数组查找(注意两个ldelema(:

// loop start (head: IL_003B)
IL_000D: ldloc.0    // Loads the local variable at index 0 onto the evaluation stack.
IL_000E: ldloc.1    // Loads the local variable at index 1 onto the evaluation stack.
IL_000F: ldarg.0    // Loads the argument at index 0 onto the evaluation stack.
IL_0010: ldloc.1    // Loads the local variable at index 1 onto the evaluation stack.
IL_0011: ldelema   ClassLibrary1.Vertex // Loads the address of the array element at a specified array index onto the top of the evaluation stack as type & (managed pointer).
IL_0016: ldfld     class [Syroot.Maths]Syroot.Maths.Vector2 ClassLibrary1.Vertex::o // Finds the value of a field in the object whose reference is currently on the evaluation stack.
IL_001B: ldarg.0    // Loads the argument at index 0 onto the evaluation stack.
IL_001C: ldloc.1    // Loads the local variable at index 1 onto the evaluation stack.
IL_001D: ldelema   ClassLibrary1.Vertex // Loads the address of the array element at a specified array index onto the top of the evaluation stack as type & (managed pointer).
IL_0022: ldfld     class [Syroot.Maths]Syroot.Maths.Vector2 ClassLibrary1.Vertex::v // Finds the value of a field in the object whose reference is currently on the evaluation stack.
IL_0027: ldarg.1    // Loads the argument at index 1 onto the evaluation stack.
IL_0028: call      class [Syroot.Maths]Syroot.Maths.Vector2 [Syroot.Maths]Syroot.Maths.Vector2::op_Multiply(class [Syroot.Maths]Syroot.Maths.Vector2, float32)  // Calls the method indicated by the passed method descriptor.
IL_002D: call      class [Syroot.Maths]Syroot.Maths.Vector2 [Syroot.Maths]Syroot.Maths.Vector2::op_Addition(class [Syroot.Maths]Syroot.Maths.Vector2, class [Syroot.Maths]Syroot.Maths.Vector2) // Calls the method indicated by the passed method descriptor.
IL_0032: callvirt  instance void class [netstandard]System.Collections.Generic.List`1<class [Syroot.Maths]Syroot.Maths.Vector2>::set_Item(int32, !0)    // Calls a late-bound method on an object, pushing the return value onto the evaluation stack.
IL_0037: ldloc.1    // Loads the local variable at index 1 onto the evaluation stack.
IL_0038: ldc.i4.1   // Pushes the integer value of 1 onto the evaluation stack as an int32.
IL_0039: add    // Adds two values and pushes the result onto the evaluation stack.
IL_003A: stloc.1    // Pops the current value from the top of the evaluation stack and stores it in a the local variable list at index 1.
IL_003B: ldloc.1    // Loads the local variable at index 1 onto the evaluation stack.
IL_003C: ldarg.0    // Loads the argument at index 0 onto the evaluation stack.
IL_003D: ldlen  // Pushes the number of elements of a zero-based, one-dimensional array onto the evaluation stack.
IL_003E: conv.i4    // Converts the value on top of the evaluation stack to int32.
IL_003F: blt.s     IL_000D  // Transfers control to a target instruction (short form) if the first value is less than the second value.
// end loop

我不知道为什么会这样。也许一些编译器专家可以澄清。也许JIT仍然优化了这一点。Damien的评论(另一个我不是专家的话题:-((。

最新更新