【问题标题】:Which is faster in a loop: calling a property twice, or storing the property once?循环中哪个更快:调用属性两次,或存储一次属性?
【发布时间】:2009-08-19 22:06:02
【问题描述】:

这更像是一个关于性能的学术问题,而不是一个现实的“我应该使用什么”,但我很好奇,因为我根本没有涉足 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 each 之间的性能差异迭代(假设 s 可以是任何原始类型或字符串)与每次迭代调用对象上的 getter 相比。

【问题讨论】:

  • 你为什么要问我们?你已经编写了代码。拿出一个秒表,双向运行十亿次,然后你就会知道哪个更快。

标签: c# performance


【解决方案1】:

在我的测试中,你的第一个选项明显更快。我真是太牛了!不过说真的,在我最初的测试中,一些 cmets 是关于代码的。这是显示选项 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();
    }

【讨论】:

  • Andy:您在哪个运行时/平台上进行了测试?另外,它是发布版本吗?
  • 对我来说,结果是:2294, 702 这与你的结论相矛盾。
  • 我只是在本地运行了几次 - debug/3.5/Win7 64。差距通常是 10%,但有时会上升到接近 100%。我会尝试发布。
  • 第一项的所有运行平均值为 311,第二项为 306。第一个的最大值是 435,第二个是 419。我没有费心去捕捉最小值。我认为您看到的疯狂结果是由于新的 List() 是在秒表启动后发生的,因此列表空间的分配是不一致的真正罪魁祸首。
  • @chsh:你说得对——我应该把字符串放在秒表之外。调用 Clear 而不是新建一个 List 产生了巨大的变化。我会更新代码。
【解决方案2】:

大多数时候,您的第二个代码 sn-p 应该至少和第一个 sn-p 一样快

这两个代码 sn-ps 在功能上并不等效。不保证属性在各个访问中返回相同的结果。因此,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)

【讨论】:

  • 假设编译成功,两种情况下的 o.Field 都是字符串类型。我还假设集合中每个对象的 o.Field 的值都设置为某个有意义的值。也许我不太明白你的意思;你能说得更具体点吗?
  • 查看我更新答案中的链接。这是一个特定示例,如果您缓存返回值,它会产生显着差异。
【解决方案3】:

这真的取决于实施。在大多数情况下,假设(作为惯例/礼貌的问题)房产价格低廉。但是,每个“get”都可能对某些远程资源进行非缓存搜索。对于标准的简单属性,您永远不会注意到两者之间的真正区别。在最坏的情况下,一次提取、存储和重复使用会更快。

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

【讨论】:

  • @Marc:o.Field 实际上可以在针对“某物”测试它并将其添加到List 之间更改值的第一个代码 sn-p 是否存在问题? o.Field == "something" 可能评估为 true,但是当您致电 strings.Add 时,您正在添加“其他内容”?
  • @Grant - 哦,绝对可以,但同样是......非标准 - 或者至少,应该有据可查。如果是因为线程,那么当然只能怪我们自己。
  • @Marc:我并不是说这是一种过早的优化,尤其是当您处理愚蠢的非 O(1) 属性(其中很多存在于 WinForms 中)时,就像我链接的那个在我的回答中。此外,在多线程场景中,为了正确起见,您可能更愿意保留结果。
  • @Grant,除非它是线程安全的共享资源,否则该属性的作者将是非常糟糕的做法。在任何实际情况下,您都会知道何时怀疑此类行为。
【解决方案4】:

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

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

【讨论】:

    【解决方案5】:

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

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

    我始终坚持这样做。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-09
      • 1970-01-01
      • 2020-11-01
      • 2020-08-31
      • 2016-04-22
      • 1970-01-01
      相关资源
      最近更新 更多