Hi All,
On Sat, Sep 15, 2018 at 6:36 PM Bert Freudenberg bert@freudenbergs.de wrote:
On Sat, Sep 15, 2018 at 4:58 PM Levente Uzonyi leves@caesar.elte.hu wrote:
Hi Eliot,
Here is a very simple test case:
{ [ 1 to: 500000000 do: [ :i | i = 1 ifTrue: [ true ] ifFalse: [ false ] ] ] timeToRun. [ 1 to: 500000000 do: [ :i | i = 1 ] ] timeToRun. }
On my machine it gives #(992 1436).
Huh, interesting.
Eliot, if you figure this out, please let us know what it was ...
hmmm. It's my fault. It is to do with the criteria the StackToRegisterMappingCogit uses to decide whether to inline comparison operations. The code I wrote in genSpecialSelectorComparison says
"Only interested in inlining if followed by a conditional branch." inlineCAB := branchDescriptor isBranchTrue or: [branchDescriptor isBranchFalse]. "Further, only interested in inlining = and ~= if there's a SmallInteger constant involved. The relational operators successfully statically predict SmallIntegers; the equality operators do not." (inlineCAB and: [primDescriptor opcode = JumpZero or: [primDescriptor opcode = JumpNonZero]]) ifTrue: [inlineCAB := argIsIntConst or: [rcvrIsInt]]. inlineCAB ifFalse: [^self genSpecialSelectorSend].
So the Cogit inlines the = 1 in the first case because it is followed by the branch for the ifTrue: [true] ifFalse: [false], but does not inline = 1 in the second case. So the second case is a true send, and the true send code (load a register with i, another with 1, another with the SmallInteger tag, call the checked entry point of SmallInteger>>#=, tag test the receiver, tag test the argument, compare, reify as true or false, return) is more costly, essentially because it has a call/return. I'll discuss with Clément and Sophie we'll think about measuring this tradeoff. This is perhaps a good student project.
If anyone has good arguments why the heuristics above are bogus or good measurements please post. In the mean time sorry for the late reply; I was airborne on the way back from Cagliari, or sleeping in the terminal in Rome and was none too fresh. ESUG was glorious though. Thanks to all the organisers!!
_,,,^..^,,,_ best, Eliot
Hi Eliot,
On Mon, 17 Sep 2018, Eliot Miranda wrote:
Hi All, On Sat, Sep 15, 2018 at 6:36 PM Bert Freudenberg bert@freudenbergs.de wrote: On Sat, Sep 15, 2018 at 4:58 PM Levente Uzonyi leves@caesar.elte.hu wrote: Hi Eliot,
Here is a very simple test case: { [ 1 to: 500000000 do: [ :i | i = 1 ifTrue: [ true ] ifFalse: [ false ] ] ] timeToRun. [ 1 to: 500000000 do: [ :i | i = 1 ] ] timeToRun. } On my machine it gives #(992 1436).
Huh, interesting.
Eliot, if you figure this out, please let us know what it was ...
hmmm. It's my fault. It is to do with the criteria the StackToRegisterMappingCogit uses to decide whether to inline comparison operations. The code I wrote in genSpecialSelectorComparison says
"Only interested in inlining if followed by a conditional branch." inlineCAB := branchDescriptor isBranchTrue or: [branchDescriptor isBranchFalse]. "Further, only interested in inlining = and ~= if there's a SmallInteger constant involved. The relational operators successfully statically predict SmallIntegers; the equality operators do not." (inlineCAB and: [primDescriptor opcode = JumpZero or: [primDescriptor opcode = JumpNonZero]]) ifTrue: [inlineCAB := argIsIntConst or: [rcvrIsInt]]. inlineCAB ifFalse: [^self genSpecialSelectorSend].
So the Cogit inlines the = 1 in the first case because it is followed by the branch for the ifTrue: [true] ifFalse: [false], but does not inline = 1 in the second case. So the second case is a true send, and the true send code (load a register with i, another with 1, another with the SmallInteger tag, call the checked entry point of SmallInteger>>#=, tag test the receiver, tag test the argument, compare, reify as true or false, return) is more costly, essentially because it has a call/return. I'll discuss with Clément and Sophie we'll think about measuring this tradeoff. This is perhaps a good student project.
If anyone has good arguments why the heuristics above are bogus or good measurements please post. In the mean time sorry for the late reply; I was airborne on the way back from Cagliari, or sleeping
I see no reason to limit inlining when the result is used. When it's not used, then the program is probably incorrect. But if you want specific situations when such expressions should be jitted, then here's a quick list:
- when the resulting boolean is returned. E.g.:
isEmpty
^tally = 0
- when the resulting boolean is the value returned by a block:
array select: [ :each | each > 0 ]
- when the resulting boolean is part of a larger boolean expression (don't know how this can be detected):
(x = 1 or: [ y = 2 ]) ifTrue: [ ... ]
All in all, I think it's easier to always inline it than not. And I can't really find a reason not to inline it.
Levente
in the terminal in Rome and was none too fresh. ESUG was glorious though. Thanks to all the organisers!!
_,,,^..^,,,_ best, Eliot
Hi Levente,
On Tue, Sep 18, 2018 at 8:21 AM Levente Uzonyi leves@caesar.elte.hu wrote:
Hi Eliot,
On Mon, 17 Sep 2018, Eliot Miranda wrote:
Hi All, On Sat, Sep 15, 2018 at 6:36 PM Bert Freudenberg bert@freudenbergs.de
wrote:
On Sat, Sep 15, 2018 at 4:58 PM Levente Uzonyi <
leves@caesar.elte.hu> wrote:
Hi Eliot, Here is a very simple test case: { [ 1 to: 500000000 do: [ :i | i = 1 ifTrue: [ true ]
ifFalse: [ false ] ] ] timeToRun.
[ 1 to: 500000000 do: [ :i | i = 1 ] ] timeToRun. } On my machine it gives #(992 1436).
Huh, interesting.
Eliot, if you figure this out, please let us know what it was ...
hmmm. It's my fault. It is to do with the criteria the
StackToRegisterMappingCogit uses to decide whether to inline comparison operations. The code I wrote in genSpecialSelectorComparison says
"Only interested in inlining if followed by a conditional branch." inlineCAB := branchDescriptor isBranchTrue or: [branchDescriptor
isBranchFalse].
"Further, only interested in inlining = and ~= if there's a SmallInteger
constant involved.
The relational operators successfully statically predict SmallIntegers;
the equality operators do not."
(inlineCAB and: [primDescriptor opcode = JumpZero or: [primDescriptor
opcode = JumpNonZero]]) ifTrue:
[inlineCAB := argIsIntConst or: [rcvrIsInt]]. inlineCAB ifFalse: [^self genSpecialSelectorSend].
So the Cogit inlines the = 1 in the first case because it is followed by
the branch for the ifTrue: [true] ifFalse: [false], but does not inline = 1 in the second case. So the second case is a true
send, and the true send code (load a register with i, another with 1,
another with the SmallInteger tag, call the checked entry point of SmallInteger>>#=, tag test the receiver, tag test the argument,
compare, reify as true or false, return) is more costly, essentially
because it has a call/return. I'll discuss with Clément and Sophie we'll think about measuring this tradeoff. This is perhaps a
good student project.
If anyone has good arguments why the heuristics above are bogus or good
measurements please post. In the mean time sorry for the late reply; I was airborne on the way back from Cagliari, or sleeping
I see no reason to limit inlining when the result is used. When it's not used, then the program is probably incorrect. But if you want specific situations when such expressions should be jitted, then here's a quick list:
- when the resulting boolean is returned. E.g.:
isEmpty
^tally = 0
when the resulting boolean is the value returned by a block:
array select: [ :each | each > 0 ]
when the resulting boolean is part of a larger boolean expression
(don't know how this can be detected):
(x = 1 or: [ y = 2 ]) ifTrue: [ ... ]
All in all, I think it's easier to always inline it than not. And I can't really find a reason not to inline it.
Cool. Always great to have an excuse for more JIT hacking :-)
Levente
in the terminal in Rome and was none too fresh. ESUG was glorious
though. Thanks to all the organisers!!
_,,,^..^,,,_ best, Eliot
_,,,^..^,,,_ best, Eliot
vm-dev@lists.squeakfoundation.org