Not Squeak but... Ni goes multithreaded

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

Not Squeak but... Ni goes multithreaded

Göran Krampe
Hey guys!

I am unfortunately not hacking in Squeak/Pharo anymore but I am instead
playing along with my Ni-language in my spare time. But the Squeak
community is still very dear to me and I just couldn't help sharing some
fun I had last night! ;)

As we all know native threads (multicore) and Smalltalk is not a "common
combo" but Ni is now sporting a first trivial native threading mechanism!

Last night I threw together a first hack on utilizing the native
threadpool module in the underlying Nim. So in the REPL this works:

gokr@yoda:~/nim/ni/src$ ./nirepl
We are the knights who say... Ni! Enter shrubbary... eh, code and
an empty line will evaluate previous lines, so hit enter twice.
>>> 5 timesRepeat: [spawn [echo "yo"]]
>>>
yo
yo
yo
yo
yo
nil
>>>

The "nil" at the end is the return value from timesRepeat: so nothing
"wrong". If the code looks odd (spawn and echo) it's because Ni can use
prefix syntax for functions as well as Smalltalkish style.

This is "shared nothing" threading, the spawned block gets run in a
completely separate instance of the Ni interpreter inside its own native
thread. The code block passed over is deep copied. Each native thread
has its own GC etc. Of course Nim has MUCH more advanced mechanisms for
intercommunication so... this is just a toe dipping.

...but the larger commented trixier example below runs fine too!

