Skip to content

GraalJS for..of is way slower than classical for loop in GraalCE #11419

@JohnTortugo

Description

@JohnTortugo

I received a report from a customer mentioning that after they changed some piece of JavaScript code to use "classical" for loops instead of "for of" they obtained a huge performance increase in their application. After doing some benchmarking on my own I also noticed that there is quite a big difference in performance between the two kinds of loops. In all cases the loop was supposed to iterate on arrays of primitive integers.

Please, see below the two versions that I tried.

function classic(array) {
    let ll = array.length;
    for (let i=0; i<ll; i++) {
        let entry = array[i];
		if (entry == 1) {
			return true;
		}
    }
    return false;
}

and

function forof(array) {
    for (let entry of array) {
		if (entry == 1) {
			return true;
		}
    }
    return false;
}

I call each method using another "main" method. The main method is simply:

function main(length) {
    const array   = new Array(1_000_000).fill(0);
    classic(array); // forof(array);
}

My experiments were controlled to make sure both methods are compiled at tier 2. The results I get show that the classical for loop is about 50% faster than the for .. of. The performance difference increases based on the number of nested loops, size of the arrays, etc. With two nested loops I see about 3x difference in performance.

I understand that the for ... of version uses an Iterator behind the scenes, but I was expecting all the iterator machinery to go away (or be largely eliminated) after Partial Evaluation and the other optimizations. However, AFAIU the "bulk" of the iterator machinery is still present in the IR even after the "Low Tier" phase.

In the IR graph I see a couple of nodes for allocating objects. Particularly, I see two allocations that are due to boxing of int to Integer because of the return value of this method AbstractIntArray::getInBoundsFast. Please, see stacktrace below.

The boxing seems to be required because the Iterator uses "Object" as the type of the elements being iterated upon. I'm wondering if we can have Iterators specialized for different data types, may be at least for numeric values?

Please let me know what you think.

