Haskell Naming Conventions and the Cloud Haskell API

98 views
Skip to first unread message

Tim Watson

unread,
Apr 5, 2013, 10:34:40 AM4/5/13
to parallel...@googlegroups.com
I'm currently adding a function to the distributed-process API that allows you to send messages to another local process without serialising them - thereby avoiding the unnecessary runtime overhead at the cost of potentially introducing bugs when passed data structure are not fully evaluated. This works just fine if the data structures you're passing around are made up of only strict fields, for example. 

I was thinking of calling this version of send 'unsafeSend' but then it's not really 'unsafe' as such, more like "risky, use with caution". On the other hand, the difference between these two functions is really that one handles its input strictly (by forcing total evaluation) whilst another does not (by just assuming that the user knows what they're doing). Actually "handles its input" isn't quite right, because neither function is strict in its arguments. So what is the right convention here - should we have 

1. send (always serialised) & unsafeSend (only serialised for remote target sites)
2. send' (always serialised) & send (only serialised for remote target sites)
3. something else (send & lazySend perhaps?)

I'm hoping someone can help me determine the correct idiomatic naming convention here.

Cheers,
Tim 

AlanKim Zimmerman

unread,
Apr 5, 2013, 11:23:15 AM4/5/13
to watson....@gmail.com, parallel...@googlegroups.com
What about send and localSend?

Alan


--
You received this message because you are subscribed to the Google Groups "parallel-haskell" group.
To unsubscribe from this group and stop receiving emails from it, send an email to parallel-haske...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Tim Watson

unread,
Apr 5, 2013, 11:53:55 AM4/5/13
to AlanKim Zimmerman, parallel...@googlegroups.com
I thought about that, but there's a catch. Currently send is implemented something along the lines of

> if (isLocal destination) then sendRemote else sendLocal

And 'sendLocal' (which is a private function) does serialise but throws the result away and passes a pointer to the data structure locally.
 
I don't particularly want users to have to constantly decide whether the destination they're sending to is local or remote - this breaks the location transparency that is at the heart of message passing concurrency, and whilst in practise it is often useful to know whether you're making a remote call or not (given the potential overheads of transmission across the network) I don't think we should break the API up like this.

Instead, what I had in mind was something akin to

-- | Send a message [note about serialisation for local calls and the `decode . encode = id` rule]
send = if (isLocal dest) then sendLocal else ...

-- | Send a message [note about lack of serialisation for local calls and the risks]
unsafeSend = if (isLocal dest) then unsafeSendLocal else ...

Admittedly you're already making a choice there about the semantics of local (intra-node) message passing, but you can use the same call for remote calls and it will work either way. The decision becomes whether or not your data structure *needs* the special handling of serialisation, viz if all its fields are strict then the overhead is unnecessary. And just to be clear, the overhead of serialisation is many orders of magnitude (e.g., the difference between 1.3 seconds and 33.51 seconds when sending the same message around a ring of 10k processes 10 times). So having a data type with only strict fields and skipping serialisation for local calls will make a massive difference. And I don't want to write the whole d-p-platform API to have to provide two different sending semantics if possible. Generic servers and supervisors do lots of local message passing, which should just be optimised out of the box (because I control the data types) whilst public facing modules can provide two compatible APIs for either case, as our generic server will do, viz

> import qualified Control.Distributed.Process.Platform.ManagedProcess.Client.Unsafe as Client

or

> import qualified Control.Distributed.Process.Platform.ManagedProcess.Client as Client

Hope that makes sense.

Cheers,
Tim

Jeff Epstein

unread,
Apr 5, 2013, 12:23:03 PM4/5/13
to watson....@gmail.com, AlanKim Zimmerman, parallel...@googlegroups.com
I like unsafeSend. The function is unsafe, in the sense of possibly
resulting in incongruous behavior, and breaking the abstraction that
presents local and remote processes as the same. I hold that this is
an instance of an unsafe leaky abstraction, similar to, for example,
ByteString.Unsafe, which can break referential transparency; it is
still unsafe, even if it won't necessarily lead to a segfault. The
"unsafe" prefix, then, serves as a necessary warning.

Jeff

Tim Watson

unread,
Apr 5, 2013, 12:55:23 PM4/5/13
to Jeff Epstein, AlanKim Zimmerman, parallel...@googlegroups.com
I'm inclined to agree with Jeff. I'll be poking around with this over the weekend, so if anyone else wants to voice an opinion, now's the time.

Cheers Jeff.

Tim

Andrew Cowie

