Re: Lahman, how ya doing?
- From: glhansen@xxxxxxxxxxxxxxxxxxxxx (Gregory L. Hansen)
- Date: Mon, 9 May 2005 20:47:19 +0000 (UTC)
In article <kStfe.2486$Dn.2243@trndny02>,
H. S. Lahman <hsl@xxxxxxxxxxxxxxxxx> wrote:
>Responding to Hansen...
[Snipping a little. We really do tend to get long-winded.]
>> A priority queue is an interesting idea. I suppose when that empties it
>> goes to the normal queue. Some systems let your prioritize tasks on a
>> scale of 0 to 7 or so, I wonder if they have eight queues.
>
>Welcome to real-time. B-) I know R-T people dealing with hard R-T
>constraints who argued that WinCE was a unusable for R-T work because it
>only supported 32 priorities for threads.
I'm no R-T person, but... 32 ain't enough? Well, I'm sure they know what
they're doing.
>
>However, I suspect in your case things are a whole lot simpler.
Truth be told, in my case I think most of your advice is like using
an 8 pound sledge on a nail to hang a picture on the wall, except for the
instructional value.
>For the
>hypothesized example, the event queues would be entirely separate object
>instances for the threads. Since computing the average is so rarely
>done one can be confident it will get done long before it should be
>triggered again. So Timer just enqueues the event on the right queue.
>The Timer events will get consumed (popped) immediately so everything
>effectively starts at the same time on the current tick. Hence there is
>no need to merge events back into a "normal" queue; the processing that
>was triggered just gets time-sliced based on priority.
>
>>
>> But really, a lot of this just isn't much of a problem if timing
>> information is passed to an object. E.g. in our real equipment we have
>> some CAMAC counters that accumulate NIM pulses until they're stopped,
>> read, and reset. And one channel is connected to a precision time
>> standard. We're making a precision measurement, if the timing is off by
>> 1/60 second that would be horrible. But for as long as the other channels
>> count events from the charged particle detectors, the timing channel will
>> be counting ticks from the time standard, and we can simply divide to find
>> the counting rate to a precision that's limited by counting statistics but
>> not by time resolution.
>>
>> Unless there are gross timing errors that e.g. would cause the control
>> system to go unstable or get too choppy to collect good data, I think just
>> passing an elapsed time to the parts that need it is a simple and
>> satisfactory solution.
>
>Remember you already have a sort of priority in the order of event
>generation in Timer.
>
>I assume that the CAMAC counters are pure hardware (i.e., the count is
>incremented in a hardware register.)
Yep, all hardware. All counters start counting at the same time when
triggered, they all stop at the same time when stopped, and the values can
be read at one's leisure.
>At that point the simplest
>possible thing the driver would be to (1) write the stop register, (2)
>read the accumulator and save the value as an attribute in an existing
>object somewhere, and (3) write the reset register. That needs to
>happen /exactly/ on your 1/60th second tick no matter what else happens
>in the system.
The timing isn't crucial because there's a time standard feeding into one
of the counters to tell us how much time has passed. The CAMAC crate is
part of the measurement that I don't think I've told you about; they count
events from charged particle detectors in a beam monitor that is to be
calibrated. The calibration is done with the radiometer that measures the
heat of reaction of the beam dumping into a target. That radiometric
measurement is done by an HP voltmeter measuring voltage drops across the
heater resistor and a precision shunt resistor that gives current I=V/R.
The radiometric measurement depends on the heater current changing slowly
in a period after it's been allowed to settle. E.g. two minutes to
settle, average the power for the remaining eight minutes in a ten minute
half-cycle.
So we get a count rate = counts/time, and if we're late in triggering the
counters we get more counts, but we also get more timer ticks, it doesn't
change the rate.
So that's the background I'm working with-- it's not crucial to get the
timing exactly right, we just need to know what the timing is.
>So I would put that <rather trivial> operation in the
>highest priority thread (i.e., put the relevant event queue manager in
>that thread).
>
>That ensures that the count will be precise so long as the 1/60th event
>is consumed in a timely fashion. But you can get that for free from
>Timer by making that Timer event be the first one generated off the tick
>if it were a 1/60th tick.
>
>The next problem is making sure the processing that needs that count get
>done accessing it before another 1/60th tick is processed. That may
>Just Work because the value in the attribute is quickly processed and
>converted to some other form or a history of values are stored until
>they are processed. I suspect one of these would be the case even if
>the rest of the feedback processing was pretty low priority.
>
>However, if that isn't true there is a fallback position that is a
>variation on your proposal: pass the attribute value to subsequent
>processing rather than accessing it synchronously on an as-needed basis.
> This is a variation on the notion of "snapshot" data integrity. No
>matter how long the feedback loop processing takes, all you have to do
>is make sure that the first step extracts the value from the attribute
>and passes it to any subsequent processing via event data packets. Even
>at low priority that first step gets kicked off _on the same time tick_
>so it should be able to access the attribute stored by the first event
>before the next tick no matter how low its priority is.
I think I can connect to this by way of the CAMAC counters-- the counters
hold the snapshot. As long as it stops and starts counting the particle
detectors and the clock at the same time it doesn't matter, to a first
approximation, how long it takes to get the data out and process it.
>
>[In fact, you may have the opposite problem because the events are
>consumed in concurrent threads. That is, in theory the reader of the
>count attribute may read it before the CAMAC processing writes it.
>However, that is extremely unlikely so long as the CAMAC processing is
>kept as simple as above just because of the queue processing. And it
>would probably be easy to fix by putting the attribute access statement
>a couple of statements into the initial step's processing.]
>
>I bring this up because it makes another subtle distinction. Passing
>the data to be processed is semantically different than passing timing
>information. By passing the data one is explicitly relying on the
>design strategy as-is rather than augmenting it. Thus the receiver just
>processes data as usual without needing to make decision about timing.
>(Hold this thought; it comes up again later in the message.)
>
>>>>>The only situation that seems like a problem is if the total computation
>>>>>time on some "witching" ticks where the maximum number of events occur
>>>>>(e.g., a 1 tick sample, a 60 tick statistic, and a 7/60 adjustment all
>>>>>occur on the 420th tick) causes the total computation time to extend
>>>>>beyond the tick interval. That would screw up everything if one did all
>>>>>the processing. Is that what you had in mind?
>>>>
>>>>
>>>>Yep. In the real system, running on a ten year old computer, sometimes
>>>>a scheduled time is missed. Whether it's because of witching ticks or
>>>>delays when reading the GPIB or other, I'm not sure. But that happens on
>>>>a small fraction of the total ticks, and the timing information is passed
>>>>to the functions that need it.
>>>
>>>OK. I described one general approach above, but in the end it depends
>>>on how the skip occurs and what significance it has.
>>>
>>>Unrealistic as it may be, let's assume just for the sake of example that
>>>the statistics tick comes along but there are only 59 samples because
>>>one single tick event never got processed and we need exactly 60 samples
>>>in the statistic for consistency. Let's also assume that everything
>>>will be fine if you do two things: (1) don't invoke computeStatistics on
>>>the current tick and (2) do invoke computeStatistics on the next tick.
>>>
>>>If that is all that needs to be done to keep synchronization, then your
>>>solution of having Timer defer the event generation to the next tick
>>>would work fine. There are lots of ways to do that, such has having
>>>Timer maintain an internal queue of deferred events (which could be a
>>>simple bitmap for events). You could also make Thermometer responsible
>>>for keeping track of how may samples it had since the last statistics
>>
>>
>> Thermometer might be a bad example in a real-time system because it would
>> be replaced by physical components. The end result would be a voltage
>> that is read periodically. But Controller, as a typical PID controller,
>> has an integral and derivative term, and a low-pass filter, that all
>> require a time interval. (And a proportional term that doesn't.)
>
>I was referring to the software responsibilities in the Thermometer
>driver. The values need to be saved as they are sampled and
>periodically some basic statistics on those samples are done (as I
>thought you described it). That processing will be common to both the
>real controller and your simulation and both would be synchronized
>through Timer ticks.
Ah, yes, right.
>
>>>computation. Then my code in Timer.tick for the event generation loop
>>>might look something like:
>>>
>>> int eventTickCount = eventList[i].getTickCount();
>>> bool isReady = eventList[i].getRecipient()->isReady();
>>> if ((tick_count MOD eventTickCount) == 0)
>>> {
>>> if (isReady())
>>> queueManager->push(&eventList[i]);
>>> else
>>> deferredBitmap |= (1 << i); // set deferral of event
>>
>>
>> Woah, what are you doing to deferredBitmap there? Looks kinky.
>
>Again, my R-T/E background is showing. One gets used to bit-wise
>processing because hardware real estate is usually precious to the
>Hardware Guys and they cram multiple fields into a single register. So
>masking, shifting, and bitwise AND, OR, and XOR operations are a Way Of
>Life. A logical extension of that is that bitmaps become very natural
>to use. (I've used bitmaps that were megabytes in length to save memory.)
That must be micocontrollers, because any computer you can get at
Best Buy has a lot of room for sloppy software.
>
>This code is just setting a bit for an event. The bit position in the
>bitmap is the index of the 1-bit boolean (true = set; false = reset or
>0) for whether the event is deferred or not. I used the index of the
>event in eventList as the identity of the event in the bitmap. Alas,
>what isn't shown is an assumption in the declaration of deferredBitmap
>that the total number of events does not exceed the number of bits in
>the int or long associated with deferredBitmap. (If one needs more bits
>in the bitmap, then one needs macros for getting to the right bit and I
>was trying to keep it simple.)
>
>[BTW, apropos of the point previously, I wasn't sure what you thought
>was kinky. So I addressed both the bitmap processing basics via indexed
>OR mask and the deferredBitmap variable assumption about bit count.
>That sort of coverage gets wordy too. B-)]
That |= symbol, mainly. I didn't even recognize it.
>
>>
>>
>>> }
>>> else
>>> {
>>> if ((deferredBitmap &= (1 << i))) && isReady)
>>> {
>>> queueManager->push(&eventList[i]);
>>> deferredBitmap ^= (1 << i); // clear deferred event
>>> }
>>> }
>>>
>>>[There is a drawback here because every receiver needs an isReady()
>>>method even if this notion of readiness is not relevant. (Worse, in C++
>>>isReady needs to be a behavior of Object.) But I have a different point
>>>to make in the example, so ignore the details.]
>>>
>>>I chose this implementation because it is logically almost identical to
>>>your solution where Thermometer decides when to do the computation.
>>>Indirectly Thermometer /is/ driving the decision here. There are,
>>>though, subtle differences.
>>
>>
>> Well, I didn't have Thermometer deciding when to do a calculation. Except
>> in a manner of speaking when it was initialized with Timer. After that,
>> Timer had a list of scheduling information which it updated and used to
>> calculate elapsed times, which were included when events were sent.
>> Thermometer would receive an event and read an elapsed time. There was no
>> communication back to Timer, although I did have Timer itself track
>> whether it had just sent an event there.
>
>For Thermometer to register with Timer, it needs to include Timer's
>header file. That alone means Thermometer knows Timer. (One
Okay.
[shortened]
>Finally, I thought you indicated that the "block" had the responsibility
>to decide whether its processing should be skipped for the current tick.
> If so, then I think that is a substantial dependency since Thermometer
>necessarily needs to understand /how/ Timer does its scheduling just to
>make that decision.
No, I was just assuming that it would be told an elapsed time when it's
triggered. I had Timer taking care of all the timing functionality,
including determining the elapsed time and passing it.
[...]
>>>One corollary of this separation of knowledge and behavior is manifested
>>>in two facts that are rarely mentioned in the OO literature but
>>>logically follows from encapsulation, separation of message and method,
>>>peer-to-peer collaboration, all the rest of that OO stuff. Behavior
>>>messages very rarely carry data in OO applications and behavior methods
>>>never return values in well-formed OO applications. The first is
>>>because knowledge access is synchronous so one accesses it directly from
>>>the source when it is needed. The second is because of the separation
>>>of message an method and encapsulation; returning a value that the
>>>caller uses would create an instant dependence on what the callee did.
>>
>>
>> I want to discuss that last paragraph a little more. The second point,
>> that behavior messages never return values, is something I hadn't really
>> thought of, but have been finding no reason to try to do.
>
>If you meant you are finding no reason to have methods return values,
Yep. I have a lot of voids in my header file.
>then ignore this next bit. If you meant you are finding to no reason to
>try to not return value from behavior methods, then read on...
Oh, what the heck. But I'm going to snip it when I'm done.
>> The first, though, that behavior messages very rarely carry data, that one
>> accesses it directly from the source when it is needed, I need more on
>> that. For starters, I thought behavior messages *do* carry data, or that
>> they can. E.g. when a mouse down event is generated, you're going to want
>> to know where it went down. But in the case we're discussing, I get the
>> idea that if Controller needs to know an elapsed time it should ask Timer
>> for the time, keep its own record of the previous time, and do its own
>> calculation of elapsed time. Which is easily done. But then that
>> requires that Controller knows that Timer exists, which I thought was
>> something we're trying to avoid.
>
>The basic problem being addressed is data integrity. In procedural
>development one of the Big Problems was global data. Global data still
>exists in OO applications for all practical purposes because there is
>almost always a relationship path between any two classes so any class
>can access knowledge in any other class.
I'd never thought of global data in quite that way before.
>However, the OO paradigm
>provides mechanisms for managing access to that data.
>
>The most prominent one is relationship instantiation and navigation.
>Relationships are crucially important to OO development because they
>severely limit what specific /objects/ one can reach via relationship
>navigation. In effect, relationship instantiation "hard-wires"
>participation in relationships so that one can't access a lot of the
>knowledge despite the paths apparent at the Class Model level. One
>simply can't get there from here. While enormously important to the OO
>paradigm, this aspect isn't too relevant here.
>
>Another way that the OO paradigm addresses data, though, is timeliness.
> A big problem with global data is that it was being updated
>unexpectedly. One answer to that is the functional programming approach
>where one simply eliminates persistent state variables and /always/
>passes data. Unfortunately that introduces another potential problem.
>Long chains of state passing may result in the data being out of date by
>the time it is actually processed. This is a serious problem in R-T/E
>systems where data is updated asynchronously by the hardware but it can
>be a problem anywhere.
>
>The OO paradigm then decrees that one should access knowledge
>synchronously from whoever is responsible for it. That ensures that the
>data is up to date. The other side of that coin, though, is making sure
>that the data has been updated /before/ invoking the method that
>accesses it when that is necessary. The OO paradigm formalizes this
>through DbC. Recall that I mentioned that DbC can be used to rigorously
>determine where messages should be originated. The key insight there is
>that the precondition that must be satisfied is compound. In addition
>to simply the algorithmic step sequence (e.g., as in a use case), the
>condition includes the rules and policies for ensuring the data has been
>updated properly.
>
>That paradigm does not work well if one passes data. To link data
>integrity to the precondition for executing the method that uses the
>data, the method must access the data directly from whoever is
>responsible for it. Otherwise the precondition would have to include
>whoever passed the data and in an asynchronous behavior model that gets
>very messy, especially if the data is passed methods through on a series
>of messages.
>
>The exception is snapshots. Sometimes there are rules and policies in
>the problem space that place consistency constraints on multiple pieces
>of data or special constraints on integrity relative to particular
>behaviors. In that case it may be necessary to collect consistent data
>and use it even if it was been updated after its collection. That is
>sort of the problem we talked about above in thinking about the CAMAC
>samples and how they relate to subsequent processing. Special
>synchronization rules may actually /require/ that one copy knowledge or
>pass it in messages.
>
>However, those are the exceptions rather than the rule. One does that
>only when one realizes that synchronous access may not work correctly
>because of other things that are going on in the solution. (One
>advantage of always trying to use synchronous access is that it is
>relative easy to recognize when it won't work because of the limited
>method scope and formulating DbC preconditions.)
>
>Now, to your more specific comments. A mouse move message really
>doesn't count because it originates externally in the OS window manager
>code. It we have applied good OO application partitioning, it is a
>subsystem interface message and we deliberately design those interfaces
>to be pure data transfer interfaces. Such interfaces provide "firewall"
>decoupling between subsystems. So passing the position information is
>something we deliberately design in.
>
>For the second part, I would try to find a way so that the object does
>not need to deal with a notion of elapsed time. That is inherently a
>scheduling issue and it would be bleeding cohesion from Timer for the
>object to deal with that. One way to do that in the overall context is
>the way I suggested above: pass the data itself. Now we are using the
>snapshot approach because the synchronization rules and our basic
>sequencing design may not provide timely data if we use synchronous
>access. Note that no /additional/ information or decisions are
>required; we just use the data passing mechanism rather than synchronous
>access to ensure DbC on data integrity is satisfied.
Remember that 8 pound sledge hammer? In my case, I've decided not to use
an event queue because the strategy of the program has Timer as the sole
passer of messages. There's no event that can be passed which can't be
processed immediately. I've also changed the message passing to be closer
to the example that you gave, and simplified it a little since it's a
simulation and not R-T and so everything is carefully controlled. And I
also load an elapsed time in the events before I send them off, which
wouldn't be necessary if I'd used ticks as the time unit as you did, but
I've decided to stay with seconds because it's closer to the real world,
it's easier to change the time resolution, and because I don't really
expect the processing time to become onerously long because of it. And
what the heck is the difference between an event and a pure data transfer
interface?
>
>However, in any other situation where one could access consistent data
>synchronously, I would opt for asking Timer for any knowledge one needed.
>
>[Caveat. This is the OOA view. When addressing nonfunctional
>requirements during OOP one may have to sacrifice pristine decoupling
>for practical optimization. Just as one might implement OOA state
I'm glad you brought that up, because I've found reasons to want to tell
one class to get data from another class sometimes through one accessor
function, and sometimes through another accessor function. Like the two
sides of a thermal link-- the direction of heat flow depends on which
thermal mass has the higher temperature, and it would be convenient to
specify, say,
heatsink.connect(&(link.side1));
target.connect(&(link.side2));
Which would conveniently make the thermal link just another source (or
sink) of heat that can be thrown into a list with the controller, the
beam, noise, etc., and dealt with in a uniform way with no decision
making, no need to pass a temperature only to thermal link sources of
heat, etc. Or record different aspects of a block, like
ChartRecorder chart1(&(controller.power));
ChartRecorder chart2(&(controller.gain_term));
And I like the sort-of self-documenting nature of that compared with my
original thought to give each class a generic output() member that
outputs whatever that block is supposed to put out. But it turns out
that in C++ pointers to member functions are a tricky thing. The advice
I've found involves static wrapper functions, but you still have to
specify which wrapper function, and that doesn't help if I want to select
from several potential member functions in the same instantiation of a
class. (Although there may just be a solution I don't see.)
That's easily done with public variables, as in
class Class {
public:
float power;
float gain_term;
};
And that could be used with the above code as it is. But we're not
supposed to make variables publicly accessible in C++. We're supposed to
write
class Class {
private:
float power;
float gain_term;
public:
float get_power() {return power;}
float get_gain_term() {return gain_term;}
};
Which is fine as long as you don't need a pointer to the data. As far as
I can see, that makes my scheme, if not unworkable, at least not worth
the effort versus the brute force way of passing a parameter to indicate
which number to get back, and using a switch statement to get the right
value from a member function to return.
I was thinking of just using public variables, calling them an interface,
and suffering the fate of being outcast as a pariah.
>machines as direct method calls in a synchronous environment, one might
>need to avoid the context switches, etc. implied in direct access of
>data in the message sender by passing the data in the original event.
>IOW, the OOA solution addresses functional requirements while OOD/P
>address various nonfunctional requirements. Those are independent
>considerations and one may have to deliberately shoot oneself in the
>foot. The key is to get the functional requirements solution right in
>the OOA and then elaborate nonfunctional requirements on a case-by-case
>basis. Then the skeleton is at least right.]
>
>>>>>The issue is not about providing the programmer options. There is only
>>>>>one way to correctly define the sequencing so the programmer doesn't
>>>>>have any options in that sense. The issue, as in all OO software, is
>>>>>how hard will it be to change the sequencing when the requirements
>>>>>change. Envision the various sorts of changes that might be made in the
>>>>>sequencing of Chart's responsibilities, especially relative to other
>>>>>object's responsibilities. I assert that for many such changes it will
>>>>>be a whole lot easier to do that by manipulating my statements than the
>>>>>implementations of Chart, Timer, and other objects. (In fact, I gave an
>>>>>example, as immediately below...)
>>>>
>>>>
>>>>In my implementation it would only be manipulating Chart, since by design
>>>>Chart just internally takes on the task of doing what you did explicitly
>>>>in main(). And if Chart's responsibilities change, you'd be mucking about
>>>>with it anyway.
>>>
>>>The problem is that proper sequencing is a broader issue than only one
>>>object. Consider:
>>>
>>>Timer.add_event(&chart, E1, 1);
>>>Timer.add_event(&thermometer, E2, 10);
>>>Timer.add_event(&chart, E3, 20);
>>>
>>>vs.
>>>
>>>Timer.add_event(&chart, E1, 1);
>>>Timer.add_event(&chart, E3, 20);
>>>Timer.add_event(&thermometer, E2, 10);
>>>
>>>In my solution this changes the ordering in which the E1, E2, and E3
>>>responses are executed on the 20th tick, which could be significant to
>>>the results. How would you capture that change? I suspect you will
>>>have to change the implementations of at least two objects.
>>
>>
>> I can see that being used to give some basic prioritization, for an
>> example.
>>
>> I know you don't like my thought to issue update events to all the blocks
>> at the end of an iteration, to move new output values into current values.
>> But what you wrote above does suggest an easy way to provide for that.
>>
>> Timer.add_event(&target, eEvolve1, 1);
>> Timer.add_event(&controller, eEvolve1, 7);
>> Timer.add_event(&target, eUpdate, 1);
>> Timer.add_event(&controller, eUpdate, 7);
>>
>> Instead of fiddling around with sorting the time evolution needs from the
>> updating needs, just save all the update events for the end.
>
>It is not so much that I don't like it as I am not convinced it is the
>best way, given alternatives. This gets into the issue of knowledge vs.
>behavior. Keeping a history of samples is a knowledge responsibility.
>I also happen to think it is important enough to the problem so that one
>should model is explicitly at a high level because it is crucial to
>synchronization, accuracy, or however one wants to view it.
>
>If the history can be expressed purely in terms of knowledge (e.g.,
>attributes for old vs. new values), then changing of that knowledge is a
>knowledge operation rather than a behavior operation. Since events are
>only useful in an asynchronous model of behavior communication, I
>wouldn't want to use events for that. [Doing so may also actually
>introduce synchronization issues (though unlikely here) because of the
>implied arbitrary delay between when an event is generated and when it
>is consumed in an asynchronous model.] Moving history values is
>obviously something that could be done just before they are modified.
>So I don't see a need for an event to trigger that; the move would be
>included in the action that did the modification.
>
>That leaves making sure other objects access the right values. As I
>suggested, one can do that via relationships that are instantiated in a
>manner consistent with the "hard-wired" processing order. In fact, I
>would prefer that because it incorporates the synchronization rules
>(lead/follow :: old/new) as static structure. Whenever reasonable one
>should opt for static structure over dynamic behavior for enforcing
>problem space rules. It simplifies the application, usually improves
>performance, and improves reliability.
Another factor is that I understand my way, and I'm not sure I understand
what you're talking about. I mean, relating to my special interface
above, would I declare, for instance,
heatsink.connect(&(link.new_side1));
target.connect(&(link.old_side2));
--
"You're not as dumb as you look. Or sound. Or our best testing
indicates." -- Monty Burns to Homer Simpson
.
- Follow-Ups:
- Re: Lahman, how ya doing?
- From: H. S. Lahman
- Re: Lahman, how ya doing?
- References:
- Re: Lahman, how ya doing?
- From: H. S. Lahman
- Re: Lahman, how ya doing?
- From: Gregory L. Hansen
- Re: Lahman, how ya doing?
- From: H. S. Lahman
- Re: Lahman, how ya doing?
- Prev by Date: Re: Help! Difficulty understanding DB -> Object mapping
- Next by Date: Re: Lahman, how ya doing?
- Previous by thread: Re: Lahman, how ya doing?
- Next by thread: Re: Lahman, how ya doing?
- Index(es):
Relevant Pages
|