Next up is probably to try to make Ni "stackless" (rewrite the
interpreter so that it doesn't nest the calls so). This would enable
even more fun stuff.

regards, Göran
---------------------------------

# This is called a block in Ni. It is like an OrderedCollection.
# At this point the block is just a series of nested words and literals.
# Assignment is actually an infix function!
code = [
   # This is a local function to recursively calculate factorial.
   # ifelse is currently a 3-argument prefix function, but Ni could use
   # Smalltalk syntax for that too.

   factorial = func [
     # Arguments are sucked in as we go, so no need to declare n first
     ifelse (:n > 0)
       [n * factorial (n - 1)]
       [1]
   ]
   # Echo is a prefix function.
   echo factorial 1
]

# Ni has keyword messages that can take the first argument from the left.
# Ni also has closures and non local returns so we can implement
Smalltalkish
# control structures and things like select: or reject: easily. Or we
can write
# them as Nim primitive functions.
10 timesRepeat: [
   # Ni is homoiconic so we can modify the block as if it is code.
   # We remove the last element of the code block (the number) and add
   # a random number from 1-20.
   code removeLast

   # In Ni the parenthesis is currently needed, evaluation is strict
from left to right.
   code add: (20 random)

   # Spawn fires upp a native thread from a threadpool and
   # inside that thread a new fresh interpreter is created.
   # Spawn will deep copy the Ni node being passed and will
   # then run it as code in the new interpreter.
   # Currently the result value is not handled.
   spawn code
]
echo "Spawned off threads"

# Sleeping this thread to wait for the 10 above
sleep 1000
echo "Done sleeping, bye"

Reply | Threaded
Open this post in threaded view
|

Re: Not Squeak but... Ni goes multithreaded

David T. Lewis
Cool.

Are the numbers in the factorial example values on the stack, or are
they "objects" in the Smalltalk sense? I'm curious how Ni might be
approaching a shared object space for the interpreters.

Thanks for posting.
Dave

On Tue, Feb 02, 2016 at 01:40:03PM +0100, G??ran Krampe wrote:

> Hey guys!
>
> I am unfortunately not hacking in Squeak/Pharo anymore but I am instead
> playing along with my Ni-language in my spare time. But the Squeak
> community is still very dear to me and I just couldn't help sharing some
> fun I had last night! ;)
>
> As we all know native threads (multicore) and Smalltalk is not a "common
> combo" but Ni is now sporting a first trivial native threading mechanism!
>
> Last night I threw together a first hack on utilizing the native
> threadpool module in the underlying Nim. So in the REPL this works:
>
> gokr@yoda:~/nim/ni/src$ ./nirepl
> We are the knights who say... Ni! Enter shrubbary... eh, code and
> an empty line will evaluate previous lines, so hit enter twice.
> >>>5 timesRepeat: [spawn [echo "yo"]]
> >>>
> yo
> yo
> yo
> yo
> yo
> nil
> >>>
>
> The "nil" at the end is the return value from timesRepeat: so nothing
> "wrong". If the code looks odd (spawn and echo) it's because Ni can use
> prefix syntax for functions as well as Smalltalkish style.
>
> This is "shared nothing" threading, the spawned block gets run in a
> completely separate instance of the Ni interpreter inside its own native
> thread. The code block passed over is deep copied. Each native thread
> has its own GC etc. Of course Nim has MUCH more advanced mechanisms for
> intercommunication so... this is just a toe dipping.
>
> ...but the larger commented trixier example below runs fine too!
>
> Next up is probably to try to make Ni "stackless" (rewrite the
> interpreter so that it doesn't nest the calls so). This would enable
> even more fun stuff.
>
> regards, G??ran
> ---------------------------------
>
> # This is called a block in Ni. It is like an OrderedCollection.
> # At this point the block is just a series of nested words and literals.
> # Assignment is actually an infix function!
> code = [
>   # This is a local function to recursively calculate factorial.
>   # ifelse is currently a 3-argument prefix function, but Ni could use
>   # Smalltalk syntax for that too.
>
>   factorial = func [
>     # Arguments are sucked in as we go, so no need to declare n first
>     ifelse (:n > 0)
>       [n * factorial (n - 1)]
>       [1]
>   ]
>   # Echo is a prefix function.
>   echo factorial 1
> ]
>
> # Ni has keyword messages that can take the first argument from the left.
> # Ni also has closures and non local returns so we can implement
> Smalltalkish
> # control structures and things like select: or reject: easily. Or we
> can write
> # them as Nim primitive functions.
> 10 timesRepeat: [
>   # Ni is homoiconic so we can modify the block as if it is code.
>   # We remove the last element of the code block (the number) and add
>   # a random number from 1-20.
>   code removeLast
>
>   # In Ni the parenthesis is currently needed, evaluation is strict
> from left to right.
>   code add: (20 random)
>
>   # Spawn fires upp a native thread from a threadpool and
>   # inside that thread a new fresh interpreter is created.
>   # Spawn will deep copy the Ni node being passed and will
>   # then run it as code in the new interpreter.
>   # Currently the result value is not handled.
>   spawn code
> ]
> echo "Spawned off threads"
>
> # Sleeping this thread to wait for the 10 above
> sleep 1000
> echo "Done sleeping, bye"

Reply | Threaded
Open this post in threaded view
|

Re: Not Squeak but... Ni goes multithreaded

Göran Krampe
Hey!

On 02/02/2016 02:48 PM, David T. Lewis wrote:
> Cool.
>
> Are the numbers in the factorial example values on the stack, or are
> they "objects" in the Smalltalk sense? I'm curious how Ni might be
> approaching a shared object space for the interpreters.

In Ni everything is a "node" as in an AST Node. ints and floats are
boxed Nim ints/floats. Strings are boxed Nim strings and blocks are
boxed Nim seq (dynamic array). Dictionaries are boxed Nim OrderedTables.
true, false, nil and undef are singleton objects.

So they are Nim objects (which means the VM can actually do dynamic
dispatch on them using Nim's mechanism for that) but in Ni they are
"primitive types".

BUT... the model I am working on for "OO" in Ni is centered around
instance tagging (!) and dynamic dispatch using these tags of Ni funcs.
Since everything (ints floats etc) are nodes, they can be tagged and
thus "methods" can be polymorphically dispatched on everything.

And since Ni (just like Nim) offers "infix functions with more than one
argument, optionally using keyword syntax" you should get a very OO-ish
model.

So Ni uses Nim objects for the AST nodes and thus also uses Nim's GC (it
has 5 different ones!). The reified stack is also built with Nim objects
as a spaghetti stack so it too is GCd by Nim.

Nim has several interesting things going in the multithreading space. It
has an asynch model and it also has channels one can use between native
threads - the latter is used in a very nice article that Dennis Felsing
just wrote:

http://hookrace.net/blog/writing-an-async-logger-in-nim/

In fact, Dennis blog has a whole slew of brilliant Nim articles.

Exactly how Ni will piggy back on all these options is unclear at this
point - and oh, it uses FlowVars too.

But do note that the Nim GC runs *per thread* so to do memory sharing
between native threads in Nim you would need to use special shared
constructs. However... stuff is brewing in that area too in Nim.

But generally I am inclined towards a shared nothing model between
native threads (as I have now) but perhaps implementing coroutines or
something within a single thread. That is also why I need to go
stackless, so that I can manipulate the Ni stack without being hindered
by the Nim (C) stack.

regards, Göran

PS. I am still waiting for someone to join me in this coding adventure.
:) This is fresh ground here, but we have a tremendous solid base in the
Nim eco system.