This example's a little more complicated.
For clarity, remember that I'm just hard coding the idea
that there's 128 threads and therefore we're going to operate on 128 elements of the array, all right?
And I'm going to skip all this sort of out-of-bounds check and assertions
that you would normally use to make sure that you're not trying to access a piece of memory that's not there.
So once again, we have a function, use shared memory GPU.
This function is a kernel, and we're going to pass in a local variable,
which is a pointer to a bunch of floats.
And, this local variable is a pointer to global memory that's been allocated in advance.
I wanted to come up with some code that actually did something using shared memory,
and that's why function is a little more complicated than the examples you've seen.
So, we're going to declare a few local variables that are private to each thread,
a couple of indices we'll declare this variable index, just to be shorthand for thread index dot x.
That's just to save some typing.
And we're going to declare a floating point variable called average, and another one called sum.
And we'll initialize sum to 0.0.
Here's the big example.
Now we're going to declare a variable that lives in shared memory.
In this case, it's an array.
Again, I've hard coded that array to contain 128 elements.
It's an array of floats.
And I tag it as being a shared memory with this shared_declspec.
And remember the whole point of shared memory is that
this variable is visible to all the threads in the thread block.
And it only has the lifetime of the thread block.
So it doesn't exist outside of this thread block.
Every thread block is going to have its own copy,
it's own single copy of this array that all of the threads in that thread block can see.
I call it sh_array.
The first thing we're going to do is put some data in this array.
Remember I passed in this array that's in global memory called array,
and I'm basically going to initialize the shared memory array
to contain exactly what's in the global memory array. Notice how I do it.
I'm copying data from this array in global memory to this array in shared memory.
And I'm doing it with a single operation.
How does this work?
If you look at this code, every thread is figuring out what its own index is, okay?
Which thread it is, and it's copying the element at array sub index into the element at shared array sub index.
Okay, so since all of the threads in the thread block will be doing
exactly this operation and since they will all have different values for index,
when this single line has completed in all the threads then
we'll have copied all of the information in this global memory array into our shared memory array.
Okay, and the trick is that operation is going to take some time.
Multiple threads are running.
They're not running all at the same time, so it won't happen instantaneously.
We have to make sure all of these writes to shared memory have completed before we go on
and do anything with that array in shared memory.
And that's what the syncthreads operation is about.
You've seen this before.
We call our barrier to make sure that all the writes to shared memory have completed.
Now after that barrier we're guaranteed that this shared memory array is fully populated.
Every element has been initialized now.
Just to be doing something, I said let's just find
the average of all the elements previous to this thread's index.
What we're going to do is we're going to do is with this for loop
we're going to set i = 0 and go up and up to index, which is the number of this thread.
And we're going to accumulate all the variables in the shared memory array up to this thread's index.
And after we're done we'll compute the average,
which is simply equal to the sum divided by the number of elements that we added up.
And then, once again, now we need to do something with that average.
What I decided to do is to have every thread
look at the average that it just computed of all the elements
in the array to its left if you will.
If the value in the array at this thread's index is greater than the average
that it just computed of all of the elements in the array to the left of this thread's index,
then we're going to set array for this thread's index equal to that average.
In other words, we're going to replace any threads that are greater than
the average of all the threads to the left with that average.
Notice that I'm operating on array and not shared array.
I used this shared memory array to do my averaging.
And that's a good idea, because remember that shared memory is fast.
It's much faster to access than global memory.
Here every thread is accessing a bunch of elements in the array.
It make sense to move this array to first shared memory so that it moves faster.
We'll talk about this later.
But now I'm making this change back in the global memory array.
And that means that when this kernel completes this change is going to be seen by the host
and would also potentially be seen by other threads in other thread blocks if there were any.
And then just to sort of make a point,
here's a piece of code afterwards that has no effect at all,
because I'm going to set an element of the shared memory array to 3.14, but then the kernel completes.
Nothing ever gets done with that value that's sitting in shared memory.
And since the shared memory has the lifetime of the thread block,
this memory evaporates as soon as this thread block completes.
This code has no effect and in fact can probably be removed by the compiler.
Calling a kernel that uses a shared memory is no different
than calling a kernel that uses global memory right.
Since all you can do is pass in a local variable
that points to global memory if you've so allocated it.
Then you know what that kernel does with its shared memory is completely up to it
and not visible to the host code at all.
This code showing how to use shared memory is identical to the code we saw up here.