Finally Time to Make Good on "Everything is a message send"?

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

Finally Time to Make Good on "Everything is a message send"?

Sean P. DeNigris
Administrator
 
On the "Squeak Beginners" ML, a new Smalltalker wrote:
> I don't understand why Smalltalk doesn't allow me to have an or:
> method that takes a block argument (except on Boolean).

The answer apparently was that the compiler replaces the code with an
optimization that is usually what one wants, and the actual message never
gets sent:
>    Ralph Johnson wrote
>    Yes, when the compiler sees   exp or: [ ... ] then it assumes that
> "exp" is
>   a boolean-valued expression and generates code that fails if it isn't.

I remember the pain of tripping over these little "everything is a message
send to an object*" sins as a new Smalltalker. I wonder now, with the
incredible speed of Cog, Spur, Sista, etc., if these devil's bargains from
prior decades are still necessary. It would be psychologically satisfying
(and nice for newbies) to remove the asterisk from the principle above.

p.s. IIRC e.g. Opal has flags to modify this behavior, but could the actual
messages be sent by default, and the flag be set to inline when that
performance boost is actually required?



-----
Cheers,
Sean
--
Sent from: http://forum.world.st/Squeak-VM-f104410.html
Cheers,
Sean
Reply | Threaded
Open this post in threaded view
|

Re: Finally Time to Make Good on "Everything is a message send"?

Bert Freudenberg
 
On Sun, Sep 17, 2017 at 3:51 PM, Sean P. DeNigris <[hidden email]> wrote:

On the "Squeak Beginners" ML, a new Smalltalker wrote:
> I don't understand why Smalltalk doesn't allow me to have an or:
> method that takes a block argument (except on Boolean).

The answer apparently was that the compiler replaces the code with an
optimization that is usually what one wants, and the actual message never
gets sent:
>    Ralph Johnson wrote
>    Yes, when the compiler sees   exp or: [ ... ] then it assumes that
> "exp" is
>   a boolean-valued expression and generates code that fails if it isn't.

I remember the pain of tripping over these little "everything is a message
send to an object*" sins as a new Smalltalker. I wonder now, with the
incredible speed of Cog, Spur, Sista, etc., if these devil's bargains from
prior decades are still necessary. It would be psychologically satisfying
(and nice for newbies) to remove the asterisk from the principle above.
 
​Yes, optimization of boolean-looking expressions is cheating that gets caught once in a while. 

But even in Cog it's still 400% faster:

[false or: [true]] bench

 '198,000,000 per second. 5.06 nanoseconds per run.'
 '211,000,000 per second. 4.75 nanoseconds per run.'
 '209,000,000 per second. 4.79 nanoseconds per run.'

... and again with #transformOr: disabled:

 '47,100,000 per second. 21.2 nanoseconds per run.'
 '47,500,000 per second. 21.1 nanoseconds per run.' 
'48,000,000 per second. 20.8 nanoseconds per run.'
We don't want to give up that performance, but we would like to not get caught cheating either. This would be very simple if instead of raising the "must be boolean" error, we could simply retry the send as a regular message send. 

The major complication here is that in the byte code we don't know the original method selector (e.g. ifTrue:ifFalse: etc), and we do not have the original block closure arguments, because they never got constructed in the first place.

There are various ways to tackle that problem with different speed/space tradeoffs. If we don't care too much about the non-boolean receiver performance (it's the slow path after all) then no modifications to the VM would be needed.

You should be able to do it all in the #mustBeBoolean handler: perform the original selector with the block arguments, then jump to after the optimized block. Sounds like a fun, if not exactly trivial project :) 

- Bert -
Reply | Threaded
Open this post in threaded view
|

Re: Finally Time to Make Good on "Everything is a message send"?

Denis Kudriashov
 
Hi
 
We don't want to give up that performance, but we would like to not get caught cheating either. This would be very simple if instead of raising the "must be boolean" error, we could simply retry the send as a regular message send. 

This is already in Pharo :)

2017-09-18 15:32 GMT+02:00 Bert Freudenberg <[hidden email]>:
 
On Sun, Sep 17, 2017 at 3:51 PM, Sean P. DeNigris <[hidden email]> wrote:

On the "Squeak Beginners" ML, a new Smalltalker wrote:
> I don't understand why Smalltalk doesn't allow me to have an or:
> method that takes a block argument (except on Boolean).

