Android 性能 - “避免内部获取器/设置器”

Posted

技术标签:

【中文标题】Android 性能 - “避免内部获取器/设置器”【英文标题】:Android Performance - 'Avoid Internal Getters/Setters' 【发布时间】:2011-10-06 16:24:22 【问题描述】:

只需在开发网站上阅读此内容:

Avoid Internal Getters/Setters

在 C++ 等本地语言中,通常使用 getter(例如 i = getCount())而不是直接访问字段(i = mCount)。这对于 C++ 来说是一个很好的习惯,因为编译器通常可以内联访问,如果需要限制或调试字段访问,可以随时添加代码。

android 上,这是一个坏主意。虚拟方法调用很昂贵,比实例字段查找要贵得多。遵循常见的面向对象编程实践并在公共接口中使用 getter 和 setter 是合理的,但在类中您应该始终直接访问字段。

在没有 JIT 的情况下,直接访问字段比调用普通的 getter 快大约 3 倍。使用 JIT(直接字段访问与访问本地一样便宜),直接字段访问比调用普通 getter 快约 7 倍。这在 Froyo 中是正确的,但在未来 JIT 内联 getter 方法时会有所改进。

2019/05 更新:在当前版本中,上述材料已从文档中删除!

也就是说你会在类中使用字段访问:

 public class MyObject 

    public Object innerObject; // This would be private if I was using a getter

    public void doSomeStuff()
          if(innerObject)        // Within class access like this
                 // ....
          
    

    public Object getInnerObject()  // This would be removed if I was using field access
         return innerObject;
    
 

但是从另一个对象访问呢?:

 public class SecondObject 

      public void doSecondSomething()
                MyObject ob = new MyObject();
                Object inner;

                //This is my question basically (from an Android performance perspective)   
                inner = ob.getInnerObject();
                // OR 
                inner = b.innerObject

       

 

【问题讨论】:

只是指出这一点,请记住,Dalvik 不是 JVM,这种做法不应被视为普通 Java 开发中的通用方式。 您的报价说明了一切,不是吗? 遵循常见的面向对象编程实践并在公共接口中使用 getter 和 setter 是合理的。另外:不要过度设计。除非您进行大量调用,否则只需使用 getter。或有速度问题。 【参考方案1】:

使用内部 getter 和 setter 对性能的影响也适用于外部 getter 和 setter。

但是,在外部情况下,getter 和 setter 在其他领域具有显着优势;例如保留封装、减少有害耦合、使您的代码更易于维护等等。因此,尽管可能会导致性能下降,但使用 getter 和 setter 通常被认为是最佳实践

