Struggling to implement parallel foreach...

Timon Gehr timon.gehr at gmx.ch
Mon Jun 17 23:56:47 UTC 2019


On 18.06.19 00:42, Manu wrote:
> On Tue, Jun 18, 2019 at 5:40 AM Timon Gehr via Digitalmars-d
> <digitalmars-d at puremagic.com> wrote:
>>
>> On 17.06.19 12:18, Nicholas Wilson wrote:
>>>
>>> That is all moot if mutable promotes to shared under "New Shared"™,
>>
>> I was discussing the current language. And even then, with old shared as
>> well as new shared, my interpretation is the correct one.
>>
>> What "New Shared" it is actually trying to do is to introduce a new
>> `threadsafe` qualifier. This doesn't conflict with `shared`. Roughly
>> speaking, `threadsafe` is to unshared and `shared` what `const` is to
>> mutable and `immutable`.
>>
>> threadsafe(int) <- inaccessible
>> threadsafe(shared(int)) <- same as shared(int), accessible
> 
> If this threadsafe concept is something that exists, I am not a party
> to any such conversation.
> ...

I think this is what you wanted the `shared`-qualifier to mean. I think 
`threadsafe` is the canonical descriptor for the concept.

> There was some discussion about promotion possibility (but that never
> talked about 'threadsafe'), and that was universally rejected.
> ...

A large part of the rejection was probably because `shared` is an 
existing qualifier and you argued as if it didn't already have a 
meaning. It's much easier to argue that a new primitive is useful if you 
don't need to argue at the same time that an existing primitive should 
be removed (people feel loss more strongly than gain ;)).

I think it is more productive to keep the qualifiers separate for the 
time being and then at some point in the discussion decide that perhaps 
yes, we can drop `shared` as a qualifier and move a restricted version 
of it into druntime.

> Making shared inhibit read/write needs to happen regardless of
> anything else to work according to current rules.
> 
>> The "New Shared" vision is to remove type qualifier support for `shared`
>> and to instead move `shared` into druntime in a restricted form. (Last
>> time I discussed this with Manu, I believe he was adamant that the
>> language shouldn't distinguish between shared and unshared data at all,
>> so we had a long unproductive debate.)
>> Then `shared` is repurposed to mean something completely different.
> 
> I don't know what you're talking about.
> Shared needs to have read/write access removed... I feel like that was
> universally agreed.

threadsafe(unshared) needs to have read/write access removed 
(completely, UB if you access with casts).

shared needs to have unsynchronized read/write access removed. There are 
multiple ways to go about this:

1. shared data cannot be accessed directly, all accesses need to go 
through special druntime functions.

2. direct accesses to shared data are atomic/sequentially consistent, 
other kinds of consistency guarantees are supported with special 
druntime functions.

3. direct accesses to shared data have low consistency guarantees (some 
sort of lowest-common-denominator of what's provided by weak memory 
models), if you want more consistency guarantees, you need to use 
special druntime functions.

Why is 1 better than 2 or 3? (I think Walter is actually leaning towards 
2 at the moment.)

>>From there, I can get to work.
> 
>>> I don't think anybody disagrees that qualified local functions should
>>> work.
>>
>> Yes, they do. Manu said you shouldn't be able to call an
>> `immutable`-qualified local function! This is extremely weird, because
>> it is always valid to drop a context qualifier!
>>
>>> It would be useful to decouple this from the the current
>>> discussion to avoid derailing.
>>
>> I think it's related to why Manu is so confused.
> 
> I don't think I'm as confused as you'd like to think.

I'd have liked to think you are exactly as confused as someone who 
argues that useful closure capturing behavior is a blocker for a 
parallel `foreach` implementation, but you have been slowly backing down 
on that claim, so it is indeed likely that you are less confused now 
than you seemed to be when I wrote that post. :)

> I understand I'm proposing to obliterate a thing. It's a complex and surprising thing,
> and as far as I can tell, it's useless, and only a point of friction
> and bugs. Can you show how it's useful?
> ...

The language needs a way to tell what kind of semantics are expected for 
variable accesses. The usefulness is that you (and the compiler 
implementer) know which code is guaranteed to keep working across 
compiler versions and which code is not guaranteed to work and you just 
got lucky and it might break on the next compiler update, leaving you 
with some weird concurrency bug that only happens on Tuesdays in release 
builds.

You absolutely need some notion of shared variable. Unshared is 
optional, but why pessimize thread-local code, after having gone through 
the effort to make globals thread-local by default? It is possible that 
a shared _qualifier_ is ultimately too expensive and it should be 
replaced by some _special_ druntime constructs (which also has 
non-trivial implications, and needs to be designed properly). The 
language will however in each case need know about shared and unshared 
memory locations.

>> Basically, there are two cases for local context qualifiers:
>>
>> 1. can implicitly promote mutable variable (`const`, `threadsafe`/"New
>> Shared")
>> 2. cannot implicitly promote mutable variable (`immutable`, (old) `shared`).
>>
>> So far, Manu only understands case 1, because there is a crutch: it can
>> be interpreted by creating a local context struct containing all local
>> variables in the stack frame, of which the local function is a method.
>> But case 2 exists even if we move from `shared` to `threadsafe`. Because
>> Manu only understands case 1, he considers the fact that `shared` ->
>> `threadsafe` /"New Shared" is a move from case 2 to case 1 as evidence
>> that old shared is broken while `threadsafe`/"New Shared" is not.
>>
>> This is the argument that is complete nonsense that I am so annoyed
>> about, because it leads to `immutable` capturing not working for no
>> reason at all! Skip step 1, where you break local function qualifiers.
> 
> Show me one example of an immutable local function in the wild;
> explain how it's useful? It's a meaningless concept; how can a
> functions capture be immutable? We have no language to qualify the
> capture, and certainly not immutable; the callstack is mutable.
> 

Answered in previous post.

> But I said before, solve however you like.
> 

I hope so.


More information about the Digitalmars-d mailing list