假设:
- 正在使用最近的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的评论(另一个我不是专家的话题:-((。