Re: The goals of the concurrency standard?

2006-04-12 Thread John Meacham
Fortunatly, I don't expect these issues to be much of a problem in
practice. (though, they certainly are real issues)

the vast majority of programs, haskell ones included, are either
interactive or batch. an interactive program spends most of its time
waiting for user input or external events, responding, and in general
using very little CPU, this is the sort of app threads are ideal for,
text editors, chat clients, window managers, etc... batch programs are
things like compilers or meresenne prime calcalculators, that tend to
accept input, run for a while, and produce output. however, these types
of programs rarely need concurrency.

not that all programs fall into those categories, one can write their
GUI enabled meresenne prime calculator, and then they will have to worry
about such things. but at least with the standard they can now say 'this
requires the OS threading option' rather than 'this requires GHC'.

in any case, the situation you describe has been the case in GHC for a
long time and has not seemed to hurt the use of concurrency for writing
a lot of useful apps.

However, I am also of the mind that preemtiveness alone doesn't buy
enough to make the runtime cost of locking worth it which is why I plan
for jhc to be fully cooperative or fully OS threaded with no middle
ground. but the situation is different in compilers such as nhc, where
preemptiveness can be added relatively easily due to its run-time design
and absolute speed was never a goal. In any case, the standard should
admit a range of implementations.

though, your erlang example reminds me of a truely horrid hack I did
with jhc once when experimenting with concurrency models, link two
instances of haskell programs together and have them communicate via the
FFI while running in different OS threads :)

John

-- 
John Meacham - ⑆repetae.net⑆john⑈
___
Haskell-prime mailing list
Haskell-prime@haskell.org
http://haskell.org/mailman/listinfo/haskell-prime


RE: The goals of the concurrency standard?

2006-04-12 Thread Simon Marlow
On 12 April 2006 08:41, John Meacham wrote:

 However, I am also of the mind that preemtiveness alone doesn't buy
 enough to make the runtime cost of locking worth it which is why I
 plan for jhc to be fully cooperative or fully OS threaded with no
 middle ground. but the situation is different in compilers such as
 nhc, where preemptiveness can be added relatively easily due to its
 run-time design and absolute speed was never a goal. In any case, the
 standard should admit a range of implementations.

Couldn't pre-emption be implemented quite easily in JHC if you compiled
to C--?  And imprecise exceptions, for that matter.

Cheers,
Simon
___
Haskell-prime mailing list
Haskell-prime@haskell.org
http://haskell.org/mailman/listinfo/haskell-prime


RE: The goals of the concurrency standard?

