属于公共领域有史以来OK?(Are public fields ever OK?)

2019-08-05 04:03发布

您可以先在肠道反应,像我一样一开始,读取整个问题,请。 我知道他们让你觉得脏,我知道我们都被烧毁之前,我知道这是不是“好作风”,但是,是公共领域不断好吗?

我的工作是创建并与工作在一个结构的内存模型相当大规模工程应用(从高层建筑什么桥脱落,无所谓)。 有参与这个项目几何分析和计算的吨。 为了支持这一点,该模型是由许多微小的不可变的只读结构来表示的东西,如点,线段等一些结构的(像点的坐标)被访问数十或数百万人的值一个典型的程序执行过程中的时间。 由于模型的复杂性和计算的体积,性能绝对是至关重要的。

我觉得,我们正在尽一切努力来优化我们的算法,性能测试,以确定瓶颈,使用正确的数据结构,等等,等等,我不认为这是过早优化的情况。 性能测试表明量级 (至少)性能通过对象上的属性访问字段时,直接而不是提升。 鉴于这一信息,而事实上,我们也可以公开的信息相同的属性,以支持数据绑定和其他情况...这行吗? 请记住,阅读不可改变的结构仅领域。 任何人都可以想出一个理由,我会后悔吗?

这里有一个样本测试应用程序:


struct Point {
    public Point(double x, double y, double z) {
        _x = x;
        _y = y;
        _z = z;
    }

    public readonly double _x;
    public readonly double _y;
    public readonly double _z;

    public double X { get { return _x; } }
    public double Y { get { return _y; } }
    public double Z { get { return _z; } }
}

class Program {
    static void Main(string[] args) {
        const int loopCount = 10000000;

        var point = new Point(12.0, 123.5, 0.123);

        var sw = new Stopwatch();
        double x, y, z;
        double calculatedValue;
        sw.Start();
        for (int i = 0; i < loopCount; i++) {
            x = point._x;
            y = point._y;
            z = point._z;
            calculatedValue = point._x * point._y / point._z;
        }
        sw.Stop();
        double fieldTime = sw.ElapsedMilliseconds;
        Console.WriteLine("Direct field access: " + fieldTime);

        sw.Reset();
        sw.Start();
        for (int i = 0; i < loopCount; i++) {
            x = point.X;
            y = point.Y;
            z = point.Z;
            calculatedValue = point.X * point.Y / point.Z;
        }
        sw.Stop();
        double propertyTime = sw.ElapsedMilliseconds;
        Console.WriteLine("Property access: " + propertyTime);

        double totalDiff = propertyTime - fieldTime;
        Console.WriteLine("Total difference: " + totalDiff);
        double averageDiff = totalDiff / loopCount;
        Console.WriteLine("Average difference: " + averageDiff);

        Console.ReadLine();
    }
}

结果:
直接的现场访问:3262
属性访问:24248
总的区别:20986
平均差异:0.00020986


这是只有 21秒,但为什么不呢?

Answer 1:

您的测试是不是真的是公平的基于属性的版本。 该JIT是足够聪明,内联简单的特性,使他们有相当于直接字段访问一个运行时的性能,但它似乎并没有足够的(今天)智能时属性访问的常数值来检测。

在你的榜样,的字段访问版本整个循环体优化掉,成为刚:

for (int i = 0; i < loopCount; i++)
00000025  xor         eax,eax 
00000027  inc         eax  
00000028  cmp         eax,989680h 
0000002d  jl          00000027 
}

而第二版本,实际上是在每次迭代执行所述浮点除法:

for (int i = 0; i < loopCount; i++)
00000094  xor         eax,eax 
00000096  fld         dword ptr ds:[01300210h] 
0000009c  fdiv        qword ptr ds:[01300218h] 
000000a2  fstp        st(0) 
000000a4  inc         eax  
000000a5  cmp         eax,989680h 
000000aa  jl          00000096 
}

使只有两个小的变化,以您的应用程序,使之更加切合实际,使这两个操作中的性能几乎相同。

首先,让他们不是常数和JIT是不是足够聪明,完全删除师随机的输入值。

从更改:

Point point = new Point(12.0, 123.5, 0.123);

至:

Random r = new Random();
Point point = new Point(r.NextDouble(), r.NextDouble(), r.NextDouble());

其次,确保每个循环迭代的结果某处使用:

每个循环之前,设置calculatedValue = 0,所以他们都开始在同一点。 每个循环调用Console.WriteLine后(calculatedValue.ToString()),以确保其结果是“拿来主义”,因此编译器不会优化它拿走。 最后,以便使用每次迭代循环体从“calculatedValue = ...”变为“calculatedValue + = ...”。

在我的机器,这些变化(与发布版本)产生以下结果:

Direct field access: 133
Property access: 133
Total difference: 0
Average difference: 0

正如我们预期,对于这些修改环路的86是相同的(除了环路地址)

000000dd  xor         eax,eax 
000000df  fld         qword ptr [esp+20h] 
000000e3  fmul        qword ptr [esp+28h] 
000000e7  fdiv        qword ptr [esp+30h] 
000000eb  fstp        st(0) 
000000ed  inc         eax  
000000ee  cmp         eax,989680h 
000000f3  jl          000000DF (This loop address is the only difference) 


Answer 2:

既然你处理与只读字段不可变对象,我会说,你已经打了一个情况下我没有找到公共领域是一个肮脏的习惯。



Answer 3:

IMO,“无公共领域”的规则是那些技术上是正确的规则之一,但除非你正在设计意图被公众使用的库是不可能的,如果你打破它给你带来任何问题。

