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

时间:2018-12-12 09:14:09

标签: 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;
}

1 个答案:

答案 0 :(得分:1)

我们应该检查发布配置中由.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)的评论(另一个主题,我不是:-)的专家)。