【问题标题】:Is constructor of class that inherits from another class less effective?从另一个类继承的类的构造函数效率较低吗?
【发布时间】:2011-04-25 14:09:58
【问题描述】:

问题

在我的应用程序中,我创建了大量仅保存一些数据的“小”类实例。

我知道创建类(即构造函数调用)的成本很高。我的问题是:如果我让这个小类从另一个类继承,成本会更高吗?有些字段只会移动到超类,所以基本上我可以使用

注意:这个问题专门针对.NET中的性能。


问题举例情况及演示

考虑这两种情况:

1. case 只包含一个类。它是原始类:

public class ShapeWithOffset{
    public double XOffset { get; private set; }
    public double YOffset { get; private set; }
    public IShape Shape{ get; private set; }

    public ShapeWithOffset(double xOffset, double yOffset, IShape shape){
        //check if arguments are correct/throw ArgumentException if not
        XOffset = xOffset;
        YOffset = yOffset;
        Shape = shape;
    }
    //equality members
}

2。 case 由 2 个类组成,其中 second 从第一个继承。第二类ShapeWithHorizontalAndVerticalOffset提供与1.case中ShapeWithOffset相同的功能。

public class ShapeWithHorizontalOffset{
    public double XOffset { get; private set; }
    public IShape Shape { get; private set; }

    public ShapeWithHorizontalOffset(double xOffset, IShape shape){
        //check if arguments are correct/throw ArgumentException if not
        XOffset = xOffset;
        Shape = shape;
    }
    //equality members
}

public class ShapeWithHorizontalAndVerticalOffset : ShapeWithHorizontalOffset{
    public double YOffset { get; private set; }

    public ShapeWithHorizontalAndVerticalOffset(double xOffset, double yOffset, 
                                                IShape shape) : base(xOffset, shape){
        //check if yOffset is correct/throw ArgumentOutOfRangeException if not
        Yffset = yOffset;
    }
    //equality members
}

我的问题是:命令var foo = new ShapeWithOffset(20, 10, shape);var boo = new ShapeWithHorizontalAndVerticalOffset(20, 10, shape); 快吗?

如果我使用组合而不是继承,那将是......但是继承呢?

【问题讨论】:

  • 您最多可以为每个对象实例节省一个函数调用,也就是说,如果您原谅我的表述,peanuts 在性能方面与您的可维护性和表现力相比从继承中获得。然而,您是否应该实际上在您的特定情况下使用继承是非常主观的并且有待商榷(这就是为什么这是评论而不是答案)。
  • 这个问题在 SO 上出现了很多,它总是让我感到困惑。也就是说,“这里有两种编写代码的方法;哪个更快?”的问题。您已经以两种方式编写了代码; 尝试两种方式,看看哪个更快。人们似乎认为社区有一些神奇的方法可以知道两件事中哪一个更快,而无需尝试。没有这种神奇的方法。试试看,测量结果,然后你就会知道。
  • @drasto:让我这样说吧:我也不具备你想要的知识,我向你保证,我对编译器的了解比绝大多数人都多。当我需要性能问题的答案时我用两种方式编写代码并尝试一下。多年的经验告诉我,我对真正造成性能差异的猜测常常是大错特错。 经验调查没有神奇的替代品。
  • @drasto:此外,我们都不知道您所做的事情,即您认为重要的性能差异。有些人认为几纳秒的差异非常重要;有些人认为十分钟的差异无关紧要。我们谁也无法告诉您哪些绩效指标对您和您的客户很重要。
  • @drasto:我不认为这很清楚。例如考虑碱基调用是否内联的问题。可能是,可能不是,不尝试我不知道。现在考虑内联的性能成本;它删除了一个间接级别,这是一些指令,这是更多的时间。它增加了调用站点的代码大小,这可能会改变内存中代码的布局,这可能会导致额外的缓存未命中。现在,您已经用一纳秒换取了潜在的多微秒失误!不尝试怎么知道是不是这样?

标签: c# .net performance inheritance constructor


【解决方案1】:

这闻起来像premature optimization

您问题中的关键词是“...我认为...”。如果您知道自己有性能问题,那么确定瓶颈并对其进行优化。如果您预计会出现性能问题,请不要!以您建议的方式使用继承导致任何性能瓶颈的可能性极小。但是,如果您真的想提前知道,请提前测试。

【讨论】:

  • 我认为这是一个答案,但我仍然想知道如果没有继承的类更快怎么办。我的应用程序肯定会出现性能问题:有任务将运行数十分钟,用户将不得不等待。尽管我创建了所有启发式算法,但在最坏的情况下算法是指数级的。我能做的任何加速对我来说都很有趣。但我也想知道这个问题的一般答案:它是否更快?
  • 我在我的问题中删除了唯一的词“......我认为......” - 它们不是关于问题,而是关于另一个案例(如果使用了组合)。我的问题的上下文没有改变。