以前我太大规模downvoted,我要补充一点, 封装是一件好事。 鉴于不变“Value属性必须为空,如果HasValue为假”,这样的设计是有缺陷的:

class A {
    public bool HasValue;
    public object Value;
}

然而,鉴于不变的,这样的设计也同样有缺陷:

class A {
    public bool HasValue { get; set; }
    public object Value { get; set; }
}

正确的设计

class A {
    public bool HasValue { get; private set; }
    public object Value { get; private set; }

    public void SetValue(bool hasValue, object value) {
        if (!hasValue && value != null)
            throw new ArgumentException();
        this.HasValue = hasValue;
        this.Value    = value;
    }
}

(甚至更好的将是提供一个构造函数初始化,使类不可变的)。



Answer 4:

我知道你觉得有点脏这样做,但规则和准则,以获得拍摄到地狱的时候性能就成了一个问题,这种情况并不少见。 例如,使用MySQL不少高流量的网站有数据复制和非规范化表。 其他人去更疯狂 。

这个故事的寓意 - 它可能对你不利被教导或建议的一切,但基准是不会说谎的。 如果它工作得更好,就去做。



Answer 5:

如果你真的很需要那额外的性能,那么它很可能是做正确的事。 如果你不需要额外的成效,则可能不是。

波多黎各马里亚尼有几个相关的帖子:

  • 十问对基于价值的编程
  • 十问对基于价值的程序:解决方案


Answer 6:

就个人而言,我会考虑使用公共领域唯一的一次是在一个非常特定于实现的私有嵌套类。

其他时候,我就觉得太“错”来做到这一点。

CLR将通过优化出来的方法/属性(在发布版本)照顾的性能,因此,不应该是一个问题。



Answer 7:

这并不是说我不同意其他的答案,或者与你的结论......但我想知道你在哪里得到数量级的性能差异统计的顺序。 据我了解C#编译器,任何简单的属性(比直接访问现场没有其他额外的代码),应该得到由JIT编译器直接存取反正内联。

即使在这些简单的情况下使用性能(在大多数情况下)的ADVANTEDGE是通过写它作为一个属性,你允许有可能修改属性未来的变化。 (虽然你的情况就不会是当然未来任何这样的变化)



Answer 8:

尝试编译发布版本,然后直接从exe文件,而不是通过调试运行。 如果应用程序是通过一个调试器中运行,则JIT编译器将不会内联属性访问。 我是不是能复制你的结果。 事实上,每个测试我跑表明,在执行时间几乎没有差别。

但是,和其他人一样,我不能完全oppossed直接字段访问。 特别是因为它很容易使外地私营和无必要化妆以后添加一个公共属性访问任何代码的修改去编译应用程序。

编辑:好吧,我最初的测试中使用int数据类型而不是双。 我看到用双打时的巨大差异。 随着整数直接与属性几乎是相同的。 随着双打属性访问约7倍比我的机器上直接访问慢。 这是有点令人费解给我。

此外,调试器之外运行的测试是非常重要的。 即使在发布版本的调试器增加了开销,这歪斜的结果。



Answer 9:

下面是一些场景中它是确定(从框架设计指南书):

  • 不要使用固定字段永远不变的常量。
  • 不要使用预定义的对象实例公共静态只读字段。

以及它是不是:

  • 不分配可变类型的实例只读字段。

从你说什么,我不知道为什么你不重要的性质没有得到由JIT内联?



Answer 10:

如果您修改您的测试中使用的分配,而不是直接访问在计算性能的临时变量,你会看到一个大的性能提升:

        sw.Start();
        for (int i = 0; i < loopCount; i++)
        {
            x = point._x;
            y = point._y;
            z = point._z;
            calculatedValue = x * y / z;
        }
        sw.Stop();
        double fieldTime = sw.ElapsedMilliseconds;
        Console.WriteLine("Direct field access: " + fieldTime);

        sw.Reset();
        sw.Start();
        for (int i = 0; i < loopCount; i++)
        {
            x = point.X;
            y = point.Y;
            z = point.Z;
            calculatedValue = x * y / z;
        }
        sw.Stop();


Answer 11:

也许我会重复别人,但这里是我的点太多,如果它可以帮助。

教导给你,你遇到这种情况时,需要实现轻松的一定水平的工具。

敏捷软件开发方法,说,你必须将产品第一交付给客户,无论你的代码可能是什么样子。 其次,你可以优化,使你的代码“美”,根据现有技术的编程状态。

在这里,您或您的客户需要的性能。 在项目中,性能是至关重要的,如果我理解正确。

所以,我想你会同意我的看法,我们不关心什么的代码可能看起来像或者它是否尊重“艺术”。 你有什么使它高性能和强大的! 属性让你的代码为“格式” /如果需要I O数据。 一个属性都有其自己的内存地址,然后查找其成员的地址,当您返回该成员的值,所以你有地址的两个搜索。 如果性能是关键等,只是做它,让你不变的成员公开。 :-)

这反映了一些人的角度来看也是如此,如果我正确读取。 :)

祝你有美好的一天!



Answer 12:

其功能封装类型应该使用属性。 类型的仅用于保存数据应该使用公共领域,除了一成不变的类(其中只读属性包装领域是可靠地保护他们免受修改的唯一方法)的情况下。 揭露成员公共领域基本上宣告“这些成员可以随时随意修改不为别的方面”。 如果有问题的类型是类类型,它进一步宣称“谁暴露了一个参考这件事将是允许接收者在他们认为合适的任何方式随时更改这些成员。” 虽然人们不应该在案件暴露的公共领域,其中这一宣布是不合适的,应该在公开的情况下这一宣布是适当的和客户端的代码可以从由此,能够假设有利于公共字段。



文章来源: Are public fields ever OK?