The answer apparently was that the compiler replaces the code with an
optimization that is usually what one wants, and the actual message never
gets sent:
>    Ralph Johnson wrote
>    Yes, when the compiler sees   exp or: [ ... ] then it assumes that
> "exp" is
>   a boolean-valued expression and generates code that fails if it isn't.

I remember the pain of tripping over these little "everything is a message
send to an object*" sins as a new Smalltalker. I wonder now, with the
incredible speed of Cog, Spur, Sista, etc., if these devil's bargains from
prior decades are still necessary. It would be psychologically satisfying
(and nice for newbies) to remove the asterisk from the principle above.
 
​Yes, optimization of boolean-looking expressions is cheating that gets caught once in a while. 

But even in Cog it's still 400% faster:

[false or: [true]] bench

 '198,000,000 per second. 5.06 nanoseconds per run.'
 '211,000,000 per second. 4.75 nanoseconds per run.'
 '209,000,000 per second. 4.79 nanoseconds per run.'

... and again with #transformOr: disabled:

 '47,100,000 per second. 21.2 nanoseconds per run.'
 '47,500,000 per second. 21.1 nanoseconds per run.' 
'48,000,000 per second. 20.8 nanoseconds per run.'
We don't want to give up that performance, but we would like to not get caught cheating either. This would be very simple if instead of raising the "must be boolean" error, we could simply retry the send as a regular message send. 

The major complication here is that in the byte code we don't know the original method selector (e.g. ifTrue:ifFalse: etc), and we do not have the original block closure arguments, because they never got constructed in the first place.

There are various ways to tackle that problem with different speed/space tradeoffs. If we don't care too much about the non-boolean receiver performance (it's the slow path after all) then no modifications to the VM would be needed.

You should be able to do it all in the #mustBeBoolean handler: perform the original selector with the block arguments, then jump to after the optimized block. Sounds like a fun, if not exactly trivial project :) 

- Bert -


Reply | Threaded
Open this post in threaded view
|

Re: Finally Time to Make Good on "Everything is a message send"?

Henrik Sperre Johansen
 
Denis Kudriashov wrote

> Hi
>
>
>> We don't want to give up that performance, but we would like to not get
>> caught cheating either. This would be very simple if instead of raising
>> the
>> "must be boolean" error, we could simply retry the send as a regular
>> message send.
>
>
> This is already in Pharo :)

And it's cool and all, but neither trivial*, nor especially fast:

a := MyBooleanObject new.
[a or: [true]] bench '2,829 per second'
[false or: [true]] bench '188,853,858 per second'

Cheers,
Henry

*Ref threads like
http://forum.world.st/Re-Pharo-dev-mustBeBooleanMagicIn-can-crash-the-vm-td4772716.html#a4772724
http://forum.world.st/Re-Pharo-dev-eventual-crashes-pharo-vm-tp4961763.html



--
Sent from: http://forum.world.st/Squeak-VM-f104410.html
Reply | Threaded
Open this post in threaded view
|

Re: Finally Time to Make Good on "Everything is a message send"?

Levente Uzonyi
In reply to this post by Sean P. DeNigris
 
On Sun, 17 Sep 2017, Sean P. DeNigris wrote:

>
> On the "Squeak Beginners" ML, a new Smalltalker wrote:
>> I don't understand why Smalltalk doesn't allow me to have an or:
>> method that takes a block argument (except on Boolean).
>
> The answer apparently was that the compiler replaces the code with an
> optimization that is usually what one wants, and the actual message never
> gets sent:
>>    Ralph Johnson wrote
>>    Yes, when the compiler sees   exp or: [ ... ] then it assumes that
>> "exp" is
>>   a boolean-valued expression and generates code that fails if it isn't.
>
> I remember the pain of tripping over these little "everything is a message
> send to an object*" sins as a new Smalltalker. I wonder now, with the
> incredible speed of Cog, Spur, Sista, etc., if these devil's bargains from
> prior decades are still necessary. It would be psychologically satisfying
> (and nice for newbies) to remove the asterisk from the principle above.
>
> p.s. IIRC e.g. Opal has flags to modify this behavior, but could the actual
> messages be sent by default, and the flag be set to inline when that
> performance boost is actually required?

You can turn off all those optimizations in your image except for
potentially infinite loops.

