简体   繁体   English

继续调用 array.length 或 list.size() 的性能损失

[英]Performance loss of continued call to array.length or list.size()

I have seen people say to cache the values of size for a list or length for an array when iterating, to save the time of checking the length/size over and over again.我见过有人说在迭代时缓存列表的size值或数组的length值,以节省一遍又一遍检查长度/大小的时间。

So所以

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

Would be turned into会变成

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

But since Array#length isn't a method, just a field, shouldn't it not have any difference?但是既然Array#length不是一个方法,只是一个字段,它不应该没有任何区别吗? And if using an ArrayList, size() is just a getter so shouldn't that also be the same either way?如果使用 ArrayList, size()只是一个吸气剂,那么这两种方式不应该相同吗?

It is possible the JIT compiler will do some of those optimizations for itself. JIT 编译器可能会为自己做一些优化。 Hence, doing the optimizations by hand may be a complete waste of time.因此,手动进行优化可能完全是浪费时间。

It is also possible (indeed likely) that the performance benefit you are going to get from hand optimizing those loops is too small to be worth the effort.也有可能(确实很可能)从手动优化这些循环中获得的性能优势太小,不值得付出努力。 Think of it this way:这样想:

  • Most of the statements in a typical program are only executed rarely典型程序中的大多数语句只很少执行
  • Most loops will execute in a few microseconds or less .大多数循环将在几微秒或更短的时间内执行。
  • Hand optimizing a program takes in the order of minutes or hours of developer time.手动优化程序需要几分钟或几小时的开发时间。
  • If you spend minutes to get a execution speedup that is measured in microseconds, you are wasting your time.如果您花费几分钟来获得以微秒为单位的执行加速,那么您就是在浪费时间。 Even thinking about it too long is wasting time.即使得太久也是在浪费时间。

The corollary is that:推论是:

  • You should benchmark your code to decide whether you need to optimize it.您应该对代码进行基准测试以确定是否需要对其进行优化。
  • You should profile your code to figure out which parts of your code is worth spending optimization effort on.您应该分析您的代码,以确定您的代码的哪些部分值得花费优化精力。
  • You should set (realistic) performance goals, and stop optimization when you reach those goals.您应该设置(现实的)性能目标,并在达到这些目标时停止优化。

Having said all of that:说了这么多:

  • theArr.length is very fast, probably just a couple of machine instructions theArr.length非常快,可能只有几条机器指令
  • theList.size() will probably also be very fast, though it depends on what List class you are using. theList.size()可能也会非常快,但这取决于您使用的List class 。
  • For an ArrayList the size() call is probably a method call + a field fetch versus a field fetch for length .对于ArrayListsize()调用可能是方法调用 + 字段获取与length的字段获取。
  • For an ArrayList the size() call is likely to be inlined by the JIT compiler... assuming that the JIT compiler can figure that out.对于ArrayList ,JIT 编译器可能会内联size()调用......假设 JIT 编译器可以解决这个问题。
  • The JIT compiler should be able to hoist the length fetch out of the loop. JIT 编译器应该能够将length提取提升到循环之外。 It can probably deduce that it doesn't change in the loop.它可能会推断它在循环中没有改变。
  • The JIT compiler might be able to hoist the size() call, but it will be harder for it to deduce that the size doesn't change. JIT 编译器可能能够提升size()调用,但它更难推断出大小没有改变。

What this means is that if you do hand optimize those two examples, you will most likely get negligible performance benefit.这意味着如果您手动优化这两个示例,您很可能会获得微不足道的性能优势。

In general the loss is negligible.一般来说,损失可以忽略不计。 Even a LinkedList.size() will use a stored count, and not iterate over all nodes.即使是LinkedList.size()也会使用存储的计数,而不是遍历所有节点。

For large sizes you may assume the conversion to machine code may catch up, and optimize it oneself.对于大尺寸,您可能会假设转换为机器代码可能会赶上并自行优化。

If inside the loop the size is changed (delete/insert) the size variable must be changed too, which gives us even less solid code.如果在循环内部改变了大小(删除/插入),那么大小变量也必须改变,这给我们提供了更不可靠的代码。

The best would be to use a for-each最好的方法是使用 for-each

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

You might also use the somewhat more costing Stream:您也可以使用成本更高的 Stream:

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

Streams can be executed in parallel.流可以并行执行。

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

And last but not least you may use standard java code for simple loops:最后但并非最不重要的一点是,您可以使用标准 java 代码进行简单循环:

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

We are talking here about micro-optimisations .我们在这里谈论的是微优化 I would go for elegance.为了优雅,我会选择 go。

Most often the problem is the used algorithm & datastructurs.最常见的问题是使用的算法和数据结构。 List is notorious, as everything can be a List . List是臭名昭著的,因为一切都可以是List However Set or Map often provide much higher power/expressiveness.然而SetMap通常提供更高的功率/表现力。

If a complex piece of software is slow, profile the application.如果一个复杂的软件速度很慢,请分析应用程序。 Check the break lines: java collections versus database queries, file parsing.检查断线:java collections 与数据库查询、文件解析。

It is best not to call a method on every loop round, so you should avoid calling size() on the condition part that is evaluated on every round.最好不要在每一轮循环中调用方法,因此应避免在每轮评估的条件部分调用 size()。 If you use a for each loop, this does not apply.如果您使用 for each 循环,则不适用。

for (int i = 0; i < foos.bar(); i++) // slow

for (int i = 0, size = foos.bar(); i < size; i++) // fast

for (Foo foo : foo.bar()) // fast

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM