循环中哪个更快:调用属性两次,或者存储属性一次?

时间:2009-08-19 22:06:02

标签: c# performance

这更像是一个关于性能的学术问题,而不是一个现实的“我应该使用什么”,但我很好奇,因为我根本不涉及IL,看看是什么构造的,我没有大数据集在手边反对。

哪个更快:

List<myObject> objs = SomeHowGetList();
List<string> strings = new List<string>();
foreach (MyObject o in objs)
{
    if (o.Field == "something")
        strings.Add(o.Field);
}

或:

List<myObject> objs = SomeHowGetList();
List<string> strings = new List<string>();
string s;
foreach (MyObject o in objs)
{
    s = o.Field;
    if (s == "something")
        strings.Add(s);
}

请记住,我真的不想知道string.Add(s)的性能影响(因为无论需要做什么操作都无法真正改变),只是每个设置的性能差异迭代(假设s可以是任何原始类型或字符串),每次迭代都会在对象上调用getter。

5 个答案:

答案 0 :(得分:9)

你的第一个选择在我的测试中明显加快。我是这样的翻转翻牌!但是说真的,我对原始测试中的代码做了一些评论。这是更新的代码,显示选项2更快。

    class Foo
    {
        public string Bar { get; set; }

        public static List<Foo> FooMeUp()
        {
            var foos = new List<Foo>();

            for (int i = 0; i < 10000000; i++)
            {
                foos.Add(new Foo() { Bar = (i % 2 == 0) ? "something" : i.ToString() });
            }

            return foos;
        }
    }

    static void Main(string[] args)
    {

        var foos = Foo.FooMeUp();
        var strings = new List<string>();

        Stopwatch sw = Stopwatch.StartNew();

        foreach (Foo o in foos)
        {
            if (o.Bar == "something")
            {
                strings.Add(o.Bar);
            }
        }

        sw.Stop();
        Console.WriteLine("It took {0}", sw.ElapsedMilliseconds);

        strings.Clear();
        sw = Stopwatch.StartNew();

        foreach (Foo o in foos)
        {
            var s = o.Bar;
            if (s == "something")
            {
                strings.Add(s);
            }
        }

        sw.Stop();
        Console.WriteLine("It took {0}", sw.ElapsedMilliseconds);
        Console.ReadLine();
    }

答案 1 :(得分:7)

大多数情况下,您的第二个代码段至少与第一个代码段一样快。

这两个代码段在功能上并不相同。不保证属性在各个访问中返回相同的结果。因此,JIT优化器无法缓存结果(除了微不足道的情况),如果缓存长时间运行的属性的结果,它会更快。请看这个例子:why foreach is faster than for loop while reading richtextbox lines

但是,对于某些特定情况,例如:

for (int i = 0; i < myArray.Length; ++i)

其中myArray是一个数组对象,编译器能够检测模式并优化代码并省略绑定的检查。如果缓存Length属性的结果可能会更慢,如:

int len = myArray.Length;
for (int i = 0; i < myArray.Length; ++i)

答案 2 :(得分:4)

这实际上取决于实施。在大多数情况下,假设(通常的做法/礼貌)财产是廉价的。但是,每个“get”都可以通过某些远程资源进行非缓存搜索。对于标准的简单属性,您永远不会注意到两者之间的真正差异。对于最坏的情况,fetch-once,存储和重用会更快。

我很想使用get两次,直到我知道存在问题......“过早优化”等等......但是;如果我在紧密循环中使用它,那么我可能会将它存储在一个变量中。除了数组上的Length,它有特殊的JIT处理;-p

答案 3 :(得分:2)

将值存储在字段中是更快的选择。

虽然方法调用不会产生巨大的开销,但它远远超过将值一次存储到堆栈上的局部变量然后检索它。

我一个人一直这样做。

答案 4 :(得分:2)

通常第二个更快,因为第一个重新计算每次迭代的属性。 这是一个可能需要花费大量时间的例子:

 var d = new DriveInfo("C:");
 d.VolumeLabel; // will fetch drive label on each call