On 6/6/2013 9:58 AM, EricP wrote:
> Ivan Godard wrote:
>>
>> There is only one belt, which holds all in-flight data of all types,
>> kinds and sizes. The (potential) size of a belt operand is fixed for
>> any given family member, but will be at least eight bytes. Thus a belt
>> position on Tin, with an 8-byte height, may hold a scalar datum of 1,
>> 2, 4, or 8 bytes in any position, or vectors of 8 1-byte, 4 two-byte,
>> 2 four-byte, or 1 eight-byte elements. A Gold is 32 bytes high, so it
>> has all the scalars of Tin plus 16-byte (quad) scalars in hardware,
>> plus vectors of all scalar element widths whose cumulative length is
>> 32 bytes.
>
>
> In your talk you say that 80% of all vars are used once.
> I assume that any belt value that is not used before
> falling off the belt must be explicitly saved someplace,
> scratch register or memory similar to a register spill.
You have to do something about values you are about to lose but are
still wanted. Storing to memory is possible, but loses metadata attached
to the belt operands (a well known problem for x86 too). Spill to the
scratchpad retains the metadata (scratchpad is not memory).
In addition, a value can be retained on the belt without spill. Consider
what happens to the value in belt position 15 (on a 16 position belt) if
you execute "add b15, 0". The result of the add will drop to the belt,
pushing the former b15 off the end. But now b0 is a copy of the old b15.
:-)
This idea is generalized to the Mill "rescue" op, which takes a belt
argument list like the call operation, and renames the belt so that the
rescued operands are at the front of the belt.
> I think you said the belt had 32 positions (or is that model dependent?).
Model dependent.
> Any idea what the average value lifespan is, relative to belt length?
The spill-to-fill latency using the scratchpad is three cycles at our
current clock rates, so if you need a value within the next three cycles
then you need to keep it on the belt rather than run it out to
scratchpad and back. So we set the belt length to (roughly) the number
of operands that will drop in three cycles at high confidence level at
peak load.
For the rare cases where the code is dropping too many results in those
three cycles then we use rescue or similar to keep them around. The
problem is that high-drop code is usually in pipelined loops, and those
loops also tend to be slot bound so we don't want to waste any of the
encode/execute capacity on rescue ops. The alternative is to use spill
fill and wait for the extra latency, because latency is largely
irrelevant in piped loops. However, spill/fill also chew up operation
slots, so you are not really that much better off.
Complicating configuration definition is that the belt length really
wants to be a power-of-two for encoding reasons, whereas drop rate is a
much smoother function of the number of functional units. As one
increases the number of FUs, the drop rate increases until it bumps so
hard against the belt length that rescue is common and you have to add
slots to make sure you can do all the work you want and a rescue too.
Eventually with increasing FUs you have to bump up the belt length, and
suddenly rescue becomes almost unknown again. This step-function in
constraint is exactly the same problem that designers of
general-register legacy ISAs have to deal with when they are deciding
the number of register to use. At least in our case we don't have a hard
number of bits that we have to encode three register numbers into :-)
> That is to ask, of those read-once values, what percentage can
> be consumed before falling off a 32 position belt,
> verses have to be explicitly saved?
The code generator schedules operations in data-flow order so the
producers and consumers are close to each other in time, and hence in
belt distance. As a practical matter, transient values (one use) almost
never spill; belt is cheap, so we just make it long enough to ensure this.
In addition, program non-loop frame-local scalar variables with multiple
references are allocated in the scratchpad and not on the stack and so
are spilled for later use anyway. As a result they have multiple lives
on the belt: once when first created, and again each time they are
re-filled from scratchpad. As a rule of thumb, half of all references to
such data will be satisfied from the belt without re-filling; i.e. each
lifetime satisfies two references average.
Lastly, loop-carried variables (iteration variables and recurrences) can
be numerous enough that they get pushed off the belt by transient
results before they are updated by the next iteration. Think of code like:
for(int i = 0; i < N; i++)
sum += A[i] + B[i] + C[i];
Here sum is a recurrence and i is an iteration variable. Each gets a new
value in each iteration and so both live in the belt throughout the
loop; they have neither memory nor scratchpad presence.
However, the array references and the add results are transients, which
must also co-exist with sum and i on the belt. If the computation
generates enough transients, sum and i may be off the end of the belt
before the next iteration. You don't want to run the recurrence out to
scratchpad, because the loop is banging one iteration every cycle and
the spill-to-fill latency is three, which would wast a lot of cycles. So
you use rescue on sum and i in every iteration, to lift them out of the
belt rubble of other computation. This is essentially the only place
where you see rescue with any frequency at all.
All this is rules of thumb and guidelines for configuration. We expect
to see a rescue op in complicated piped loops, but I've never seen a
loop with so many recurrence variables that it needs more belt than one
rescue per iteration provides. We expect to spill/fill open-code scalar
locals that have long lifetimes and many references. Other than that, a
spill rate over 1% I would consider cause for reconfiguring to fewer FUs
or more belt.
Ivan