Hi Chapel Developers --

Here's a semantic question that comes up perennially and is coming up 
again for me this week:

Traditionally, Chapel has kept variables that are logically on the stack 
alive after their lexical scope ends if there are other asynchronous tasks 
referring to them.  For example, in code like this:

        {
          var A: [1..10] real;

          begin foo(A);

          ...

        } // A leaves scope here

if foo(A) is long-running and we hit the end of the lexical scope first, 
we will not deallocate A, but will keep it alive for foo()'s benefit. 
This choice has traditionally been advocated for in order to keep the 
programmer's life simple (and when asked in the past, key users have asked 
to have the current behavior preserved).


Unfortunately, the effect of this choice is that it makes the 
implementation far more complicated, and in many cases has led to 
inefficiency and/or incorrectness in the current implementation (e.g., 
memory leaks due to bugs, performance hits due to reference counting, 
etc.).


In 1:1 discussions this week, we smacked back into this issue once again, 
and I found myself (again) curious whether we could move away from these 
semantics and say that if the user has an asynchronous task referring to 
data that has been reclaimed through traditional lexical scoping, their 
program is incorrect.

To that end, I wanted to ask this group whether anyone had any qualms 
about exploring this question again (e.g., reasons that we rely on it, or 
will want to, in key cases), or any reasons that we shouldn't put this 
question to the user community.


One downside to the proposed change is that incorrect programs might 
result in segfaults.  My hope would be that a good Chapel implementation 
would introduce runtime checks in cases where the compiler can't determine 
whether a task will complete before its referenced variables leave scope, 
with the option to turn these checks off when performance is desired. I'm 
hoping that the current analysis that moves such variables to the heap can 
be leveraged to create these checks without much difficulty.  That said, 
I'm tired enough of this issue that I think I'd be willing to switch the 
semantics even if the runtime checks were not yet implemented.


My thinking for why this doesn't seem likely to be the end of the world 
for anyone is as follows:

* The only time this case comes up is when using 'begin's, and our
   experience so far has been that 'coforall's, 'cobegin's are the
   far more used ways of creating parallelism in Chapel programs (and
   data parallelism of course, which almost always uses coforalls).
   Many cases of 'begin's are surrounded at some scope by 'sync' scopes
   which also help guard against such cases.

* It used to be the case that all variables went into tasks by reference,
   so even cases like this needed protection:

        {
          var x: int;

          begin { ...compute big block of code using 'x'... }

        } // x leaves scope here

   However, with the introduction of task intent semantics, scalar types
   like this are passed to tasks by 'const in' intent, therefore the
   'x' need not be kept alive after its scope closes.  Of course, one
   could use a 'ref' clause to get back to the old behavior:

        {
          var x: int;

          begin ref(x) { ...compute big block of code using 'x'... }

        } // x leaves scope here

   but I'm guessing that this is not a common idiom and that even when
   it is, it's reasonable to require the user to not rely on Chapel
   keeping 'x' alive for them.

   As a result, the main types which fall afoul of these issues are those
   passed by reference to tasks (e.g., arrays, syncs, atomics, ...).

* As I understand it, the XMT and X10 programming models (both of which
   also have asynchronous tasks like Chapel) adopt similar "it's the
   user's responsbility" semantics.



So -- does anybody want to argue in favor of keeping the current semantics 
rather than trying to make our lives easier as developers by changing 
them?


The bonus question is whether we should adopt similar semantics for 
operations that alias arrays.  E.g., if you slice an array or alias its 
elements and that slice/alias outlives the original array, should that be 
a user error rather than a means of keeping the array's elements alive 
under the covers?  I'm also very tempted to adopt this (because it would 
remove the need to reference count arrays for correctness purposes), but 
haven't thought through the implications enough to feel very confident 
about it yet.


Thanks very much for any thoughts,
-Brad


------------------------------------------------------------------------------
Dive into the World of Parallel Programming. The Go Parallel Website,
sponsored by Intel and developed in partnership with Slashdot Media, is your
hub for all things parallel software development, from weekly thought
leadership blogs to news, videos, case studies, tutorials and more. Take a
look and join the conversation now. http://goparallel.sourceforge.net/
_______________________________________________
Chapel-developers mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/chapel-developers

Reply via email to