jdk.graal.compiler.hotspot.replacements.HotSpotAllocationSnippets.newInstanceOrNull (HotSpotAllocationSnippets.java:-1) [bci:-1]
jdk.graal.compiler.hotspot.replacements.HotSpotAllocationSnippets.callNewInstanceStub (HotSpotAllocationSnippets.java:351) [bci:13]
jdk.graal.compiler.replacements.AllocationSnippets.allocateInstanceImpl (AllocationSnippets.java:68) [bci:126]
jdk.graal.compiler.hotspot.replacements.HotSpotAllocationSnippets.allocateInstance (HotSpotAllocationSnippets.java:150) [bci:20]
jdk.graal.compiler.hotspot.replacements.HotSpotAllocationSnippets.allocateInstance (HotSpotAllocationSnippets.java:-1) [bci:-1]
java.lang.Integer.valueOf (Integer.java:1005) [bci:23]
jdk.graal.compiler.replacements.BoxingSnippets.intValueOf (BoxingSnippets.java:82) [bci:5]
jdk.graal.compiler.replacements.BoxingSnippets.intValueOf (BoxingSnippets.java:-1) [bci:-1]
java.lang.Integer.valueOf (Integer.java:-1) [bci:-1]
com.oracle.truffle.js.runtime.array.dyn.AbstractIntArray.getInBoundsFast (AbstractIntArray.java:101) [bci:6]
com.oracle.truffle.js.nodes.access.ReadElementNode$WritableArrayReadElementCacheNode.doWritableArray (ReadElementNode.java:1039) [bci:19]
com.oracle.truffle.js.nodes.access.ReadElementNodeFactory$WritableArrayReadElementCacheNodeGen.executeArrayGet (ReadElementNodeFactory.java:1167) [bci:43]
com.oracle.truffle.js.nodes.access.ReadElementNode$ArrayReadElementCacheDispatchNode.doDispatch (ReadElementNode.java:870) [bci:101]
com.oracle.truffle.js.nodes.access.ReadElementNodeFactory$ArrayReadElementCacheDispatchNodeGen$Inlined.executeExpectReturn (ReadElementNodeFactory.java:667) [bci:64]
com.oracle.truffle.js.nodes.access.ReadElementNode$ArrayReadElementCacheDispatchNode.executeArrayGet (ReadElementNode.java:834) [bci:13]
com.oracle.truffle.js.nodes.access.ReadElementNode$ArrayReadElementCacheDispatchNode.executeDelegateReturn (ReadElementNode.java:829) [bci:101]
com.oracle.truffle.js.nodes.access.ReadElementNode$JSObjectReadElementTypeCacheNode.doLongIndex (ReadElementNode.java:657) [bci:60]
com.oracle.truffle.js.nodes.access.ReadElementNodeFactory$JSObjectReadElementTypeCacheNodeGen.executeExpectReturn (ReadElementNodeFactory.java:536) [bci:53]
com.oracle.truffle.js.nodes.access.ReadElementNode$JSObjectReadElementTypeCacheNode.executeWithTargetAndIndexUnchecked (ReadElementNode.java:597) [bci:10]
com.oracle.truffle.js.nodes.access.ReadElementNode$ReadElementTypeCacheDispatchNode.doJSObjectLongIndex (ReadElementNode.java:506) [bci:92]
com.oracle.truffle.js.nodes.access.ReadElementNodeFactory$ReadElementTypeCacheDispatchNodeGen.executeExpectReturn (ReadElementNodeFactory.java:215) [bci:70]
com.oracle.truffle.js.nodes.access.ReadElementNode$ReadElementTypeCacheDispatchNode.executeWithTargetAndIndexUnchecked (ReadElementNode.java:462) [bci:10]
com.oracle.truffle.js.nodes.access.ReadElementNode.executeTypeDispatch (ReadElementNode.java:405) [bci:26]
com.oracle.truffle.js.nodes.access.ReadElementNode.executeWithTargetAndIndex (ReadElementNode.java:348) [bci:7]
com.oracle.truffle.js.builtins.ArrayIteratorPrototypeBuiltins$ArrayIteratorNextNode.doArrayIterator (ArrayIteratorPrototypeBuiltins.java:150) [bci:174]
com.oracle.truffle.js.builtins.ArrayIteratorPrototypeBuiltinsFactory$ArrayIteratorNextNodeGen.execute (ArrayIteratorPrototypeBuiltinsFactory.java:164) [bci:97]
com.oracle.truffle.js.nodes.function.FunctionRootNode.executeInRealm (FunctionRootNode.java:155) [bci:5]
com.oracle.truffle.js.runtime.JavaScriptRealmBoundaryRootNode.execute (JavaScriptRealmBoundaryRootNode.java:96) [bci:110]
com.oracle.truffle.runtime.OptimizedCallTarget.executeRootNode (OptimizedCallTarget.java:823) [bci:5]
com.oracle.truffle.runtime.OptimizedCallTarget.callInlined (OptimizedCallTarget.java:619) [bci:23]
com.oracle.truffle.runtime.OptimizedDirectCallNode.call (OptimizedDirectCallNode.java:94) [bci:30]
com.oracle.truffle.js.nodes.function.JSFunctionCallNode$DirectJSFunctionCacheNode.executeCall (JSFunctionCallNode.java:1330) [bci:5]
com.oracle.truffle.js.nodes.function.JSFunctionCallNode.executeCall (JSFunctionCallNode.java:249) [bci:24]
com.oracle.truffle.js.nodes.access.IteratorNextUnaryNode.iteratorNext (IteratorNextUnaryNode.java:84) [bci:21]
com.oracle.truffle.js.nodes.access.IteratorNextUnaryNode.doDefault (IteratorNextUnaryNode.java:78) [bci:15]
com.oracle.truffle.js.nodes.access.IteratorNextUnaryNodeGen.execute (IteratorNextUnaryNodeGen.java:48) [bci:5]
com.oracle.truffle.js.nodes.access.JSWriteCurrentFrameSlotNodeGen.execute_generic3 (JSWriteCurrentFrameSlotNodeGen.java:136) [bci:7]
com.oracle.truffle.js.nodes.access.JSWriteCurrentFrameSlotNodeGen.execute (JSWriteCurrentFrameSlotNodeGen.java:67) [bci:71]
com.oracle.truffle.js.nodes.access.IteratorCompleteUnaryNode.executeBoolean (IteratorCompleteUnaryNode.java:77) [bci:5]
com.oracle.truffle.js.nodes.unary.JSNotNodeGen.executeBoolean_boolean2 (JSNotNodeGen.java:116) [bci:7]
com.oracle.truffle.js.nodes.unary.JSNotNodeGen.executeBoolean (JSNotNodeGen.java:106) [bci:20]
com.oracle.truffle.js.nodes.binary.DualNode.executeBoolean (DualNode.java:134) [bci:13]
com.oracle.truffle.js.nodes.control.StatementNode.executeConditionAsBoolean (StatementNode.java:58) [bci:2]
com.oracle.truffle.js.nodes.control.AbstractRepeatingNode.executeCondition (AbstractRepeatingNode.java:63) [bci:5]
com.oracle.truffle.js.nodes.control.WhileNode$WhileDoRepeatingNode.executeRepeating (WhileNode.java:237) [bci:2]
com.oracle.truffle.api.nodes.RepeatingNode.executeRepeatingWithValue (RepeatingNode.java:112) [bci:2]
com.oracle.truffle.runtime.OptimizedOSRLoopNode.execute (OptimizedOSRLoopNode.java:149) [bci:210]
com.oracle.truffle.js.nodes.control.WhileNode.execute (WhileNode.java:175) [bci:5]
com.oracle.truffle.js.nodes.control.IteratorCloseWrapperNode.doDefault (IteratorCloseWrapperNode.java:81) [bci:5]
com.oracle.truffle.js.nodes.control.IteratorCloseWrapperNodeGen.execute (IteratorCloseWrapperNodeGen.java:69) [bci:11]
com.oracle.truffle.js.nodes.control.IteratorCloseWrapperNodeGen.executeVoid (IteratorCloseWrapperNodeGen.java:74) [bci:2]
com.oracle.truffle.js.nodes.control.TryFinallyNode.executeVoid (TryFinallyNode.java:105) [bci:5]
...

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions