在编译语言中,你所做的一切都只是
过早优化。我想解释型语言可能会节省一点,但即使在那里,对于我来说,这似乎也是一种不寻常的编写for循环的方式,而回报却非常微小。
直接回答C#的问题,编译器不会通过缓存来优化任何内容。我可以很容易地在循环期间创建一个新数组并指定新长度。因此,每次评估停止条件时,它都会加载数组长度。或者更糟糕的是,我可能没有使用“传统”样式的停止条件,而需要评估一个函数才能知道何时停止。
话虽如此,这里有一个简单的程序:
static void Main( string[] args ) {
int[] integers = new int[] { 1, 2, 3, 4, 5 };
for( int i = 0; i < integers.Length; i++ ) {
Console.WriteLine( i );
}
}
这里是去除 nops 后的 IL 代码:
IL_000d: call void [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(class [mscorlib]System.Array,
valuetype [mscorlib]System.RuntimeFieldHandle)
IL_0012: stloc.0
IL_0013: ldc.i4.0
IL_0014: stloc.1
IL_0015: br.s IL_0024
IL_0018: ldloc.1
IL_0019: call void [mscorlib]System.Console::WriteLine(int32)
IL_0020: ldloc.1
IL_0021: ldc.i4.1
IL_0022: add
IL_0023: stloc.1
IL_0024: ldloc.1
IL_0025: ldloc.0
IL_0026: ldlen
IL_0027: conv.i4
IL_0028: clt
IL_002a: stloc.2
IL_002b: ldloc.2
IL_002c: brtrue.s IL_0017
你问题的关键答案是,将数组推送到堆栈的位置0,然后在IL_0026期间调用获得数组的长度,然后IL_0028执行小于比较,如果评估为true,则最终转到IL_0017。
通过缓存数组的长度,你只能节省ldlen和stloc调用。由于获取数组的长度不会太多浪费时间,因此ldlen指令应该很快。
编辑:
与列表的主要区别在于以下指令:
IL_002b: callvirt instance int32 class [mscorlib]System.Collections.Generic.List`1<int32>::get_Count()
callvirt会占用更多时间,但这个函数实际上只是返回一个私有变量。
你最好关注那些需要毫秒级别优化的事情 - 比如分块数据库调用或优化SQL查询等,而不是试图删减单一IL操作。