做得更好:
variable1Type foo;
variable2Type baa;
foreach(var val in list)
{
foo = new Foo( ... );
foo.x = FormatValue(val);
baa = new Baa();
baa.main = foo;
baa.Do();
}
或者:
foreach(var val in list)
{
variable1Type foo = new Foo( ... );
foo.x = FormatValue(val);
variable2Type baa = new Baa();
baa.main = foo;
baa.Do();
}
问题是:什么是更快的1例或2例? 区别是微不足道的吗?它在实际应用中是一样的吗? 这可能是一个优化微观,但我真的想知道哪个更好。
答案 0 :(得分:52)
在性能方面,让我们尝试具体的例子:
public void Method1()
{
foreach(int i in Enumerable.Range(0, 10))
{
int x = i * i;
StringBuilder sb = new StringBuilder();
sb.Append(x);
Console.WriteLine(sb);
}
}
public void Method2()
{
int x;
StringBuilder sb;
foreach(int i in Enumerable.Range(0, 10))
{
x = i * i;
sb = new StringBuilder();
sb.Append(x);
Console.WriteLine(sb);
}
}
我故意选择了值类型和引用类型,以防影响事物。现在,他们的IL:
.method public hidebysig instance void Method1() cil managed
{
.maxstack 2
.locals init (
[0] int32 i,
[1] int32 x,
[2] class [mscorlib]System.Text.StringBuilder sb,
[3] class [mscorlib]System.Collections.Generic.IEnumerator`1<int32> enumerator)
L_0000: ldc.i4.0
L_0001: ldc.i4.s 10
L_0003: call class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [System.Core]System.Linq.Enumerable::Range(int32, int32)
L_0008: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
L_000d: stloc.3
L_000e: br.s L_002f
L_0010: ldloc.3
L_0011: callvirt instance !0 [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current()
L_0016: stloc.0
L_0017: ldloc.0
L_0018: ldloc.0
L_0019: mul
L_001a: stloc.1
L_001b: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
L_0020: stloc.2
L_0021: ldloc.2
L_0022: ldloc.1
L_0023: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(int32)
L_0028: pop
L_0029: ldloc.2
L_002a: call void [mscorlib]System.Console::WriteLine(object)
L_002f: ldloc.3
L_0030: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
L_0035: brtrue.s L_0010
L_0037: leave.s L_0043
L_0039: ldloc.3
L_003a: brfalse.s L_0042
L_003c: ldloc.3
L_003d: callvirt instance void [mscorlib]System.IDisposable::Dispose()
L_0042: endfinally
L_0043: ret
.try L_000e to L_0039 finally handler L_0039 to L_0043
}
.method public hidebysig instance void Method2() cil managed
{
.maxstack 2
.locals init (
[0] int32 x,
[1] class [mscorlib]System.Text.StringBuilder sb,
[2] int32 i,
[3] class [mscorlib]System.Collections.Generic.IEnumerator`1<int32> enumerator)
L_0000: ldc.i4.0
L_0001: ldc.i4.s 10
L_0003: call class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [System.Core]System.Linq.Enumerable::Range(int32, int32)
L_0008: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
L_000d: stloc.3
L_000e: br.s L_002f
L_0010: ldloc.3
L_0011: callvirt instance !0 [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current()
L_0016: stloc.2
L_0017: ldloc.2
L_0018: ldloc.2
L_0019: mul
L_001a: stloc.0
L_001b: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
L_0020: stloc.1
L_0021: ldloc.1
L_0022: ldloc.0
L_0023: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(int32)
L_0028: pop
L_0029: ldloc.1
L_002a: call void [mscorlib]System.Console::WriteLine(object)
L_002f: ldloc.3
L_0030: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
L_0035: brtrue.s L_0010
L_0037: leave.s L_0043
L_0039: ldloc.3
L_003a: brfalse.s L_0042
L_003c: ldloc.3
L_003d: callvirt instance void [mscorlib]System.IDisposable::Dispose()
L_0042: endfinally
L_0043: ret
.try L_000e to L_0039 finally handler L_0039 to L_0043
}
正如你所看到的,除了编译器碰巧选择的堆栈顺序 - 这可能只是一个不同的顺序 - 它完全没有效果。反过来,实际上没有任何东西可以让抖动充分利用另一个人没有给予它。
除此之外,还有一种区别。
在我的Method1()
中,x
和sb
的范围限定为foreach
,无法在其外部故意或意外访问。
在我的Method2()
中,x
和sb
在编译时未知在foreach
内可靠地分配值(编译器不知道<{1}}将执行至少一个循环),因此禁止使用它。
到目前为止,没有真正的区别。
我可以分配并使用foreach
之外的x
和/或sb
。作为一项规则,我会说这可能在大多数情况下都很糟糕,所以我赞成foreach
,但我可能有一些合理的理由想要引用它们(如果它们不可能则更现实一些)未分配的),在这种情况下,我会去Method1
。
但是,这是每个代码如何扩展的问题,而不是编写代码的差异。真的,没有区别。
答案 1 :(得分:4)
没关系,它对性能没有任何影响。
但我真的想知道做正确的方法。
大多数人会告诉你内部循环最有意义。
答案 2 :(得分:3)
这只是一个范围问题。在这种情况下,foo和baa仅在for循环中使用,最佳做法是在循环内声明它们。它也更安全。
答案 3 :(得分:1)
好的,我没有注意到原始海报每次在循环中创建一个新对象而不只是“使用”该对象时回答了这个问题。所以,不,在性能方面,不应该有一个微不足道的差异。有了这个,我会使用第二种方法并在循环中声明对象。这样,它将在下一次GC传递期间被清理,并将对象保持在范围内。
---我会留下我的原始答案,因为我输入了所有内容,这可能会帮助后来搜索此消息的其他人。我保证,在我尝试回答下一个问题之前,我会更加关注。
添
实际上,我认为存在差异。如果我没记错,每次创建新对象= new foo()
时,都会将该对象添加到内存堆中。因此,通过在循环中创建对象,您将增加系统的开销。如果你知道循环会很小,那就不是问题了。
所以,如果你最终在一个包含1000个对象的循环中,你将创建1000个变量,这些变量在下一个垃圾收集之前不会被处理掉。现在点击一个你想要做某事的数据库,你有20,000多行可以使用...你可以根据你正在创建的对象类型创建相当大的系统需求。
这应该很容易测试...创建一个应用程序,当您进入循环和退出时创建带有时间戳的10,000个项目。第一次这样做时,在循环之前和循环期间的下一次声明变量。你可能不得不将这个计数提高到远高于10K才能看到真正的速度差异。
同样,还有范围问题。如果在循环中创建,则一旦退出循环就会消失,因此您无法再次访问它。但这也有助于清理,因为垃圾收集一旦退出循环就会最终处理它。
添
答案 4 :(得分:0)
两者都是完全有效的,不确定是否有'正确的方法'来做到这一点。
您的第一个案例更节省内存(至少在短期内)。在循环中声明变量将强制更多地重新分配内存;但是,对于.NETs垃圾收集器,由于这些变量超出范围,因此它们将定期清理,但不一定立即清理。速度的差异可以忽略不计。
第二种情况确实有点安全,因为尽可能地限制变量的范围通常是很好的做法。
答案 5 :(得分:0)
在JS中,内存分配每次都是完整的,在C#中,通常没有这样的差异,但是如果局部变量被像lambda表达式这样的匿名方法捕获,那么它就很重要。