我一直在阅读关于Javascript性能提升的提示,并且一个提示说要在循环比较器语句中缓存所有变量(不会改变),我想知道这是否也适用于.NET.
假设我有一个简单的for循环,下面哪个会更快或者它们是否相同?
没有缓存:
for (int i = 0; i < someArray.Length; i++)
{
}
Run Code Online (Sandbox Code Playgroud)
使用缓存:
for (int i = 0, count = someArray.Length; i < count; i++)
{
}
Run Code Online (Sandbox Code Playgroud)
根据文章"缓存" Length,在循环中删除一个操作的值,因为与访问对象的成员相比,它更快地访问局部变量.与简单地访问成员相比,它实际上更快地声明局部变量吗?编译器是否接受此操作并自动缓存该值?声明局部变量而不是访问该成员是否有任何负面影响?
虽然速度可能是这里的一个关键因素,但它不是唯一的因素.我的下一个问题可能是哪一个更有效率.哪个使用较少的内存分配?哪个执行较少的堆栈操作?等等...
从评论来看,似乎访问数组的长度非常快.让我们说我用了一个IList<>.缓存值Count是否比每次迭代检索它更快?
在编译语言中,您所做的只是过早优化。我认为解释性语言可能会节省一点,但即使在那里,对于(根据我的经验)编写 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 );
}
}
Run Code Online (Sandbox Code Playgroud)
这是 IL(删除了 nops):
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
Run Code Online (Sandbox Code Playgroud)
这里问题的关键答案是,它将数组推入堆栈中的位置 0,然后在 IL_0026 期间执行调用以获取数组的长度,然后 IL_0028 执行小于比较,最后转到 IL_0017如果评价属实的话。
通过缓存数组的长度,您所节省的只是 ldlen 和 stloc 调用。ldlen 指令应该很快,因为获取数组的长度不会浪费太多时间。
编辑:
与列表的主要区别是这条指令:
IL_002b: callvirt instance int32 class [mscorlib]System.Collections.Generic.List`1<int32>::get_Count()
Run Code Online (Sandbox Code Playgroud)
callvirt 会占用更多时间,但该函数实际上所做的只是返回一个私有变量。
与尝试减少单个 IL 操作相比,您最好不要担心需要几毫秒的事情,例如分块数据库调用或优化 SQL 查询以使其更快等。
| 归档时间: |
|
| 查看次数: |
2670 次 |
| 最近记录: |