【问题标题】:Efficiency of method call in for loop conditionfor循环条件中方法调用的效率
【发布时间】:2015-03-30 10:54:04
【问题描述】:

我正在编写一个游戏引擎,其中包含在 ArrayList 中的一组对象使用 for 循环进行迭代。显然,效率是相当重要的,所以我想知道循环的效率。

for (String extension : assetLoader.getSupportedExtensions()) {
    // do stuff with the extension here
}

其中getSupportedExtension() 返回ArrayListStrings。我想知道是否每次循环遍历新扩展时都会调用该方法。如果是这样,这样做会更有效吗:

ArrayList<String> supportedExtensions = ((IAssetLoader<?>) loader).getSupportedExtensions();

for (String extension : supportedExtensions) {
    // stuff
}

?提前致谢。

【问题讨论】:

  • 一般来说,如果您担心性能......然后考虑进行真正的分析。
  • @EddyG - 不一定..您不必总是进行分析..考虑OP的问题..调查字节码会给你正确的答案..在字节码中,方法在这两种情况下只会被调用一次..
  • 你有一个类似的帖子来回答你的问题:stackoverflow.com/questions/6093537/for-loop-optimization
  • @TheLostMind 我同意有低悬的果实。但进一步的优化应该以运行时行为的评估为指导。否则你可能会浪费你的时间。
  • @EddyG - 我通常会在进行分析之前进行字节码分析。但那就是我:P

标签: java performance game-engine


【解决方案1】:

按规范,成语

for (String extension : assetLoader.getSupportedExtensions()) {
  ...
}

扩展到

for (Iterator<String> it = assetLoader.getSupportedExtensions().iterator(); it.hasNext();)
{
    String extension = it.next();
    ...
}

因此,您询问的调用仅在循环初始化时发生一次。它是迭代器对象,其方法被重复调用。

但是,如果您真的对应用程序的性能感兴趣,那么您应该确保您专注于大获全胜,而不是像这样的小人物。几乎不可能让 getter 调用成为任何代码段中的瓶颈。这对于在 HotSpot 上运行的应用程序来说是双倍的,它将内联该 getter 调用并将其转换为直接字段访问。

【讨论】:

  • 好的,谢谢。我忘记了 getter 内联,但这会消除方法调用中的瓶颈。非常感谢,我可能应该专注于更大的瓶颈,但这不会降低代码的可读性,所以我只是想检查一下对此的微优化是否会提供任何优势。
  • “瓶颈”的定义是“代码中占用大量时间的地方”。我认为您将其与“开销”混淆了。
  • 是的,谢谢 - 这就是我的意思。我是这种优化的新手 ;)
  • 现在请记住:虚拟方法调用的开销是几纳秒。单个 CPU 缓存未命中的惩罚是 60-100 ns。因此,如果您删除了方法调用,但仍必须从主 RAM 中获取实际数据,则方法调用的开销将变得无关紧要。这就是为什么要确定真正的间接费用是非常棘手的原因。
  • 如果你足够了解HotSpot会做什么事,你可以从源代码间接控制结果。未来的 Java 版本将为打包数组和值类型提供更多控制。
【解决方案2】:

不,方法assetLoader.getSupportedExtensions()在循环的第一次迭代之前只调用一次,用于创建增强for循环使用的Iterator&lt;String&gt;

两个 sn-ps 将具有相同的性能。

【讨论】:

  • 请注意,编译器会优化您的代码以确保方法调用发生在循环开始之前
【解决方案3】:
  1. 直接成本。

因为正如人们之前所说,以下

for (String extension : assetLoader.getSupportedExtensions()) {
  //stuff
}

变成

for (Iterator<String> it = assetLoader.getSupportedExtensions().iterator(); it.hasNext();) {
    String extension = it.next();
    //stuf
}

getSupportedExtensions() 被调用一次,并且您的两个代码 sn-ps 具有相同的性能成本,但不是通过列表的最佳性能,因为...

  1. 间接成本

这是实例化和使用新的短期对象的成本 + 方法 next() 的成本。方法 iterator() 准备一个 Iterator 的实例。因此,需要花时间实例化对象,然后(当该对象变得无法访问时)对它进行 GC。总的间接成本不是这么太多(大约 10 条指令为新对象分配内存 + 一些构造函数指令 + 大约 5 行 ArrayList.Itr.next() + 从Eden on minor GC),但我个人更喜欢索引(甚至是普通数组):

ArrayList<String> supportedExtensions = ((IAssetLoader<?>) loader).getSupportedExtensions();

for (int i = 0; i < supportedExtensions.size(); i++) {
    String extension = supportedExtensions.get(i);
    // stuff
}

当我必须在我的应用程序的主路径中频繁地遍历列表时,过度迭代。其他一些具有隐藏成本的标准 Java 代码示例是一些字符串方法(substring()、trim() 等)、NIO 选择器、将它们存储在集合中的原语装箱/拆箱等。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-16
    • 2015-07-23
    • 2019-11-06
    相关资源
    最近更新 更多