In the previous post, I pointed out the usefulness of introducing the method mean: to actually represent those kinds of common calcuation. There is however, an even simpler abstraction, which is more interesting.
Dividing by two is the most common static divsion performed. Real world processes call for it all the time. So much we have a word for it: half. What if we explore adding the method for this mathematical expression to the system?
ArithmeticValue>>half
^self / 2
Even on it's own this is handy. Ever centered a label?
self bounds center - label extent / 2
There's a subtle bug in there that I have stumbled on more times than I care to recollect. The last two terms need to paren'ed. With a half method, I can more correctly express the intent
self bounds center - label extent half
The code says what it's doing. The interesting thing about this method though, is that once we have it in place, we can exploit attributes of the receiver. Let us consider some baseline calculations:
| Expression | ~ms for 10,000,000 reps |
| 5 / 2 | 7300 |
| 6 / 2 | 400 |
| 5.0 / 2 | 1950 |
| 5.0d / 2 | 2350 |
| (11/2) / 2 | 8300 |
| (8@10) / 2 | 5650 |
Now let's do the same with the half method
| Expression | ~ms for 10,000,000 reps | X faster |
| 5 half | 4550 | 1.60 |
| 6 half | 980 | 0.41 |
| 5.0 half | 1575 | 1.24 |
| 5.0d half | 1950 | 1.21 |
| (11/2) half | 3050 | 2.72 |
| (8@10) half | 2850 | 1.98 |
The astute observer will realize that though were sending a message, as opposed to sending messages that the VM optimizes away in glue code often, we're actually coming out faster in all but one case. The reason is that while the implementation in ArithmeticValue is a nice correct catch all implementation (it actually should probably be more correctly defined as self + self / self), every single subtype in the table can exploit something about itself to come out ahead of the naive / 2 computation. Let's look at them:
Integer
half
"We can exploit some assumptions here and short circuit both."
^self even
ifTrue: [self // 2]
ifFalse: [Fraction basicNew setNumerator: self denominator: 2]
Basically Integers have two ways they can go. Half of an even integer is still an integer, so we can skip straight to integer division. And odd Integers are a special fraction that doesn't need to through all the GCD reduction that normal Fraction creation entails, so we can directly to the Fraction we want there. The even Integer is the one case where half is slower. But given an arbitrary sample of Integers, 50% of them should be odd, and if we look at the average times, we still come up with a net speed improvement of 1.40 for Integers in general.
Floats and Doubles
half
"A multiply is always faster than a divide."
^self * 0.5
Always faster to use a multiply than a divide. I have a hunch that on an x86 chip, this difference would be even more marked. Double has it's own that uses 0.5d.
Fraction
half
"Sidestep reduction since halving a Fraction has no further reduction opportunity."
^self species basicNew setNumerator: numerator denominator: denominator * 2
Here we take advantage of knowing we don't have to engage the whole fraction reduction engine.
Point
half
^x half @ y half
Again, by knowning we'll make another point, we can skip the whole double dispatching engine that will engage to eventually divide each half by 2. And by reusing half here, we pick up the speed benefits of half for those reciever types where appropriate.
But what can this mean?
So coming full circle, with this in place, we can now write our original mean: method as
mean: anArithmeticValue
^(self + anArithmeticValue) half
We've gained some speed, and some handy expressions that clarify many of these common computations.
Now I have to get back to finishing the Cairo presentation, the thing that started this little adventure.