性能下降是由于旧版 Android JIT 编译器的限制所致。使用 Gingerbread 后,这种情况得到了显着改善(请参阅 - https://***.com/a/4930538/139985 ...并注意谁写了这个答案!)并继续改善。事实上,在当前 (2019) 版本的 Performance Tips 中,有关内部 getter 和 setter 的整个部分已被删除。

一般来说,为劣质平台“调整”代码是个坏主意,尤其是在有可能出现更好的平台时。

【讨论】:

等等,android JIT 没有内联琐碎的函数调用?我的意思是,这是任何 JIT 的基本优化之一——确保它必须确保类没有被覆盖等等,但这很容易做到 99/100。难以置信。 @Voo - 出于商业原因,现在有必要提供第二流的东西,而不是等待 6 个月直到它成为第一流。 Java 和 javascript(以及 Windows Vista 和...)也发生了同样的事情。如果您忽略历史,那只会“难以置信”。【参考方案2】:

虽然b.innerObject 更快,但随着技术的进步(更好的 cpu、JIT 等),这两个选项之间的差异会越来越小。

唯一重要的一点是在始终执行的密集循环中完成。例如,在游戏的onDraw 方法中,当您遍历数百个对象时。

【讨论】:

【参考方案3】:

请记住,仅当相关成员每秒被访问数千次时,这些性能注意事项才相关。

直接访问可能是个好主意的一个很好的例子是游戏的场景图 (libgdx)

public abstract class Actor 
    public Group parent;
    public final String name;
    public boolean touchable = true;

    public float x;
    public float y;
    public float width;
    public float height;
    public float originX;
    public float originY;
    public float scaleX = 1;
    public float scaleY = 1;
    public float rotation;
    public final Color color = new Color(1, 1, 1, 1);

【讨论】:

【参考方案4】:
// this is faster
inner = b.innerObject

// but this won't hurt performance much because
// it's assumed that it will be rare
inner = ob.getInnerObject();

【讨论】:

【参考方案5】:

Getter 和 Setter 总是有开销,因为它们是函数调用。当您在同一个对象中时,您可以通过不使用它们来优化您的代码,因为您知道它们的用途,并且您不需要从自己的对象中抽象/封装它们。

我觉得你也需要换个角度看:

    没有 getter/setter 会破坏常见的 oops 做法吗?如果您正在制作其他人将使用的对象/模块,您将不希望有直接引用。

    你真的不想像最后那样多次使用 getter/setter,除非 sh!* 它优化出来的函数调用会产生开销。

你真的需要根据具体情况进行优化,如果我正在构建两个模块,其中一些组件只能相互访问,我可能会创建一个静态字段,否则我会坚持使用 getter/setter

【讨论】:

“Getter 和 Setter 总是有开销,因为它们是函数调用”在任何现代 JIT 上都不是这样 - 在 Hotspot 中会被内联,除非在极少数情况下(即 setter 真的被覆盖在子类和 jit 不能保证对象的身份之一 - 或者说,几乎从不)【参考方案6】:

在性能方面,访问this.fieldthat.field 没有区别。

实例字段更容易被托管它的对象访问的感觉只是一种语法错觉。

OO 明智,严肃地说,Android 应用程序可以变得多复杂?许多 OO 口头禅来自构建怪物应用程序。如果你的小应用使用结构体之类的对象,有什么大不了的?

即使在一个巨大的应用程序中,只要它在内部,并且所有访问字段的源代码都可用于重构,那么暴露字段就没有问题。

【讨论】:

【参考方案7】:

为了记录,setter 和 getter(在 Java 中)的另一个问题是它们很难用。

比方说下一个练习,我们需要在一个对象的字段中修改一个字段

在java中是:

   object.getField().getSubField().setField3("hello"); // ugly

虽然在 C# 中是相同的代码(即使有封装)

   object.Field.SubField.Field3="hello";  // fine

因此,在 Java(或 android)中使用公共字段要干净得多:

  object.field.subfield.field3="hello"; // fine too

【讨论】:

啊,但这是 TrainWreck 反模式,只与您最近的邻居交谈并拥有代码 object.setField3("hello"); c2.com/cgi/wiki?TrainWreck 我发现 getter/setter 范式卖得太多了。 99% 的时间它们只是传递给成员变量。如果您打算同时实现它们,只需跳过 getter/setter 并将变量公开。工程师不断引用良好的封装(或更具体地说是隔离)作为 getter/setter 的主要好处。代码绑定到 getter/setter 的语法,这与绑定到类成员变量相同。所以没有收获。收益是如果您在将来更改实现或派生类重载。当发生这种情况时,让 getter/setter @user1959190 没错。但是,我不同意关于工程师的评论。真正的工程师专注于效率和结果。事实上,经验丰富的工程师经常作弊。但是,有那么多程序员不是工程师,而是更接近于科学家。我使用 setter 和 getter 是因为它的标准和团队合作。然而,我畏缩,因为它在 99% 的时间里是无用的,而当它有用时,它隐藏了实现并且它真的很危险。 不使用 getter/setter 的一个很好的例子是 Android LatLng 类 developers.google.com/android/reference/com/google/android/gms/… 我也同意,所以让我们将组从工程师更改为开发人员。

以上是关于Android 性能 - “避免内部获取器/设置器”的主要内容,如果未能解决你的问题,请参考以下文章

Android性能优化总提纲

Android性能优化总提纲

Android性能优化总提纲

Android性能优化

(七) 中篇 Android 性能优化 Perfetto 文件分析

Android性能优化(八)--Android图片内存优化