C# - 你需要缩短查找链吗,就像在 JavaScript 中一样?

4

我有一个包含公共属性的类。在一个函数中,我大约会引用这个属性30-40次。

  this.MyProp; 

在函数中定义一个局部变量是否更好?

 string myProp = this.MyProp;

在这个过程中,我缩短了查找链,所以在函数中只需要引用myProp,而不是this.MyProp。在JavaScript中,这种查找链的缩短确实可以提高性能。但是在C#中会更好还是更差呢?因为显然,我还需要创建另一个本地字符串变量。

我自己也很想知道这个。在.NET 1.1中,常见的建议是先将属性分配给本地变量,以避免重复执行get方法。我不知道更近期的C#编译器是否更加智能化。 - David Andres
6个回答

11

从纯性能角度来看,这要取决于属性的作用。如果MyProp getter只是返回一个私有字段,那么开销就完全可以忽略不计。实际上,除非你明显在做一些重要的事情,比如一些巨大的枚举或调用数据库,否则这并不重要。担心这个问题是微观优化。

但需要注意的是属性的目的是通过封装的程序强制访问值以确保一致性。如果尝试绕过这一点,会使代码面临更大的错误风险。

根据@DavidAndres评论:

按照惯例,属性通常不应在getter或setter中执行任何实质性操作。因此,如果仅验证属性表现良好,可以通过反射器、文档或查看自己的代码等方式,那么这个问题就无需关注。只有在已经验证有必要时,处理明显需要本地化值的情况才是边缘情况。

编辑说明:需要澄清的是,我并不建议您出于任何性能原因普遍避免使用本地值。正如@Guffa所指出的,影响可以忽略不计。我想指出的是属性通常有其存在的原因,访问值应默认经过属性。


我还有另一个更复杂的情况,其中我有3个复杂对象,存储为1个对象中的属性... 是否将这些更复杂的对象本地化是一个好主意? - JL.
@JL 不需要。只需使用属性即可。 - Rex M
1
根据惯例,属性通常不应在getter或setter中执行任何实质性操作。因此,我认为如果您仅通过反射器、文档或查看自己的代码来验证属性的行为良好,那么这并不是什么问题。@DavidAndres - Rex M
与其仅使用getter/setter,属性还可以执行一些规则验证等操作。想象一下,在每次访问时都执行大量不必要的计算的循环!即使CPU的架构也是建立在缓存概念的基础上的,整体性能至关重要。 - Akash Kava
2
@Akash 只有在出现问题时性能才很重要。如果我们还没有对代码进行分析并确定瓶颈,那么目前还不存在问题。 - Rex M
显示剩余3条评论

1

不要害怕声明本地变量,这几乎是免费的。在32位系统上,字符串变量将使用四个字节的堆栈空间。分配局部变量的堆栈帧总是被创建的,因此分配变量不需要额外的执行时间。

然而,是否应该使用局部变量应该基于情况中更正确的选择。除非你有一个循环,在其中你使用像一千次或更多这样的值,否则性能差异甚至几乎无法测量。

(当然,这是基于假设属性已经正确实现,所以读取属性并不昂贵。)


0

对于简单的函数和常规属性,这应该不会让你担心。
然而,这似乎是一个很好的关于lambda表达式的问题,快速测试表明如果你有很多属性引用(这类似于这里所做的),那么它可能是值得的:

class Program
{
    public static int Property { get; set; }

    static void Main(string[] args)
    {
        Property = 3;
        int mainVar = Property;

        for (int run = 0; run < 5; run++)
        {
            Stopwatch s = new Stopwatch();
            Action useProperty = () =>
            {
                double res;
                for (int counter = 0; counter < 10000000; counter++)
                    res = Math.Cos(Property);
            };
            s.Start();
            useProperty();
            Console.WriteLine("Property Direct   : {0}", s.Elapsed);
            s.Reset();

            Action useMainVar = () =>
            {
                double res;
                for (int counter = 0; counter < 10000000; counter++)
                    res = Math.Cos(mainVar);
            };
            s.Start();
            useProperty();
            Console.WriteLine("Variable from Main: {0}", s.Elapsed);
            s.Reset();

            Action useLocalVariable = () =>
            {
                int j = Property;
                double res;
                for (int counter = 0; counter < 10000000; counter++)
                    res = Math.Cos(j);
            };
            s.Start();
            useLocalVariable();
            Console.WriteLine("Lambda Local      : {0}", s.Elapsed);
            Console.WriteLine();
        }
        Console.ReadKey();
    }
}
结果:
直接属性:00:00:00.6410370
来自主函数的变量:00:00:00.6265704
Lambda局部变量:00:00:00.2793338
直接属性:00:00:00.6380671 来自主函数的变量:00:00:00.6354271 Lambda局部变量:00:00:00.2798229
直接属性:00:00:00.6337640 来自主函数的变量:00:00:00.6280359 Lambda局部变量:00:00:00.2809130
直接属性:00:00:00.6286821 来自主函数的变量:00:00:00.6254493 Lambda局部变量:00:00:00.2813175
直接属性:00:00:00.6279096 来自主函数的变量:00:00:00.6282695 Lambda局部变量:00:00:00.2783485

