In this section we look at the dispatch optimization color information for part of the Reversi application and see what we can do to optimize it.
Before doing that, we should build the Reversi application in Production mode so we know that the application has been optimized as much as possible.
We can now see color information showing how dispatch optimizations were or were not carried out during the last build.
This is the definition of
define class <reversi-game> (<object>) slot reversi-game-board :: <reversi-board> = make(<reversi-board>); slot %player :: <player> = #"black", init-keyword: player:; slot %players :: <integer> = 1, init-keyword: players:; slot black-algorithm :: <algorithm> = default-algorithm-for-player(#"black"), init-keyword: black-algorithm:; slot white-algorithm :: <algorithm> = default-algorithm-for-player(#"white"), init-keyword: white-algorithm:; slot reversi-game-update-callback :: <function> = always(#f), init-keyword: update-callback:; slot reversi-game-message-function :: false-or(<function>) = #f, init-keyword: message-function:; end class <reversi-game>;
There are three different colorings in this definition. The call to the function
always, a Dylan language built-in function, is in light gray. That means the call has been eliminated completely from the compiled application. A call to the function
always is defined to return a function object that always returns the value passed in the call to
always. So here, the function object would always return
#f. Unsurprisingly, the compiler evaluated this call completely, avoiding the need for run-time method dispatch.
The two calls to
default-algorithm-for-player, a Reversi application method from
algorithms.dylan, are colored in blue, signifying that the compiler managed to determine precisely which method to call, and inserted a direct call to that method in the compiled application. Again, the need for run-time method dispatch was averted.
Investigation shows that there is only one method on
default-algorithm-for-player, which makes blue optimization simple here. The generic function for
default-algorithm-for-player is defined implicitly, in the single
default-algorithm-for-player call. Recall from the DRM (chapter 6) that implicitly defined generic functions are sealed by default. That fact allows the compiler to conclude that this method is the only method there will ever be on
default-algorithm-for-player, making the optimization possible.
The third coloring is magenta, in the call to
<reversi-board>, in the
reversi-game-board slot definition. Here, then, is a generic function call that was not optimized. Magenta coloring means that for this call to
make, the compiler could not determine the complete set of methods from which it could attempt to select the appropriate method to call. We will now make changes to the Reversi sources to optimize this call.
The problem here is that the compiler cannot be sure that additional methods on
make might not be added at run time. By defining a sealed domain on make for
<reversi-board>, we can clear this up.
define sealed domain make(subclass(<reversi-board>));
With this information, the compiler knows it has access to the complete set of methods on
make for this class, and therefore can attempt to do the method selection itself.
We can recompile the application to see what effect our change has had.
game.dylanwith File > Save.
game.dylanwith View > Refresh.
The refreshed coloring shows the call to
<reversi-board> in the
reversi-game-board slot definition in light gray. This coloring means that the compiler determined which
make method to call, computed the result of the call--a
<reversi-board> object--and inlined the object.
Looking further down
game.dylan, notice that the definition of
reversi-game-size-setter also calls
<reversi-board>, a call that is also colored light gray.
We can now look at other possible optimizations in
The definition of
define method initialize-board (board :: <reversi-board>) => () let squares = reversi-board-squares(board); for (square from 0 below size(squares)) squares[square] := #f end; for (piece in initial-pieces(board)) let square = piece; squares[square] := piece end; end method initialize-board;
In this method there is a green-colored call to
reversi-board-squares on the parameter
board, an instance of
<reversi-board>. Green coloring denotes an access to a slot whose position in a class is fixed. This optimization was possible because the
reversi-board-squares method is just the implicitly defined accessor for the slot
define class <reversi-board> (<object>) slot reversi-board-size :: <integer> = $default-board-size, init-keyword: size:; slot reversi-board-squares :: <sequence> = #; end class <reversi-board>;
The compiler achieved this optimization because it knew three things. First, it knew that the generic function implicitly defined by the accessor method was sealed. (As normal Dylan methods, accessor methods implicitly define a generic function if one does not already exist; such a generic function is sealed because implicitly defined generic functions are sealed by default.) Second, the compiler knew the type of
board in the call to the accessor method. Third, the compiler knew that the class
<reversi-board> was sealed, because classes are sealed by default.
We can now move on to some other optimization. The call
initialize-board is colored in magenta. There are several similar magenta colorings in
game.dylan, where the compiler could not optimize a method call on the value returned from
reversi-board-squares: calls to
size. In all cases this is because the type of
<sequence>, which is an open class.
We could seal domains on
<sequence> to get optimizations here. But the DRM defines
<sequence> as an open class, and it is not good practice to seal protocols that do not belong to your library or libraries. However, we can change the type of
reversi-board-squares to be in a domain which is already sealed. Changing the slot type to
<simple-object-vector> gives us a sealed type as well as preserving the protocol in use, so that we do not have to change any of the calls being made.
game.dylanwith File > Save.
game.dylanwith View > Refresh.
size(squares) call is now colored green. Green coloring means the compiler determined that the call was equivalent to a slot access--particularly, an access to slot having a fixed offset from the memory address at which its class is located. The compiler removed the need for run-time method dispatch by replacing the call with code to access the location that would contain the slot value.
This particular optimization was possible because
size is a slot accessor for instances of
<simple-object-vector>, and, of course, because
<simple-object-vector> is sealed.
You could examine the effects of this change on other calls that use the return value of
reversi-board-squares. Some calls turn blue. Some calls to
element-setter remain magenta because the compiler does not know the type of the index. Constraining the type of the index would improve such a call, turning it blue or even dark gray (inlined).