smalltalk

ifTrue:ifFalse: Faster than ifNil:

August 13, 2004 14:03:06.227

This came as a bit of a surprise to me at first. I remember now hearing somewhere that ifTrue:ifFalse: was highly optimized in VisualWorks. But the results of this test surprised me.

I had an idea that replacing the lazy initialize pattern:

myVar
	^myVar isNil ifTrue: [myVar := 1] ifFalse: [myVar]

with:

myVar
	^myVar ifNil: [myVar := 1]

would produce slightly faster and more readable code. Although its debatable on improved readability, i was surprised, and a tad dismayed, that the former is almost 50% faster than the latter.

This is because ifTrue:ifFalse: messages doesn't actually send the #ifTrue:ifFalse: message. To prove this, go delete all the ifTrue:ifFalse: messages in your base system. Everything still works. Take a look a the bytecodes for each:

isNil ifTrue:ifFalse: ifNil:

1 <00> push inst 0
2  send isNil
3  jump false 8
4 <4A> push 1
5 <58> store inst 0; pop
6 <00> push inst 0
7 <65> return
8 <00> push inst 0
9 <65> return
1 <00> push inst 0
2 <44> push self
3  make copying block (1)
6 <81> send ifNil:
7 <65> return
Notice the making of the block in ifNil:? This is all factored out by the compiler in the former. Perhaps there's a stack junkie out there who would like to comment or provide a link with more details on how this is done.

Comments

Also to:do

[Ian Bicking] August 13, 2004 15:11:17.727

In Squeak at least this was also true for to:do: (and maybe to:by:do:). I think the reason was that they used this method a lot in graphic, so it (like ifTrue:ifFalse:) had to be really fast. It confused the heck out of me; I was playing around with Point, and tried to make to:do: iterate over all coordinates in the rectangle formed by the two points, but to:do: is hardcoded something like:

to: other do: block
|pos last|
pos := self.
[pos < other] whileTrue: [
    last : = block value: pos.
    pos := pos + 1.
]
^last

So to:do: with coordinates would always end up iterating over the diagonal (1@1 + 1 = 2@2).

Smalltalk's booleans are very much like the Lambda Calculus, but in the end that's just not fast enough, so really they only look like the Lambda Calculus.

I vaguely remember that in Squeak ifNil: was a primitive in the same way ifFalse:ifTrue: is...?

Re: ifTrue:ifFalse: Faster than ifNil:

[ Troy Brumley] August 13, 2004 15:15:30.417

Comment on ifTrue:ifFalse: Faster than ifNil: by Troy Brumley

Uhm, premature optimization is the root of all evil. Write it readable, and then profile :)

[Vassili] August 13, 2004 16:50:26.866

ifNil: and its ilk are not macroexpanded in VisualWorks. You can see an example of a macroexpanded ifNil: implementation for VW in my compiler article, but when actually adding ifNil: to VW we decided the speedup was not worth the added complexity. If speed matters, don't use it--but in most cases it doesn't.

To see what messages are macroexpanded, print or inspect "MessageNode.MacroSelectors". In Squeak, the equivalent is "MessageNode classPool at: #MacroSelectors". The implementations are not indentical, but you can see they came from the same source.

[Boris] August 18, 2004 18:45:49.085

Try loading OptimizeIfNil from Public Repository and recompiling your methods. Beware of downsides of sending neither #isNil nor #ifTrue:ifFalse: though :)