The performance drop will be larger than you may think, because Cog has
its own native implementation for most if not all those methods. So it'll
skip both primitives and special bytecodes when possible and inline them
into the jitted code (This can result in nasty debug situations when the
jitter code's behavior differs from what the image side code suggests).

Also, most of those methods are not newbie-safe. Changing any of them will
probably freeze your image.

Finally, these changes can only be done on the image side, so this
question probably belongs to another list.

Levente

>
>
>
> -----
> Cheers,
> Sean
> --
> Sent from: http://forum.world.st/Squeak-VM-f104410.html
Reply | Threaded
Open this post in threaded view
|

Re: Finally Time to Make Good on "Everything is a message send"?

Nicolas Cellier
 
And last thing there's an absolutely cheap way to remove optimization: don't provide a block argument. For example provide a message send instead:

[true or: [false] yourself] bench.
 '39,900,000 per second. 25 nanoseconds per run.'

[true or: [false]] bench.
'177,000,000 per second. 5.63 nanoseconds per run.'

If you like syntactic sugar, implement BlockClosure>>unoptimized as ^self and write:

    true or: [false] unoptimized.

And last, here is a draft that I wrote in 2015 in the pharo thread about mustBeBoolean workaround
(but I never sent that draft, I didn't want to sound too negative) :

[Pharo-dev] can or: be redefined?

...snip...
> That's the way to go. I think some people like Camille are already working on it. Basically, you need abstraction over what is happening which is implemented in the image instead of the VM for simplicity and maintenance, and what the programmer wants to see which is relevant for him.

> For optimized selectors, one could mark the methods with pragmas as 'Do not show in debugger' and in addition rewire the bytecode to source code mapping from the method compiled on-the-fly executed in ExecuteUnoptimizedIn: to the regular method.

> But as I stated for Slots, it is more complicated. The debugger cannot detect if the stack is currently in a slot access, as it has access only to the bottom 20 frames. Having the debugger access the full stack is a bad idea as it would slow it considerably, making the debugger unusable on devices such as the raspberry pie, and hard to use to debug recursive code. Maybe a solution could be that if the compiled bytecode for a slot has an interrupt point (branch, back jump, send), then it could change a state in the process, probably encoded in the process instance variables, and as the debugger has access to the process it is showing it would know.
 

It sounds like inserting an elephant in the stack and keep prretending it's turtle all the way down ;)
Isn't the cheat too visible?

Some essential Smalltalk principles/properties shall better not be lost:
- there's a well defined virtual machine which acts as boundary between Smalltalk code and non Smalltalk (primitive) code.
- everyone should be in a position to understand every single bit of Smalltalk code and the debugger is the ultimate introspective tool to do so.

There are times when you want to debug slot implementation itself, think of it...
And there will be times when you want to introspect how the machinery works, maybe because you want to understand time spent in a method or come with a similar trick.
I hope it does not imply debugging at bytecode level, this would be a great loss.

To me, the whole #or: story sounds like you came with a complex workaround for hiding an optimization.
And now you're trying to hide the workaround with an even more complex machinery which endanger first principles:

Just admitting and learning that there are a very few exceptions to the rules sounds very cheap in regard.
I wish you success, but it's scary!

2017-09-18 19:22 GMT+02:00 Levente Uzonyi <[hidden email]>:

On Sun, 17 Sep 2017, Sean P. DeNigris wrote:


On the "Squeak Beginners" ML, a new Smalltalker wrote:
I don't understand why Smalltalk doesn't allow me to have an or:
method that takes a block argument (except on Boolean).

The answer apparently was that the compiler replaces the code with an
optimization that is usually what one wants, and the actual message never
gets sent:
   Ralph Johnson wrote
   Yes, when the compiler sees   exp or: [ ... ] then it assumes that
"exp" is
  a boolean-valued expression and generates code that fails if it isn't.

I remember the pain of tripping over these little "everything is a message
send to an object*" sins as a new Smalltalker. I wonder now, with the
incredible speed of Cog, Spur, Sista, etc., if these devil's bargains from
prior decades are still necessary. It would be psychologically satisfying
(and nice for newbies) to remove the asterisk from the principle above.

p.s. IIRC e.g. Opal has flags to modify this behavior, but could the actual
messages be sent by default, and the flag be set to inline when that
performance boost is actually required?

You can turn off all those optimizations in your image except for potentially infinite loops.

The performance drop will be larger than you may think, because Cog has its own native implementation for most if not all those methods. So it'll skip both primitives and special bytecodes when possible and inline them into the jitted code (This can result in nasty debug situations when the jitter code's behavior differs from what the image side code suggests).

Also, most of those methods are not newbie-safe. Changing any of them will probably freeze your image.

Finally, these changes can only be done on the image side, so this question probably belongs to another list.

Levente




-----
Cheers,
Sean
--
Sent from: http://forum.world.st/Squeak-VM-f104410.html