How do we use Blocks? Some Numbers

December 6, 2022 ☼ PharoBlocksAST

Blocks are used a lot in any Smalltalk system. Let’s analyse (statically) how we use blocks in Pharo. I use the current Pharo11 development version, so the exact numbers will of course be different tomorrow.

There are quite a lot of methods, but this includes tests, test data and all the things we encode as methods but maybe should not:

 Smalltalk globals methods size "121357"

To get all blocks, we ask for all methods and then the blockNodes of the AST:

allBlocks := Smalltalk globals methods flatCollect: [:method | method ast blockNodes ].
allBlocks size "86028". 

This kind of shows that most methods are quite simple, we have more methods than blocks. And of these, a lot are actually used as arguments of messages that implement control structures (like #ifTrue:). These are actually statically inlined, we never create a BlockClosure for these:

"but many are compiled inline (eg ifTrue:)"
currentFullBlocks := allBlocks select: [:blockNode | blockNode isInlined not].
currentFullBlocks size. "36407"

How many of these blocks actually are clean? Clean blocks are those that do not

These blocks could be created at compile time, not runtime. To check this, we have #isClean on the RBBlockNode:

cleanBlocks := currentFullBlocks select: [:blockNode | blockNode isClean].
cleanBlocks size. "10097" 

A lot of these clean blocks are actually just returning a constant, e.g. the empty block [] evaluates to nil. #isConstant checks this:

"the clean blocks are actually just constant"
constantBlocks := cleanBlocks select: [:blockNode | blockNode isConstant].
constantBlocks size. "2540" 

Of all the not optimized blocks, how many actually have a non-local return? If a block does not have a non-local return, we could create it at runtime, but without an outerContext.

"FullBlocks that need the outerContext to return"
fullBocksWithReturn := currentFullBlocks select: [ :blockNode  | blockNode hasNonLocalReturn ].
fullBocksWithReturn size  "2198"

There are not that many! And some of the code could be rewritten (e.g. to move the return out of the block). Let’s look at them, inspect fullBocksWithReturn:

Test Image

The inspector can shows for the blocknode the method source, with the block hightlighted.

The method #changeRecorderFor: is a typical example for a block that does non-local return which is not needed: as the result of the whole #at:ifAbsent: is returned, we can just remove the non-local return.