【解决方案2】:

这几乎是一样的。 基 .ctor 的调用将由 JITter 内联,并且仅执行一次分配。作为开销,它只会花费您几个字节。

如果您不确定,请尝试一个小基准。

【讨论】:

    【解决方案3】:

    我使用分析器对代码进行微优化的经验是方法调用开销可以忽略不计。我在性能改进方面的主要收获几乎总是:缓存常用计算、优化深层内部循环(通常通过使用直接数组索引而不是枚举)和避免不必要的装箱。当然,通过改进算法以减少迭代次数,所有这些都相形见绌(通常通过引入启发式算法,而不是提前排除分支或将问题转换为单独的“聚合”和“细节”阶段。)

    【讨论】:

    • +1 即使它没有直接回答我的问题,因为它是开发我的应用程序的有趣知识。除了“将问题转化为单独的‘聚合’和‘细节’阶段”之外,我理解您所写的所有内容。你能解释一下你的意思或提供一些关于主题链接的学习材料吗?
    • @drasto,这在很大程度上取决于您的特定问题,但许多问题可以在粗略的细节水平上解决,从而缩小必须进行更精细分析的特定区域。这是机器视觉中非常典型的优化,例如,您可以快速匹配较低分辨率图像中的模式,从而限制您必须搜索更高分辨率匹配的范围。创建中间低分辨率图像需要额外的开销,但这可以通过减少精细细节所需的迭代来弥补。
    • @drasto,附带说明一下,如果您遇到性能问题或需要为您的应用程序考虑这些问题,我强烈建议您投资商业分析工具。其中有很多可用(JetBrains dotTrace 和 RedGate ANTS Profiler 是我熟悉的两个。)它们将为您节省大量时间和精力,因为它们会分析您实际运行的代码并在非常精细的细节。 (确保您正在运行 Release 构建代码。)您可能会惊讶于瓶颈实际出现在哪里。
    【解决方案4】:

    首先,正如我在评论中所说,调用基本构造函数(和虚方法)不会影响性能,足以证明失去它赋予程序的可维护性和表达性是合理的。如果在这种情况下性能确实是个问题,那么结构化语言就不存在了。

    现在,从设计 的角度来看,问题归结为在您的情况下继承是否真的有利可图。这提示了一个问题:为什么ShapeWithOffset不能从Shape派生?如果可能的话,我可能会切断复杂性并执行以下操作:

    public class ShapeWithOffset : Shape
    {
        public ShapeWithOffset(double xOffset)
        : this(xOffset, 0.0) {}
    
        public ShapeWithOffset(double xOffset, double yOffset)
        {
            // TODO - Check if arguments are correct/throw ArgumentException if not.
            XOffset = xOffset;
            YOffset = yOffset;
        }
    }
    

    在 C# 4 中,您甚至可以编写:

    public ShapeWithOffset(double xOffset, double yOffset = 0.0)
    {
        // TODO - Check if arguments are correct/throw ArgumentException if not.
        XOffset = xOffset;
        YOffset = yOffset;
    }
    

    【讨论】:

    • ShapeWithOffset 不能从 Shape 派生,因为我在示例中简化了代码。在真正的代码类中,没有类Shape,而是接口IShapeShapeWithOffset 实际上是通用的,所以它被声明为:public class ShapeWithHorizontalOffset<TB> where TB: IShape。我将编辑我的问题以使用IShape 而不是Shape 以表明它是接口。
    • @drasto,原则仍然有效:我建议您定义一个 class ShapeWithOffset<T> where T : IShape 并使用构造函数覆盖而不是轴的子类。考虑一下你以后想添加 Z 轴,哪种方式更容易理解:整个子类,还是构造函数覆盖? :)
    • 是的,我当然想到了这一点。其实那是我的第一个设计。我把它改成这个是因为我的应用程序中有一些类可以在一维空间中定位形状(对齐)。他们没有y轴。我认为如果有一些YOffset 属性可能会混淆。而且它也将是无用的和巨大的空间(因为我写了很多 ShapeWithOffset 将被创建)所以我创建了这个继承。你还认为他们最好是一个班级吗?
    【解决方案5】:

    正如 kuchana 在他的书(设计模式)中所说,一般谈论继承(不是专门针对你的情况),如果没有必要,尽量避免继承,因为它很复杂。 而且我认为运行时绑定可能会导致小的性能问题,但继承的主要开销是它的设计复杂性,这比微小的性能问题重要得多。

    编辑

    我现在意识到这个问题是关于构建 :D 的。 以下段落在 java 中是正确的,也许在 .Net 中也是如此。 我认为,如果您在子类中显式定义构造函数,则不会有任何区别,因为编译器会首先在子类中查找构造函数,如果找不到,它将在继承层次结构中上升,直到他!找到合适的构造函数。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-03
      相关资源
      最近更新 更多