unread,
Apr 5, 2013, 11:19:53 PM4/5/13
to parallel...@googlegroups.com
On Fri, 2013-04-05 at 16:53 +0100, Tim Watson wrote:

> I don't particularly want users to have to constantly decide whether
> the destination they're sending to is local or remote

I suppose this all follows from the premise of being explicit about
costs (sic), but I must admit that I would have thought this is the sort
of detail that could be optimized away by some type system magic?

You give the example of a supervisor process, but what happens when
_that_ has to become distributed? Suddenly you have to re-write all your
management code, just at a time when you need to scale up and thought
the "cloud" platform that would Just Work won't.

Perhaps the right place to acknowledge the cost of serialization is not
at the code level, as was previously supposed; if I'm running something
in a single process then great! Of course it should be local memory
fast. But if I have to scale that task out over multiple machines, then
I surely have acknowledged that there will be communication overhead
which will hopefully be subsumed by my ability to throw [massive]
numbers of additional nodes at the problem.

AfC
Sydney

signature.asc

Gershom Bazerman

unread,
Apr 5, 2013, 11:46:23 PM4/5/13
to parallel...@googlegroups.com
'unsafe' is overloaded enough already and the different uses lead to all
sorts of confusion. Better to pick something different with the same gist.

`sloppySend` perhaps?

--g

Tim Watson

unread,
Apr 6, 2013, 8:08:38 AM4/6/13
to and...@operationaldynamics.com, parallel...@googlegroups.com
HI Andrew,

On 6 Apr 2013, at 04:19, Andrew Cowie wrote:

> On Fri, 2013-04-05 at 16:53 +0100, Tim Watson wrote:
>
>> I don't particularly want users to have to constantly decide whether
>> the destination they're sending to is local or remote
>
> I suppose this all follows from the premise of being explicit about
> costs (sic), but I must admit that I would have thought this is the sort
> of detail that could be optimized away by some type system magic?
>

If only it could. Sadly we cannot guarantee that an instance of, say NFDATA will evaluate the data structure in the same way as the Binary instance, and since people can implement the instances themselves, the only way to guarantee that the strictness properties on the receiving end are precisely the same for both local and remote message passing is to also use the Binary instance in the local case, but throw away its ByteString result and pass the pointer instead.

> You give the example of a supervisor process, but what happens when
> _that_ has to become distributed? Suddenly you have to re-write all your
> management code, just at a time when you need to scale up and thought
> the "cloud" platform that would Just Work won't.
>

What? I think you've completely misunderstood me - I'm arguing for exactly the opposite. I want the caller to evaluate `send pid msg` and have that work consistently regardless of the location of the other process. But if the type of `msg` is fully strict, then we can skip the serialisation. But there's no way to magically skip the serialisation. This situation is gross, but I haven't found a way around it.

> Perhaps the right place to acknowledge the cost of serialization is not
> at the code level, as was previously supposed; if I'm running something
> in a single process then great! Of course it should be local memory
> fast. But if I have to scale that task out over multiple machines, then
> I surely have acknowledged that there will be communication overhead
> which will hopefully be subsumed by my ability to throw [massive]
> numbers of additional nodes at the problem.
>

What exactly are you arguing for here? Are you suggesting we distinguish between local and remote sending only, and that local send should be the one that is optimised (and skips serialisation)? I don't see how that's really an improvement here, as 99% of code doesn't know where the other process resides - after all that would violate encapsulation - and therefore will use `send` and incur massive overheads if a lot of local communication takes place. I don't object to exporting `localSend` if you'd rather make that distinction yourself, but 90% of the code in distributed-process-platform uses data types that are entirely strict (in their fields) and does lots of local message passing. The improvements once no serialisation takes place are terrific.

Cheers,
Tim

Tim Watson

unread,
Apr 6, 2013, 8:15:13 AM4/6/13
to gers...@gmail.com, parallel...@googlegroups.com
On 6 Apr 2013, at 04:46, Gershom Bazerman wrote:
> 'unsafe' is overloaded enough already and the different uses lead to all sorts of confusion. Better to pick something different with the same gist.
>
> `sloppySend` perhaps?
>

Hehe, I like that. Or `carelessSend` perhaps? ;)

I had my doubts about 'unsafe' too, and Duncan suggested `lazySend` which is probably the most descriptive, though it feels very odd to me.

AlanKim Zimmerman

unread,
Apr 6, 2013, 8:36:23 AM4/6/13
to parallel...@googlegroups.com
It seems to me that if we have some kind of "well-behaved" serialisation / strictness of the structure then the optimisation is safe.

