Optimizing Conditionals in Smalltalk
I recently released SuffixConditionals for Visualworks Smalltalk. In the release announcement I said:
Also, the standard variants are optimized in the VM so using the suffix variants will introduce a slight performance penalty.
It turns out that this statement was not entirely correct. The standard variants are optimized as I said. However, the optimization is done by the compiler, not the VM. The code for the optimization is in the Smalltalk image, making it accessible without having to compile and distribute a modified VM.
In Visualworks Smalltalk, there is a class hierarchy rooted at
ProgramNode
. These classes represent the nodes in the abstract
syntax tree (AST) that the Smalltalk compiler produces. A few of
these classes, such as LoopNode
and ConditionalNode
, have a
comment that states, “Instances of the class are only created
temporarily for code generation.”
Tracing back from the references to these classes, you end up at
MessageNode
, which represents a message send. MessageNode
maintains a registry of MacroSelectors
– message sends that can be
transformed into more optimal ASTs before generating code. The
registry is initialized in MessageNode class>>initialize
; there, you
can see all of the message sends that are optimized.
MessageNode
implements all of the emit*
methods used to generate
code. The first time one of these messages is sent to the node, it
attempts to “expand” any MacroSelectors
and then continues to
generate the code from the expanded AST.
In order to optimize a message that isn’t already handled by the base
VW image, it is necessary to add the message selector to the registry
and to implement a method for transforming the AST. The latest
version of SuffixConditionals (version 4 as of this writing) does this
for if:
, unless:
, and if:else:
.
So now, using SuffixConditionals no longer imposes the performance penalty I warned about earlier.
These optimizations generally yield a significant performance boost.
The downside, though, is that the standard Smalltalk search tools
(like “find implementers” and “find senders”) operate on compiled
methods, so are looking at the optimized code, not the original
source. If you look in your image for senders of ifTrue:
, for
example, you’ll only see references to unoptimized sends; most sends
of ifTrue:
won’t show up in the list. The price is generally worth
it, but you need to keep this in mind when navigating your Smalltalk
code.
Thanks to Steffen Märcker for implementing this optimization for SuffixConditionals and contributing it back to the project, and for teaching me something about Smalltalk that I didn’t know before.