12

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.

Isaac Woods
  • 1,114
  • 1
  • 17
  • 28
  • 1
    In general, if you are worried about performance ... then consider doing real profiling. – GhostCat Mar 30 '15 at 10:57
  • @EddyG - Not necessarily.. You don't always have to do profiling.. Consider the OP's question.. Investigating byte-code will give you the correct answer.. In byte code, the method will be called only once in both cases.. – TheLostMind Mar 30 '15 at 11:00
  • You have a similar post which answer to your question: http://stackoverflow.com/questions/6093537/for-loop-optimization – Steph Mar 30 '15 at 11:02
  • @TheLostMind I agree that there are low hanging fruits. But further optimization should be guided by evaluation of runtime behavior. Otherwise you risk wasting your time. – GhostCat Mar 30 '15 at 11:04
  • @EddyG - I would usually do a byte-code analysis before doing profiling.. But thats me :P – TheLostMind Mar 30 '15 at 11:05
  • @TheLostMind Of course, bytecode is important. But what does it help to optimize java source code to produce "perfect" bytecode; when the corresponding method is only called 5 times; or when the JIT fails to do its work because of "our" optimization. – GhostCat Mar 30 '15 at 11:07

3 Answers3

10

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.

Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
  • Okay, thanks. I forgot about getter inlining, but that would cut out a bottleneck in the method call. Thanks very much, I should probably focus on bigger bottlenecks, but this would not reduce code-readability, and so I just wanted to check if micro-optimization of this would provide any advantage. – Isaac Woods Mar 30 '15 at 11:36
  • The definition of a "bottleneck" is "the place in code which takes up a disproportionately large amount of time". I think you're confusing it with "overhead". – Marko Topolnik Mar 30 '15 at 11:38
  • Yes, thank you - that is what I meant. I'm new to this optimization melarcy ;) – Isaac Woods Mar 30 '15 at 11:41
  • Now keep this in mind: the overhead of a virtual method call is a few nanoseconds. The penalty of a single CPU cache miss is 60-100 ns. So if you remove the method call, but still have to fetch the actual data from main RAM, the overhead of method call will become irrelevant. This is why it's very tricky to decide on what the real overheads are. – Marko Topolnik Mar 30 '15 at 11:49
  • So, without a language that supports more precise memory control, is there any way to avoid a cache miss? Not necessarily for this, with much bigger fish to fry, but out of interest? – Isaac Woods Mar 30 '15 at 17:01
  • 1
    if you know enough to be able to second-guess what HotSpot will do, you can control the outcome indirectly from source code. Future versions of Java will provide more control for packed arrays and value types. – Marko Topolnik Mar 30 '15 at 18:23
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.

Eran
  • 387,369
  • 54
  • 702
  • 768
1
  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.

AnatolyG
  • 1,557
  • 8
  • 14