Is there any other way we can check for this property, perhaps in a test environment, and then use the optimised version freely?


Tim Watson

unread,
Apr 6, 2013, 9:43:17 AM4/6/13
to alan...@gmail.com, parallel...@googlegroups.com
Hi,

On 6 Apr 2013, at 13:36, AlanKim Zimmerman wrote:

> It seems to me that if we have some kind of "well-behaved" serialisation / strictness of the structure then the optimisation is safe.
>

Precisely.

> Is there any other way we can check for this property, perhaps in a test environment, and then use the optimised version freely?
>

I'm not sure what this means. Are you suggesting that we perform some kind of post build/compile verification to determine whether or not we can skip serialisation? Or that it would be possible to use some kind of flags (or environment specific configuration, or whatever) to get the cloud haskell runtime components to switch to the 'unsafe' version if you've tested it and have confidence that you know what you're doing? For the latter, wouldn't it do to simply use a qualified import for the send primitive that suits your application's specific requirements? Is this an issue in terms of the existing set of primitives. Currently there are a few isolated places in distributed-process that utilise 'send' and all of these will need to continue doing so, unless we've defined the data being passed ourselves (about 50% of the time this is true), because it's unreasonable to make assumptions about user code that's utilising our APIs. I'll vet these to make sure we're taking advantage of optimisations where it's clearly safe, but as for other places in the distributed-process code base, they're few and far between. It's mainly in distributed-process-platform, d-p-global and the various benchmarks that this crops up.

Cheers,
Tim

AlanKim Zimmerman

unread,
Apr 6, 2013, 9:53:37 AM4/6/13
to parallel...@googlegroups.com
I was thinking of some kind of utility, perhaps based on a new class that the serialized structures need to comply with, that would allow us to generate some e.g. quickcheck tests that would confirm they were safe. So we would end up with automatically derived tests that could clarify if a given serialization scheme /data structure was suitable.

Its a bit hand-waving, unfortunately.

I was considering something similar to confirm that the signature of a function that invokes call on a gen_server matches (with appropriate transformations for State) the handler signature.

Thomas Horstmeyer

unread,
Apr 9, 2013, 11:18:42 AM4/9/13
to parallel...@googlegroups.com
Hi Tim,

Am 06.04.2013 14:08, schrieb Tim Watson:
> Sadly we cannot guarantee that an instance of, say
> NFDATA will evaluate the data structure in the same way as the Binary
> instance, and since people can implement the instances themselves,
> the only way to guarantee that the strictness properties on the
> receiving end are precisely the same for both local and remote
> message passing is to also use the Binary instance in the local case,
> but throw away its ByteString result and pass the pointer instead.
>

You could also ask the user to confirm the equality of the evaluation.
That is, introduce a class to witness the equality of evaluation:

class (NFData a, Serializable a) => Sendable a where


And then have your sendFunction

(Sendable a) => optSend :: ...


In giving an instance for (Sendable b) the user confirms that evaluation
equality between serialize and rnf is (sufficiently) met for type b.


As it happens, today we Eden developers were discussing whether we could
implement your API using the Eden RTS as a student project. What we
would like to do, among other things, is use our existing RTS-level
serialization. However, we run into the same problem that we can not do
your evaluation without your serialization. So, if we follow that route,
we will probably switch from Serializable- to NFData-constraints. A
Sendable-constraint would be much more comfortable for us :-D


Thomas

Tim Watson

unread,
Apr 9, 2013, 12:39:57 PM4/9/13
to hors...@mathematik.uni-marburg.de, parallel...@googlegroups.com
Right. I actually think that's vastly preferable to the constraints we've been discussing up until now, in that the user is going to be responsible for ensuring correct behaviour either way, but with your approach, we get a nicer way of describing that.

The question now however, is whether we keep 'send' and co as is and add this Sendable class and overloaded API calls in addition, or whether we use Sendable all over the place (and add TH support for it). I prefer the latter approach, as clearly do you, however I'd like to get a steer from Duncan and Edsko on this.

Any comment on this guys? I think either way we're putting the ball in the user's court and the Sendable approach is simpler for them (and keeps potential API complexity to a minimum). I hate the name Sendable though. ;)

Tim Watson

unread,
Apr 9, 2013, 4:16:45 PM4/9/13
to Tim Watson, hors...@mathematik.uni-marburg.de, parallel...@googlegroups.com
On 9 Apr 2013, at 17:39, Tim Watson <watson....@gmail.com> wrote:
>
> The question now however, is whether we keep 'send' and co as is and add this Sendable class and overloaded API calls in addition, or whether we use Sendable all over the place (and add TH support for it).

