【问题标题】:Performance loss of continued call to array.length or list.size()继续调用 array.length 或 list.size() 的性能损失
【发布时间】:2022-01-28 04:04:01
【问题描述】:

我看到有人说在迭代时将 size 的值缓存为列表或将 length 的值缓存为数组,以节省反复检查长度/大小的时间。

所以

for (int i = 0; i < someArr.length; i++) // do stuff
for (int i = 0; i < someList.size(); i++) // do stuff

会变成

for (int i = 0, length = someArr.length; i < length; i++) // do stuff
for (int i = 0, size = someList.size(); i < size; i++) // do stuff

但是既然Array#length不是一个方法,只是一个字段,应该没有区别吧?如果使用 ArrayList,size() 只是一个 getter,那么这两种方法不应该也是一样的吗?

【问题讨论】:

  • 我认为性能损失可以忽略不计,但我希望能解释一下
  • @VGR 你一开始就不想以这种方式迭代 LinkedList。 i 会做什么?每次迭代都要遍历整个列表直到i
  • @VGR LinkedList 将大小存储在字段中,就像ArrayList 一样。所以size() 方法在以这种方式迭代LinkedList 时不是问题......
  • @user16320675 array.length 永远不会改变。它看起来像一个字段访问,但实际上并非如此。有一个专用的字节码指令,arraylength 用于获取长度,但根本无法更改以更改数组长度。
  • @user16320675 如果someArr 更改,您想要使用更改后的someArr.length,因为仅使用相应的长度可以保证您保持在范围内。这对优化器有真正的影响,因为这在为边界检查生成额外代码或依赖循环永远不会超出边界的已知不变量之间产生差异。这意味着您每次使用length = someArr.length或访问someArr.length都没有关系;重要的是,JIT/HotSpot 优化器是否可以证明someArr 在任何一种情况下都不会改变。

标签: java arrays performance for-loop arraylist


【解决方案1】:

JIT 编译器可能会为自己做一些优化。因此,手动进行优化可能完全是浪费时间。

也有可能(确实很可能)通过手动优化这些循环获得的性能优势太小,不值得付出努力。这样想:

  • 典型程序中的大多数语句很少执行
  • 大多数循环将在几微秒内执行或更短
  • 手动优化程序需要几分钟或几小时的开发时间。
  • 如果您花费几分钟来获得以微秒为单位的执行加速,您可能在浪费您的时间。即使考虑太久也是在浪费时间。

推论是:

  • 您应该对代码进行基准测试,以决定是否需要对其进行优化。
  • 您应该分析您的代码,以确定代码的哪些部分值得花费优化工作。
  • 您应该设置(现实的)性能目标,并在达到这些目标时停止优化。

说了这么多:

  • theArr.length 非常快,可能只有几条机器指令
  • theList.size()可能也会非常快,但这取决于您使用的 List 类。
  • 对于ArrayListsize() 调用可能是方法调用 + 字段提取,而不是 length 的字段提取。
  • 对于ArrayListsize() 调用可能会被 JIT 编译器内联...假设 JIT 编译器可以解决这个问题。
  • JIT 编译器应该能够提升length 提取出循环。它可能会推断它在循环中没有变化。
  • JIT 编译器可能能够提升size() 调用,但它更难推断出大小没有改变。李>

这意味着如果您手动优化这两个示例,您很可能会获得微不足道的性能优势。

【讨论】:

    【解决方案2】:

    一般来说损失可以忽略不计。即使是LinkedList.size() 也会使用存储的计数,而不是遍历所有节点。

    对于大尺寸,您可能会假设转换为机器代码可能会赶上,并自行优化。

    如果在循环内改变大小(删除/插入),大小变量也必须改变,这给我们的代码更不可靠。

    最好使用 for-each

    for (Bar bar: bars) { ... }
    

    您也可以使用成本更高的 Stream:

    barList.forEach(bar -> ...);
    Stream.of(barArray).forEach(bar -> ...);
    

    流可以并行执行。

    barList.parallelStream().forEach(bar -> ...);
    

    最后但并非最不重要的一点是,您可以使用标准 java 代码进行简单循环:

    Arrays.setAll(barArray, i -> ...);
    

    我们在这里谈论的是微优化。我会追求优雅。

    最常见的问题是使用的算法和数据结构。 List 是臭名昭著的,因为一切都可以是 List。然而SetMap 通常提供更高的力量/表现力。

    如果一个复杂的软件运行缓慢,请配置应用程序。检查断线:java 集合与数据库查询、文件解析。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-09-19
      • 2012-02-08
      • 2016-12-20
      • 1970-01-01
      • 2019-02-09
      • 1970-01-01
      • 1970-01-01
      • 2015-05-01
      相关资源
      最近更新 更多