From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00 autolearn=ham autolearn_force=no version=3.4.4 X-Google-Language: ENGLISH,ASCII-7-bit X-Google-Thread: 103376,4c86cf2332cbe682 X-Google-Attributes: gid103376,public X-Google-ArrivalTime: 1995-02-01 12:46:23 PST Newsgroups: comp.lang.ada Path: nntp.gmd.de!Germany.EU.net!wizard.pn.com!satisfied.elf.com!news.mathworks.com!news2.near.net!das-news2.harvard.edu!cantaloupe.srv.cs.cmu.edu!bb3.andrew.cmu.edu!news.sei.cmu.edu!firth From: firth@sei.cmu.edu (Robert Firth) Subject: Re: Ada.strings.bounded problems? Message-ID: <1995Feb1.154623.5483@sei.cmu.edu> Sender: netnews@sei.cmu.edu (Netnews) Organization: Software Engineering Institute References: Date: Wed, 1 Feb 1995 15:46:23 EST Date: 1995-02-01T15:46:23-05:00 List-Id: In article brennan@panther.warm.inmet.com (William Brennan) writes: >Please, what are "passive tasks"? (Non-periodic ones?) >Why is it important for the optimizer to recognize them? Let's start with a typical problem: two tasks, a producer and a consumer, connected by a bounded buffer. The producer creates data and drops each datum into the buffer; the consumer takes the data out and consumes them. If the buffer is full, the producer is suspended; if empty, the consumer is suspended. Producer: forever do { produce(datum); put(buffer, datum) } Consumer: forever do { get(buffer, datum); consume(datum) } In many older realtime systems, we had the "buffer" as a primitive of the kernel, and so the above system had two "real" tasks, a bit of hairy assembler in the get and put primitives, and, typically, two context switches for each burst of data, or, at worst, for each datum. The paradigm is called "loose coupling": producer and consumer can run at variable rates, and the buffer takes up the slack. But the Ada task communication mechanism, the rendezvous, requires tight coupling. Producer must hand each datum directly to consumer, like a runner in a relay race passing the baton. And this may not be convenient. The canonical solution is to write a third task, called Buffer, with Get and Put as entries. The producer calls Buffer.Put, the consumer calls Buffer.Get, and the Buffer maintains an internal data structure to buffer the data: loop select when buffer_not_full => accept Put do ... end; or when buffer_not_empty => accept Get do ... end; end select; end loop; Canonically, that's *four* context switches per datum: from Producer to Buffer and back, and from Consumer to Buffer and back. A not inconsiderable overhead. However, the buffer task is "passive". That is, it has no independent thread of control; it never does anything on its own. Apart from the elaboration of the select statement and its guards, *all* the processing is done during a rendezvous with somebody else. The optimisation, then, is to perform that processing in the context of the *entry caller*, and then you never have to create a real buffer task: no stack, no context switch; the entry call is (almost) a procedure call. Trouble is, that's not an easy optimisation, not least because it involves a rather complex loop rotation: the guards are evaluated not at the top of the loop, but at the bottom of the *previous* loop, folded into the last rendezvous. Even more trouble is, that routine maintenance of the Buffer code may quite abruptly make the optimisation go away, either by making it formally impossible or by defeating the compiler's ingenuity. So the code is very, very fragile. That's the technical gist of it; you've almost certainly guessed my opinion. Hope that helps. Robert Firth