0

大多数较小的属性都会被JIT自动内联,因此对于这些属性来说,性能提升并不明显。

对于非平凡的属性,当缩短查找链时,确实可以节省时间。优化器不能删除冗余调用,因为它无法确定它们是否具有副作用,否则它将改变程序的语义。

有一个例外情况,您不应该将属性值保存到本地:

for (int i=0; i<myArray.Length; i++) { /* do something with myArray[i] */ }

这个模式被JIT识别,它会自动删除数组访问边界测试。

但是如果你这样做:

int len = myArray.Length;
for (int i=0; i<len; i++) { /* do something with myArray[i] */ }

那么边界测试就不能被移除,因为优化器永远无法确定变量 len 是否会被篡改。

所以在“优化”之前三思而后行。


获取一次属性并重复使用该值除了速度优势之外,还有一个好处:如果将属性读取一次到变量中,则每次使用该变量都保证产生相同的值。相比之下,如果代码重复调用属性getter,则检索到的值可能会发生更改。确实,如果属性发生更改,则使用旧值可能不一定比使用新值更好,但是使用一致的值不太可能引入Heisenbugs,而使用不一致的值则可能会引入这种错误。 - supercat

0
通常情况下,您不需要使用“this.”来引用同一类中的属性或字段。只有在存在命名冲突时才会使用它。微软的naming guidelines避免了这种冲突,所以我不使用它。 :)
另外,如果您有一个单独的方法引用了30-40次的属性,那么很可能存在Long Method问题。

编辑:我并不打算引起有关使用“this”的宗教战争。( 请参见您是否在Java中使用'this'前缀实例变量? ) 在我们公司,我们在字段前使用下划线(这是另一个热门话题); 再加上我们(否则非匈牙利)的命名约定,就可以很明显地看出哪些是属性。使用ReSharper, 我们甚至可以做一些不同颜色编码参数等事情。个人而言,我认为“this”是噪音,并遵循ReSharper的建议将其删除。

最终,我仍然坚持我的答案。虽然我非常重视可读性,但如果您无法快速轻松地确定方法正在执行的操作(使用或不使用“this”),那么它可能太长了。


3
确实,你不需要使用这个......但是这是更好的做法,因为其他人(包括你自己)看到代码时可以一眼看出该属性是类的成员,而不必深入查找。不确定是否有任何编译器方面的好处,但我总是对类的属性或字段使用这个...... - JL.
1
没有编译器的好处,但清晰度是最重要的。 - annakata
1
我同意JL的观点 - 很少使用this会损害代码的清晰度。 - Rex M

-1

我进行了性能计算,虽然很微小,但每个微小的增量都可以在执行代码时节省大量的CPU周期,例如在Web服务器、网络活动等方面同时执行代码数百次。

C#本地变量缓存统计,您也可以下载代码并进行测试。


你无法测试这个 - 它完全取决于属性 get 实际执行的操作。 - annakata
1
Profileing不会猜测属性实际上在做什么,你的答案是正确的,如果属性不是每次都在计算大量内容,那么它是微小的优化,但你调用了100个你不知道内部情况的属性!!你如何确保呢? - Akash Kava
1
@Akash,请确保使用Reflector,或使用类似RedGate Profiler的工具来查看属性调用是否导致性能瓶颈。 - Rex M
@Rex M,每次我调用属性时都需要这样做吗?每次调用内部函数时都需要这样做吗?我认为这样做太麻烦了,不如遵循正确的实践方法。 - Akash Kava
1
我知道现实可能有所不同,但人们不应该编写执行大量计算的属性。这是误导性的。如果需要执行一些密集的计算,请使用方法。 - Ed S.
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接