2006-04-12 Thread Simon Marlow
On 12 April 2006 07:46, [EMAIL PROTECTED] wrote:

 I am afraid I must ask for the clarification of the goals of this
 discrimination between pre-emptive and cooperative scheduling -- which
 leads me to further questions.
 
 One one hand, I understand the motivation: providing guidance to the
 end programmer. In a cooperative thread system, the programmer just
 can't write code imagining the current thread is the only one. If the
 thread is to compute the Nth Mersenne prime, it would effectively hang
 the whole system. So, the user must specifically insert calls to yield
 -- which means that the whole code has to be converted into a monadic
 style, because yield is most certainly a monadic operation _and_
 because we must be sure that computations before yield are indeed
 finished, rather than snowball up to the very end. So, we have to
 re-write pure functional code (like the Mersenne prime computation)
 into a monadic one, and thus explicitly serialize it. That negates one
 of the significant benefits of Haskell -- no longer code looks like a
 specification or mathematical notation. If we're forced to write
 everything in the A-normal form, we might as well use a better
 language for that, like SML.
 
 In a preemptive thread system, we can indeed program a thread as if it
 were the only thread in the system -- and the scheduler would do the
 rest.

 Problem solved? Only if threads do not interact, which is not
 likely. At least they have to write something, and so contend for the
 access to stdout (assuming multi-line output). A thread running a long
 computation while holding a critical resource can just as well hang
 the whole system, pre-emptive scheduler notwithstanding. Lazy
 evaluation of Haskell makes this problem quite subtle:
 
   do
   r - return (mersenne' 44)
   acquire'lock
   putStrLn Great success
   print r
   release'lock
 
 One may think that the long computation occurs in the r - ... line.
 Alas, it may just as well occur in the print r line, when the digits
 of the number are really required to print. So, we will be running a
 long computation while holding a lock. This isn't much better than the
 situation with the cooperative scheduling, is it?

In cases where this happens, and it *does* happen, the programmer has to
insert explicit seq or deepSeq.  It's not a huge problem, at least no
worse than the need to use seq/deepSeq to control resource usage or
exception leakage.  The code inside a lock/unlock sequence is usually
quite short, and we can enumerate the free variables quite easily.

One example where this happened in GHC is in the definition of putStr
itself: our first implementation of putStr took the Handle lock, and
then proceeded to traverse the string argument, filling the Handle's
buffer with the characters.  Unfortunately traversing the string takes
arbitrary time, and might even invoke further putStrs (inside
unsafePerformIO), so we had to rethink.  The easy way is to deepSeq the
string first, but that means traversing the string twice, and we don't
like to make putStr any less efficient than it already is.  So currently
putStr works by grabbing a buffer from the Handle, and filling this
buffer without holding the Handle lock.  When the buffer is full, it
gets committed.  The Handle keeps a list of free buffers to avoid
having to allocate a new one for each putStr.  One side-effect of this
is that putStrs from multiple threads can be interleaved in strange
ways.

Fortunately STM helps a lot here, too.  (which doesn't mean much for
Haskell', but at least it means we have a solution without completely
redesigning Haskell').  If a transaction takes a long time because it is
busy evaluating free variables, that doesn't prevent other transactions
from completing.  All that happens is the long-running transaction will
probably be aborted due to conflicts with other transactions, but the
next time it runs it will probably complete because the work already
done isn't discarded.  Conclusion: the programmer doesn't have to think
about it.

Cheers,
Simon
___
Haskell-prime mailing list
Haskell-prime@haskell.org
http://haskell.org/mailman/listinfo/haskell-prime


RE: The goals of the concurrency standard?

2006-04-12 Thread Simon Marlow
On 12 April 2006 11:03, John Meacham wrote:

 On Wed, Apr 12, 2006 at 10:24:57AM +0100, Simon Marlow wrote:
 On 12 April 2006 08:41, John Meacham wrote:
 
 However, I am also of the mind that preemtiveness alone doesn't buy
 enough to make the runtime cost of locking worth it which is why I
 plan for jhc to be fully cooperative or fully OS threaded with no
 middle ground. but the situation is different in compilers such as
 nhc, where preemptiveness can be added relatively easily due to its
 run-time design and absolute speed was never a goal. In any case,
 the standard should admit a range of implementations.
 
 Couldn't pre-emption be implemented quite easily in JHC if you
 compiled to C--?  And imprecise exceptions, for that matter.
 
 no, it doesn't really help, the main things C-- provides over C are
 continuations and the introspection into the stack needed for garbage
 collection. everything c-- continuations would be useful for I am
 already using longjmp and setjmp for for cooperative concurrency.
 mainly jumping between multiple C stacks (which are the same as
 haskell stacks).
 
 the issues facing a preemptive implemenentation are that
 there is no way to implement black-holing, multiple threads will
 happily waste time evaluating the same thunk, but worse, updates would
 have to be protected by some sort of mutex or lock. jhc updates nodes
 in place, meaning a single atomic write to an indirection can't be
 used, to avoid a context switch in the middle of an update, either
 locks need to be placed around it, or run-time checks need to be made
 at safe points (which is arguably not much different than cooperative
 scheduling). 

I'll argue that point :)  GHC makes run-time checks at safe points and
implements preemptive concurrency.  Cooperative scheduling is when the
*programmer* has to insert the safe points.

The safe points don't even have to be very often: in GHC the context
switch check is made after every 4k of allocation.

Point taken about black holes.

Cheers,
Simon
___
Haskell-prime mailing list
Haskell-prime@haskell.org
http://haskell.org/mailman/listinfo/haskell-prime


Re: The goals of the concurrency standard?

2006-04-12 Thread John Meacham
On Wed, Apr 12, 2006 at 11:19:53AM +0100, Simon Marlow wrote:
 I'll argue that point :)  GHC makes run-time checks at safe points and
 implements preemptive concurrency.  Cooperative scheduling is when the
 *programmer* has to insert the safe points.

the programmer of the standard libraries or low level FFI interfaces.
not the end programmer. I would be highly surprised if anyone other than
system or very low level library implementors ever actually needed to
use an explicit 'yield'. certainly a whole lot less often than they have
to add 'seq's and it is a lot more clear when they are needed :)

 The safe points don't even have to be very often: in GHC the context
 switch check is made after every 4k of allocation.

indeed, which means GHC technically doesn't meet the preemptive
requirements since a tight mathematical non-allocating loop can halt it.

in order to do true preemption, you'd need to respond to SIGALRM or
something like that, which can be quite tricky.

John

-- 
John Meacham - ⑆repetae.net⑆john⑈
___
Haskell-prime mailing list
Haskell-prime@haskell.org
http://haskell.org/mailman/listinfo/haskell-prime


RE: The goals of the concurrency standard?

2006-04-12 Thread Simon Marlow
On 12 April 2006 11:30, John Meacham wrote:

 On Wed, Apr 12, 2006 at 11:19:53AM +0100, Simon Marlow wrote:

 The safe points don't even have to be very often: in GHC the context
 switch check is made after every 4k of allocation.
 
 indeed, which means GHC technically doesn't meet the preemptive
 requirements since a tight mathematical non-allocating loop can halt
 it. 
 
 in order to do true preemption, you'd need to respond to SIGALRM or
 something like that, which can be quite tricky.

Not at all, we could make non-allocating loops bump the heap pointer and
do heap checks, but we choose not to.  It's just a bug that we don't do
this, and it virtually never happens in practice.

Cheers,
Simon
___
Haskell-prime mailing list
Haskell-prime@haskell.org
http://haskell.org/mailman/listinfo/haskell-prime


RE: The goals of the concurrency standard?

2006-04-12 Thread oleg

Simon Marlow wrote:
  do
  r - return (mersenne' 44)
  acquire'lock
  putStrLn Great success
  print r
  release'lock
  One may think that the long computation occurs in the r - ... line.
  Alas, it may just as well occur in the print r line, when the digits
  of the number are really required to print. So, we will be running a
  long computation while holding a lock. This isn't much better than the
  situation with the cooperative scheduling, is it?

 In cases where this happens, and it *does* happen, the programmer has to
 insert explicit seq or deepSeq.  It's not a huge problem, at least no
 worse than the need to use seq/deepSeq to control resource usage or
 exception leakage.  The code inside a lock/unlock sequence is usually
 quite short, and we can enumerate the free variables quite easily.

My main trouble is foundational. The very need to use
seq/deepSeq is quite disturbing: in the above example, we must use
those constructions (and so, deepSeq must be standardized, if we take
this route). Evaluation time (and divergence) is certainly an effect;
when composing with another effect such as locking, we must be able to
specify the exact sequence of those effects. Essentially, the need for
seq/deepSeq seems to tell that non-strict evaluation is an inadequate
computational model in the example at hand.

Incidentally, the need to manually insert deepSeq isn't that much
different from the need to insert safe points? In both cases, the
programmer must be aware how much of the computation has already been
done at (some) program points, and be able to control the amount of
computation done. Strictly speaking, none of that is possible under
non-strict evaluation.

 Fortunately STM helps a lot here, too. 
Yes, it does. The previous message pointed out that the STM model
seems to be similar to the one used in Erlang. With STM, we can keep
lazy evaluation and the programmer does not need to think of
seq/deepSeq, etc. But there is a price to pay: we can't use FFI while
in the STM monad, we can't use IORefs and we can't use MVars. Right?
So, if the STM model is to become the default one (which would be a
great thing), FFI and the whole IO will have to be re-designed, and
IORefs and MVars removed from the language.

___
Haskell-prime mailing list
Haskell-prime@haskell.org
http://haskell.org/mailman/listinfo/haskell-prime