I should clarify what I mean by that. Many CH API calls make use of send/forward and if we provide optSend the we've got to provide at least a subset of the rest too, viz optNSend, optNSendRemote, optRelay, optBlah....

That's not to mention that the -platform libraries use local sends extensively and they're providing raw infrastructure so it's even more important to avoid bottlenecks there.

I'd prefer not to massively increase the API surface area, but I'd also prefer not to incur the serialization hit for local sends in the vast majority of cases.

Tim Watson

unread,
Apr 9, 2013, 4:49:24 PM4/9/13
to Tim Watson, hors...@mathematik.uni-marburg.de, parallel...@googlegroups.com
On 9 Apr 2013, at 21:16, Tim Watson <watson....@gmail.com> wrote:
> we've got to provide at least a subset of the rest too, viz optNSend, optNSendRemote, optRelay, optBlah....
>

s/optNSendRemote// though.

Konstantine Rybnikov

unread,
Apr 11, 2013, 3:33:54 AM4/11/13
to parallel...@googlegroups.com
Maybe I miss something, but if it sends message without serializing it -- I'd suggest to call it sendNonSerialized. It's more detail than any of unsafeSend / lazySend, and it also has less chances to disinform. "Unsafeness" and "laziness" should be just in docs IMHO.

пятница, 5 апреля 2013 г., 17:34:40 UTC+3 пользователь Tim Watson написал:

Tim Watson

unread,
Apr 11, 2013, 4:26:47 AM4/11/13
to k-...@k-bx.com, parallel...@googlegroups.com
That wouldn't make sense in the remote case though. Having different primitives for local vs remote sends would take us away from our "do what erlang does" mantra.
--

Edsko de Vries

unread,
Apr 11, 2013, 4:36:41 AM4/11/13
to watson....@gmail.com, k-...@k-bx.com, parallel...@googlegroups.com
I'm not convinced that a Sendable class fixes anything -- in particular, if you really want to optimise your local sends, you will probably prefer to not even have the NFData constraint -- i.e., no forcing at all. In that case a Sendable class doesn't help, because now the semantics of send vs. unsafeSend truly is different.

My vote is for 'unsafeSend'. Yes, 'unsafe' is overloaded, but it makes it very clear that you have to be very careful with this construct. After all, if you do use it, then if the destination of the send goes from remote to local, you might suddenly get very different memory behaviour (space leaks, etc.). So it really is something to be careful about. '

Personally I think 'lazySend' is a misnomer, because it is only lazy in the local case. 

Edsko

Tim Watson

unread,
Apr 11, 2013, 4:48:30 AM4/11/13
to Edsko de Vries, k-...@k-bx.com, parallel...@googlegroups.com
That's three votes for unsafe and two of them from maintainers/owners, so I'd say unsafe is the way we'll go. Edsko - I assume you're ok with adding some overloads of API calls that currently use 'send' to use unsafe instead? I could get around that in ocaml by parameterising the primitives module with the send API provider (an ml style functor), but I've no idea how to do that in Haskell - is there a way to avoid adding usage overloads for stuff like relay :: ProcessId -> Process () ?

One way you could do it I suppose, would be to duplicate those primitives in two different modules and pull them in qualified as

Import Control.D.P hiding 
  (send, relay, etc..)
import qualified C.D.P.UnsafePrimitives
  (send, relay, etc..)

Is there a better way?

Tim Watson

unread,
Apr 26, 2013, 9:23:35 AM4/26/13
to Edsko de Vries, k-...@k-bx.com, parallel...@googlegroups.com
This has been implemented and is sitting in the `unsafe-primitives` branch: https://github.com/haskell-distributed/distributed-process/compare/development...unsafe-primitives

It is scheduled for inclusion in the (long awaited) forthcoming 0.5.0 release.

Harold Carr

unread,
Aug 4, 2013, 7:45:25 PM8/4/13
to parallel...@googlegroups.com, Edsko de Vries, k-...@k-bx.com
Just a thought (I'll introduce myself later, heading out the door right now):

"Unsafe" for what.   I tend to favor API that say what they do, so, in this case, perhaps nonSerializedSend.   Of course, if one wants to do other "unsafe" things with the same API, then "nonSerialized" is too specific.

Regards,
Harold
Reply all
Reply to author
Forward
0 new messages