Okay, so now we get to talk about issues of sequential consistency and how to
actually go about implementing these things. So going back to our, valid
interleaving. We start to ask ourselves, how do we build a sequentially consistent
model with out of order processors, or out of order execution of memory. Well we
already said in our out of order system that we can re-order loads to different
addresses. We can re-order loads to the same address. Doesn't really matter. Well,
if you go take that, all of a sudden they just breaks a sequential consistency. Huh.
We said we could reorder a store in a load in our out of order processor if they were
to different addresses. We said we could reorder stores and load the other way. We
said we could reorder stores in stores. So out of order memory systems and auto, auto
processors effectively break sequential consistencies. So how you break, how do
you implement a processor which is useful? And is out of order, and we want to go out
of order for performance reasons. Also cache has started to become a big problem
here for performance. If we want to have true sequential consistency you're going
to have to somehow figure out how to not store any data in your cache. Because the
second that you store data in your cache, you're going to have to go, let's say it's
a right back cache if you do a store to your local cache and you write a one to
it, and some other processor tries to read it, well it's not going to be able to see
that data. So we need to be very careful here of when is data actually visible to
other processors if you have a cashe involved in multi-processor system. So we
are going to move towards little bit more weak or the relax memory models. So as I
said last class, almost no processor actually implements a, a truly
sequentially consistent memory system. They're all weaker than that. Now the
question is how weak do you want to go. and this starts to come up because it's a,
it's a tech trade off between what the programmer can wrap their head around
versus effectively performance in an out of order system. So what we can do is we
can think about going to weaker memory systems, and when we do that, we are going
to make the programmer decide when a load in a store can be reordered, or when a
store in a store can be reordered, or when a load in a load can be re reordered. So
let's look at these four different cases independently here. Reordering a load with
a load, reordering a load with a store after it, reordering a store with a load
after it and reordering two stores. And note we're not talking about the same
memory addresses here at all. We are talking about two different addresses, but
sequential consistency even says for different addresses we need to worry about
this. So how many of these extra dependencies can we remove and we're going
to introduce this notion of a memory fence or sometimes we call memory barrier
operation which is a serialization point that says all of the previous instructions
before well, I will be careful what I say there actually. My references is actually
a serialization operation which says all of the memory accesses or at least the
named memory accesses before a certain point have been completed before the
memory fence completes. And, all of the operations after it do not complete, have
not started or completed. Before the memory fence completes so this really is a
barrier on your memory operations and the reason I'm being a little bit hesitant
here is the basic memory fence will say that all memory operations before the
memory fence completes and that none after the memory fence have started when the
memory fence completes. But people have introduced a little bit even weaker
versions of memory fence over time of course, for performance. And you start to
see things where. You'll have only load memory fences, where it barriers against
all the loads previous. Or you have, one you need directional memory fences which
will say only look at all the stores that happen after this point and don't allow
any of them to happen before this point. So, people have actually introduced
instructions in modern architectures that have all these things. And, probably one
of the weaker ones if you actually wanna go look at a really weak memory model, is
go read about the itanium memory models, extremely weak memory model. and they,
they. Need to have fences all over the place. And the, the trade off here, the
performance trade off is how many fences you need to put in. Versus performance you
get from reordering of memory operations. So let's look at a couple, let's, let's
name a few of these things, cause everyone likes naming. And these are computer
architecture research names. These are common across the industry at this point
effectively, One of them is called total store ordering. So in total store
ordering, this is not a big step away from sequential consistency. The stores, some
total store ordering. Loads do not get reordered with other loads. Loads do not
get reordered with other stores. Stores don't get reordered with other s, stores.
But a store followed by a load? Can be reordered. So a load can be moved above a
store here. And if you want to enforce the order between the store and load you need
to write this. You need to do store word and then fence, loadwork. And this
guarantees that this load is going to happen after that store in the processor.
Partial store ordering is a little bit weaker than that. So partial store
ordering, loads of other loads don't get reordered. Loads of other stores don't
need fences, but stores followed by loads, which is up here, and stores followed by
stores also need fences to guarantee ordering. And then finally we get this
class of weak ordering, and frankly, most processors these days actually fall into
some category of weak ordering, and there's sort of different questions in how
weak it is. but we're not going into that little detail here. And here, you actually
need a fence to enforce all of these orderings, so it's kind of, kind of
interesting, interesting to think about. The nice thing about fences is that. While
these o perations are expensive and are big serialization points, the memory
fences, you only need to pay for them when you care about the ordering. When you
don't care about the ordering, the computer architecture can reorder things
and make everything go faster. It's only exactly when you care about it that you
need to introduce defenses. one thing I was going to say is some compilers will do
some of this work for you. Introduce defenses, for you. And usually one of the
ways that it's done actually is if you see atomic operation. So if they see, if a
compiler sees something like a lock operation or P or a V, in your code and
it's sorta a special way to signal back to the compiler. It'll know that you need to
make sure the memory operations have all been serialized at that point. Usually
also atomic operations like test and set are fences on their own right. which
helps, but sometimes compilers can even figure out where other fences are needed
for other memory addresses. And one of the things that I want to say here is that
fences don't actually take a argue bit. It's not going to take an address to go
fence on, and the reason for this is we really want to do is we want to make sure
all the previous memory operations, cuz in the question of consistency we haven't
said anything about the addresses. We just said previous memory operations in one
thread don't move after a subsequent memory operation. Okay, so let's look at
an example here of where we might need to introduce a different, different levels of
fences here. So here we have our producer consumer model. And one of the things we
were worried about in our original examples, we were worried about these two
stores getting reordered. Well, we can force that by putting a memory fence
operation in here, and saying, it's a store, store. So no stores can pass any
other stores, and this'll guarantee that these happen in order. But we don't really
care, in this case we'll say, if, well, this actually already has an arc, so we're
not, that's not going to get reordered, beca use this register is, is, is
dependent there. But let's go over here, and take a look over here. If we have full
sequential consistency, we would not be able to reorder these two loads. Well for
performance maybe we want to reorder those loads because one of the input registers
isn't valable. Because it's the result of a long multiply for instance, or
something, something like that. So you might want to reorder into your out of
order processes for performance reasons. In true sequential consistency you
wouldn't be able to do that. But with this weaker memory model we can reorder that,
but then guarantee correctness by introducing a fence operation here. And
this fence operation is gonna to say. After these loads are done. These loads
need to complete before this load starts. So you can sort of guarantee that if our
head equals our tail, you know at that point that you're, you're safe to go
execute the rest of this code here. Any questions about that so far, about the,
the fences and why? It's kind of a pay as you go, if you will.