Efficiency of method call in for loop condition

2020-02-28 06:33发布

问题:

I am writing a game engine, in which a set of objects held in a ArrayList are iterated over using a for loop. Obviously, efficiency is rather important, and so I was wondering about the efficiency of the loop.

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

Where getSupportedExtension() returns an ArrayList of Strings. What I'm wondering is if the method is called every time the loop iterates over a new extension. If so, would it be more efficient to do something like:

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

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

? Thanks in advance.

回答1:

By specification, the idiom

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

expands into

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

Therefore the call you ask about occurs only once, at loop init time. It is the iterator object whose methods are being called repeatedly.

However, if you are honestly interested about the performance of your application, then you should make sure you're focusing on the big wins and not small potatoes like this. It is almost impossible to make a getter call stand out as a bottleneck in any piece of code. This goes double for applications running on HotSpot, which will inline that getter call and turn it into a direct field access.



回答2:

No, the method assetLoader.getSupportedExtensions() is called only once before the first iteration of the loop, and is used to create an Iterator<String> used by the enhanced for loop.

The two snippets will have the same performance.



回答3:

  1. Direct cost.

Since, as people said before, the following

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

transforms into

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

getSupportedExtensions() is called once and both of your code snippets have the same performance cost, but not the best performance possible to go through the List, because of...

  1. Indirect cost

Which is the cost of instantiation and utilization of new short-living object + cost of method next(). Method iterator() prepares an instance of Iterator. So, it is need to spend time to instantiate the object and then (when that object becomes unreachable) to GC it. The total indirect cost isn't so much (about 10 instructions to allocate memory for new object + a few instructions of constructor + about 5 lines of ArrayList.Itr.next() + removing of the object from Eden on minor GC), but I personally prefer indexing (or even plain arrays):

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

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

over iterating when I have to iterate through the list frequently in the main path of my application. Some other examples of standard java code with hidden cost are some String methods (substring(), trim() etc.), NIO Selectors, boxing/unboxing of primitives to store them in Collections etc.