opal optimize to do limits
What is the purpose of replacing expressions, used as limits in a to:do: call with by a new temporary variable? For example: OCOpalExamples>>#exampleToDoArgumentLimitIsExpression the code is exampleToDoArgumentLimitIsExpression | count sum | count := 10. sum := 0. 1 to: count - 1 do: [ :each | sum := sum + each]. ^sum the decompiled bytecode : exampleToDoArgumentLimitIsExpression | tmp1 tmp2 tmp3 | tmp1 := 10. tmp2 := 0. tmp3 := tmp1 - 1. 1 to: tmp3 do: [ :tmp4 | tmp2 := tmp2 + tmp4 ]. ^ tmp2 So, the expression (count - 1) in the to:do: loop is replaced by a new temporay (tmp3). Ok, but why ?
SSA form? Le 19/11/15 22:14, Nicolai Hess a écrit :
What is the purpose of replacing expressions, used as limits in a to:do: call with by a new temporary variable?
For example:
OCOpalExamples>>#exampleToDoArgumentLimitIsExpression the code is
exampleToDoArgumentLimitIsExpression | count sum | count := 10. sum := 0. 1 to: count - 1 do: [ :each | sum := sum + each]. ^sum
the decompiled bytecode :
exampleToDoArgumentLimitIsExpression | tmp1 tmp2 tmp3 | tmp1 := 10. tmp2 := 0. tmp3 := tmp1 - 1. 1 to: tmp3 do: [ :tmp4 | tmp2 := tmp2 + tmp4 ]. ^ tmp2
So, the expression (count - 1) in the to:do: loop is replaced by a new temporay (tmp3).
Ok, but why ?
2015-11-19 22:29 GMT+01:00 stepharo <stepharo@free.fr>:
SSA form?
and for method arguments? foo: count | sum | sum := 0. 1 to: count do: [ :ku | sum := sum + ku ]. ^ sum transforms to: "foo: arg1 | tmp1 tmp2 | tmp1 := 0. tmp2 := arg1. 1 to: tmp2 do: [ :tmp3 | tmp1 := tmp1 + tmp3 ]. ^ tmp1"
Le 19/11/15 22:14, Nicolai Hess a écrit :
What is the purpose of replacing expressions, used as limits in a
to:do: call with by a new temporary variable?
For example:
OCOpalExamples>>#exampleToDoArgumentLimitIsExpression the code is
exampleToDoArgumentLimitIsExpression | count sum | count := 10. sum := 0. 1 to: count - 1 do: [ :each | sum := sum + each]. ^sum
the decompiled bytecode :
exampleToDoArgumentLimitIsExpression | tmp1 tmp2 tmp3 | tmp1 := 10. tmp2 := 0. tmp3 := tmp1 - 1. 1 to: tmp3 do: [ :tmp4 | tmp2 := tmp2 + tmp4 ]. ^ tmp2
So, the expression (count - 1) in the to:do: loop is replaced by a new temporay (tmp3).
Ok, but why ?
Because were to:do: not inlined the expression passed as the to: actual argument would be evaluated exactly once (bound to the formal argument for to:) and so assigning to a temporary preserves the semantics in the most direct way. If the expression has side effects then those side effects must occur only once. Note that were the to: expression merely another variable then the compiler could only substitute the original variable if it's value could not change, e.g. in exampleToDoArgumentLimitIsExpression | count sum | count := 10. sum := 0. 1 to: count do: [ :each | sum := sum + each]. ^sum It is ok to use count directly as the limit variable, but in this it is not: exampleToDoArgumentLimitIsExpression | count sum | count := 10. sum := 0. 1 to: count do: [ :each | sum := sum + each. count := #somethingElseEntirely]. ^sum _,,,^..^,,,_ (phone)
On Nov 19, 2015, at 1:14 PM, Nicolai Hess <nicolaihess@gmail.com> wrote:
What is the purpose of replacing expressions, used as limits in a to:do: call with by a new temporary variable?
For example:
OCOpalExamples>>#exampleToDoArgumentLimitIsExpression the code is
exampleToDoArgumentLimitIsExpression | count sum | count := 10. sum := 0. 1 to: count - 1 do: [ :each | sum := sum + each]. ^sum
the decompiled bytecode :
exampleToDoArgumentLimitIsExpression | tmp1 tmp2 tmp3 | tmp1 := 10. tmp2 := 0. tmp3 := tmp1 - 1. 1 to: tmp3 do: [ :tmp4 | tmp2 := tmp2 + tmp4 ]. ^ tmp2
So, the expression (count - 1) in the to:do: loop is replaced by a new temporay (tmp3).
Ok, but why ?
You can use whileTrue: if it's a problem for you not to evaluate the limit at each iteration of the loop. If you look at OrderedCollection for example, there are implemented differently in Pharo/VW and Squeak. Pharo and VW use to:do: whereas Squeak uses whileTrue: to iterate over the collection. Hence, if you remove elements from the OrderedCollection while iterating over it, the behavior is different. 2015-11-20 1:34 GMT+01:00 Eliot Miranda <eliot.miranda@gmail.com>:
Because were to:do: not inlined the expression passed as the to: actual argument would be evaluated exactly once (bound to the formal argument for to:) and so assigning to a temporary preserves the semantics in the most direct way. If the expression has side effects then those side effects must occur only once.
Note that were the to: expression merely another variable then the compiler could only substitute the original variable if it's value could not change, e.g. in
exampleToDoArgumentLimitIsExpression | count sum | count := 10. sum := 0. 1 to: count do: [ :each | sum := sum + each]. ^sum
It is ok to use count directly as the limit variable, but in this it is not:
exampleToDoArgumentLimitIsExpression | count sum | count := 10. sum := 0. 1 to: count do: [ :each | sum := sum + each. count := #somethingElseEntirely]. ^sum
_,,,^..^,,,_ (phone)
On Nov 19, 2015, at 1:14 PM, Nicolai Hess <nicolaihess@gmail.com> wrote:
What is the purpose of replacing expressions, used as limits in a to:do: call with by a new temporary variable?
For example:
OCOpalExamples>>#exampleToDoArgumentLimitIsExpression the code is
exampleToDoArgumentLimitIsExpression | count sum | count := 10. sum := 0. 1 to: count - 1 do: [ :each | sum := sum + each]. ^sum
the decompiled bytecode :
exampleToDoArgumentLimitIsExpression | tmp1 tmp2 tmp3 | tmp1 := 10. tmp2 := 0. tmp3 := tmp1 - 1. 1 to: tmp3 do: [ :tmp4 | tmp2 := tmp2 + tmp4 ]. ^ tmp2
So, the expression (count - 1) in the to:do: loop is replaced by a new temporay (tmp3).
Ok, but why ?
Thanks eliot, Clement, but if the argument for to:do: is not an expression but one of the method argument, this transformation is not needed. Or still? 2015-11-20 8:31 GMT+01:00 Clément Bera <bera.clement@gmail.com>:
You can use whileTrue: if it's a problem for you not to evaluate the limit at each iteration of the loop.
I don't need this. I just compared some compiled methods bytecodes after the latest fix for the wrong frame size calculation. For this, I compared opal compilers result with that of the old compiler. And I noticed that some methods compiled with opal had some "hidden" temps and couldn't explain way:)
If you look at OrderedCollection for example, there are implemented differently in Pharo/VW and Squeak. Pharo and VW use to:do: whereas Squeak uses whileTrue: to iterate over the collection. Hence, if you remove elements from the OrderedCollection while iterating over it, the behavior is different.
2015-11-20 1:34 GMT+01:00 Eliot Miranda <eliot.miranda@gmail.com>:
Because were to:do: not inlined the expression passed as the to: actual argument would be evaluated exactly once (bound to the formal argument for to:) and so assigning to a temporary preserves the semantics in the most direct way. If the expression has side effects then those side effects must occur only once.
Note that were the to: expression merely another variable then the compiler could only substitute the original variable if it's value could not change, e.g. in
exampleToDoArgumentLimitIsExpression | count sum | count := 10. sum := 0. 1 to: count do: [ :each | sum := sum + each]. ^sum
It is ok to use count directly as the limit variable, but in this it is not:
exampleToDoArgumentLimitIsExpression | count sum | count := 10. sum := 0. 1 to: count do: [ :each | sum := sum + each. count := #somethingElseEntirely]. ^sum
_,,,^..^,,,_ (phone)
On Nov 19, 2015, at 1:14 PM, Nicolai Hess <nicolaihess@gmail.com> wrote:
What is the purpose of replacing expressions, used as limits in a to:do: call with by a new temporary variable?
For example:
OCOpalExamples>>#exampleToDoArgumentLimitIsExpression the code is
exampleToDoArgumentLimitIsExpression | count sum | count := 10. sum := 0. 1 to: count - 1 do: [ :each | sum := sum + each]. ^sum
the decompiled bytecode :
exampleToDoArgumentLimitIsExpression | tmp1 tmp2 tmp3 | tmp1 := 10. tmp2 := 0. tmp3 := tmp1 - 1. 1 to: tmp3 do: [ :tmp4 | tmp2 := tmp2 + tmp4 ]. ^ tmp2
So, the expression (count - 1) in the to:do: loop is replaced by a new temporay (tmp3).
Ok, but why ?
Hi Nicolai,
On Nov 20, 2015, at 12:33 AM, Nicolai Hess <nicolaihess@gmail.com> wrote:
Thanks eliot, Clement,
but if the argument for to:do: is not an expression but one of the method argument, this transformation is not needed. Or still?
That's the corollary to what I said. Since method (*) arguments are read-only it is safe to use them directly and avoid the transformation. One could implement this quickly in the Squeak compiler too :-). How many cases have you identified where this matters? Is it very common? (* and with the right preference, block) _,,,^..^,,,_ (phone)
2015-11-20 8:31 GMT+01:00 Clément Bera <bera.clement@gmail.com>:
You can use whileTrue: if it's a problem for you not to evaluate the limit at each iteration of the loop.
I don't need this. I just compared some compiled methods bytecodes after the latest fix for the wrong frame size calculation. For this, I compared opal compilers result with that of the old compiler. And I noticed that some methods compiled with opal had some "hidden" temps and couldn't explain way:)
If you look at OrderedCollection for example, there are implemented differently in Pharo/VW and Squeak. Pharo and VW use to:do: whereas Squeak uses whileTrue: to iterate over the collection. Hence, if you remove elements from the OrderedCollection while iterating over it, the behavior is different.
2015-11-20 1:34 GMT+01:00 Eliot Miranda <eliot.miranda@gmail.com>:
Because were to:do: not inlined the expression passed as the to: actual argument would be evaluated exactly once (bound to the formal argument for to:) and so assigning to a temporary preserves the semantics in the most direct way. If the expression has side effects then those side effects must occur only once.
Note that were the to: expression merely another variable then the compiler could only substitute the original variable if it's value could not change, e.g. in
exampleToDoArgumentLimitIsExpression | count sum | count := 10. sum := 0. 1 to: count do: [ :each | sum := sum + each]. ^sum
It is ok to use count directly as the limit variable, but in this it is not:
exampleToDoArgumentLimitIsExpression | count sum | count := 10. sum := 0. 1 to: count do: [ :each | sum := sum + each. count := #somethingElseEntirely]. ^sum
_,,,^..^,,,_ (phone)
On Nov 19, 2015, at 1:14 PM, Nicolai Hess <nicolaihess@gmail.com> wrote:
What is the purpose of replacing expressions, used as limits in a to:do: call with by a new temporary variable?
For example:
OCOpalExamples>>#exampleToDoArgumentLimitIsExpression the code is
exampleToDoArgumentLimitIsExpression | count sum | count := 10. sum := 0. 1 to: count - 1 do: [ :each | sum := sum + each]. ^sum
the decompiled bytecode :
exampleToDoArgumentLimitIsExpression | tmp1 tmp2 tmp3 | tmp1 := 10. tmp2 := 0. tmp3 := tmp1 - 1. 1 to: tmp3 do: [ :tmp4 | tmp2 := tmp2 + tmp4 ]. ^ tmp2
So, the expression (count - 1) in the to:do: loop is replaced by a new temporay (tmp3).
Ok, but why ?
2015-11-20 9:58 GMT+01:00 Eliot Miranda <eliot.miranda@gmail.com>:
Hi Nicolai,
On Nov 20, 2015, at 12:33 AM, Nicolai Hess <nicolaihess@gmail.com> wrote:
Thanks eliot, Clement,
but if the argument for to:do: is not an expression but one of the method argument, this transformation is not needed. Or still?
That's the corollary to what I said. Since method (*) arguments are read-only it is safe to use them directly and avoid the transformation. One could implement this quickly in the Squeak compiler too :-). How many cases have you identified where this matters? Is it very common?
I don't know, but I don't think so. Recompiling all ~90000 compiled methods, once with opal and once with the other compiler and comparing the numbers of tempVars gives a difference of about 400 methods. Not all 400 differences came from the extra tempvars for the to:do: transform, and sometimes opal generates code with less temp vars. Anyway, adding extra tempvars for accessing the method arguments seems to be a bug.
(* and with the right preference, block) _,,,^..^,,,_ (phone)
2015-11-20 8:31 GMT+01:00 Clément Bera <bera.clement@gmail.com>:
You can use whileTrue: if it's a problem for you not to evaluate the limit at each iteration of the loop.
I don't need this. I just compared some compiled methods bytecodes after the latest fix for the wrong frame size calculation. For this, I compared opal compilers result with that of the old compiler. And I noticed that some methods compiled with opal had some "hidden" temps and couldn't explain way:)
If you look at OrderedCollection for example, there are implemented differently in Pharo/VW and Squeak. Pharo and VW use to:do: whereas Squeak uses whileTrue: to iterate over the collection. Hence, if you remove elements from the OrderedCollection while iterating over it, the behavior is different.
2015-11-20 1:34 GMT+01:00 Eliot Miranda <eliot.miranda@gmail.com>:
Because were to:do: not inlined the expression passed as the to: actual argument would be evaluated exactly once (bound to the formal argument for to:) and so assigning to a temporary preserves the semantics in the most direct way. If the expression has side effects then those side effects must occur only once.
Note that were the to: expression merely another variable then the compiler could only substitute the original variable if it's value could not change, e.g. in
exampleToDoArgumentLimitIsExpression | count sum | count := 10. sum := 0. 1 to: count do: [ :each | sum := sum + each]. ^sum
It is ok to use count directly as the limit variable, but in this it is not:
exampleToDoArgumentLimitIsExpression | count sum | count := 10. sum := 0. 1 to: count do: [ :each | sum := sum + each. count := #somethingElseEntirely]. ^sum
_,,,^..^,,,_ (phone)
On Nov 19, 2015, at 1:14 PM, Nicolai Hess <nicolaihess@gmail.com> wrote:
What is the purpose of replacing expressions, used as limits in a to:do: call with by a new temporary variable?
For example:
OCOpalExamples>>#exampleToDoArgumentLimitIsExpression the code is
exampleToDoArgumentLimitIsExpression | count sum | count := 10. sum := 0. 1 to: count - 1 do: [ :each | sum := sum + each]. ^sum
the decompiled bytecode :
exampleToDoArgumentLimitIsExpression | tmp1 tmp2 tmp3 | tmp1 := 10. tmp2 := 0. tmp3 := tmp1 - 1. 1 to: tmp3 do: [ :tmp4 | tmp2 := tmp2 + tmp4 ]. ^ tmp2
So, the expression (count - 1) in the to:do: loop is replaced by a new temporay (tmp3).
Ok, but why ?
Hi Nicolai, On Sat, Nov 21, 2015 at 10:40 AM, Nicolai Hess <nicolaihess@gmail.com> wrote:
2015-11-20 9:58 GMT+01:00 Eliot Miranda <eliot.miranda@gmail.com>:
Hi Nicolai,
On Nov 20, 2015, at 12:33 AM, Nicolai Hess <nicolaihess@gmail.com> wrote:
Thanks eliot, Clement,
but if the argument for to:do: is not an expression but one of the method argument, this transformation is not needed. Or still?
That's the corollary to what I said. Since method (*) arguments are read-only it is safe to use them directly and avoid the transformation. One could implement this quickly in the Squeak compiler too :-). How many cases have you identified where this matters? Is it very common?
I don't know, but I don't think so. Recompiling all ~90000 compiled methods, once with opal and once with the other compiler and comparing the numbers of tempVars gives a difference of about 400 methods. Not all 400 differences came from the extra tempvars for the to:do: transform, and sometimes opal generates code with less temp vars.
Well it's a nice surprise to find out that the Squeak compiler does use a variable directly if it's not assigned to in the block. Thanks to your observation I've simplified the test a little, not scanning for assignments if the variable is a method argument or a block argument and the assignment to block arg preference is not in effect. Anyway, adding extra tempvars for accessing the method arguments seems to
be a bug.
Agreed ;-) (* and with the right preference, block)
_,,,^..^,,,_ (phone)
2015-11-20 8:31 GMT+01:00 Clément Bera <bera.clement@gmail.com>:
You can use whileTrue: if it's a problem for you not to evaluate the limit at each iteration of the loop.
I don't need this. I just compared some compiled methods bytecodes after the latest fix for the wrong frame size calculation. For this, I compared opal compilers result with that of the old compiler. And I noticed that some methods compiled with opal had some "hidden" temps and couldn't explain way:)
If you look at OrderedCollection for example, there are implemented differently in Pharo/VW and Squeak. Pharo and VW use to:do: whereas Squeak uses whileTrue: to iterate over the collection. Hence, if you remove elements from the OrderedCollection while iterating over it, the behavior is different.
2015-11-20 1:34 GMT+01:00 Eliot Miranda <eliot.miranda@gmail.com>:
Because were to:do: not inlined the expression passed as the to: actual argument would be evaluated exactly once (bound to the formal argument for to:) and so assigning to a temporary preserves the semantics in the most direct way. If the expression has side effects then those side effects must occur only once.
Note that were the to: expression merely another variable then the compiler could only substitute the original variable if it's value could not change, e.g. in
exampleToDoArgumentLimitIsExpression | count sum | count := 10. sum := 0. 1 to: count do: [ :each | sum := sum + each]. ^sum
It is ok to use count directly as the limit variable, but in this it is not:
exampleToDoArgumentLimitIsExpression | count sum | count := 10. sum := 0. 1 to: count do: [ :each | sum := sum + each. count := #somethingElseEntirely]. ^sum
_,,,^..^,,,_ (phone)
On Nov 19, 2015, at 1:14 PM, Nicolai Hess <nicolaihess@gmail.com> wrote:
What is the purpose of replacing expressions, used as limits in a to:do: call with by a new temporary variable?
For example:
OCOpalExamples>>#exampleToDoArgumentLimitIsExpression the code is
exampleToDoArgumentLimitIsExpression | count sum | count := 10. sum := 0. 1 to: count - 1 do: [ :each | sum := sum + each]. ^sum
the decompiled bytecode :
exampleToDoArgumentLimitIsExpression | tmp1 tmp2 tmp3 | tmp1 := 10. tmp2 := 0. tmp3 := tmp1 - 1. 1 to: tmp3 do: [ :tmp4 | tmp2 := tmp2 + tmp4 ]. ^ tmp2
So, the expression (count - 1) in the to:do: loop is replaced by a new temporay (tmp3).
Ok, but why ?
-- _,,,^..^,,,_ best, Eliot
On Fri, 20 Nov 2015, Clément Bera wrote:
You can use whileTrue: if it's a problem for you not to evaluate the limit at each iteration of the loop. If you look at OrderedCollection for example, there are implemented differently in Pharo/VW and Squeak. Pharo and VW use to:do: whereas Squeak uses whileTrue: to iterate over the collection. Hence, if you remove elements from the OrderedCollection while iterating over it, the behavior is different.
One should refrain from modifying collections during iteration. In case of OrderedCollection there's #removeAllSuchThat: to let one safely remove elements from the collection while iterating over it. Levente
participants (5)
-
Clément Bera -
Eliot Miranda -
Levente Uzonyi -
Nicolai Hess -
stepharo